mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
build(api): bundle the api instead of transpiling at boot (#2227)
This commit is contained in:
@@ -325,7 +325,7 @@ services:
|
||||
limits:
|
||||
memory: 1536mb
|
||||
working_dir: /usr/src/app/fluxer_api
|
||||
command: ["./node_modules/.bin/tsx", "src/WorkerEntrypoint.ts"]
|
||||
command: ["node", "dist/WorkerEntrypoint.js"]
|
||||
environment:
|
||||
<<: *fluxer-env
|
||||
FLUXER_API_WORKER_MODE: all_lanes
|
||||
|
||||
@@ -23,6 +23,7 @@ COPY . .
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN pnpm --filter @fluxer/config run --if-present generate
|
||||
RUN pnpm --filter fluxer_api run build
|
||||
RUN pnpm deploy --legacy --filter=fluxer_api --prod /out
|
||||
|
||||
FROM node:24-bookworm-slim
|
||||
@@ -48,6 +49,7 @@ RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' > /etc/apt/s
|
||||
RUN corepack enable && corepack prepare [email protected] --activate
|
||||
|
||||
COPY --from=deploy /out ./
|
||||
COPY --from=deploy /usr/src/app/fluxer_api/dist ./dist
|
||||
COPY --from=deploy /usr/src/app/tsconfigs /usr/src/app/tsconfigs
|
||||
|
||||
RUN rm -rf pkgs && \
|
||||
@@ -57,7 +59,7 @@ RUN rm -rf pkgs && \
|
||||
ENV HOME=/usr/src/app
|
||||
ENV COREPACK_HOME=/usr/src/app/.cache/corepack
|
||||
ENV NODE_ENV=production
|
||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||
ENV NODE_OPTIONS="--max-old-space-size=2048 --enable-source-maps"
|
||||
ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
|
||||
ENV BUILD_VERSION=${BUILD_VERSION}
|
||||
|
||||
@@ -65,4 +67,4 @@ USER 65532:65532
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["./node_modules/.bin/tsx", "src/AppEntrypoint.ts"]
|
||||
CMD ["node", "dist/AppEntrypoint.js"]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node scripts/build.mjs",
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsgo --noEmit",
|
||||
"dev": "tsx watch --clear-screen=false src/AppEntrypoint.ts",
|
||||
@@ -17,6 +18,7 @@
|
||||
"@bluesky-social/jwk-jose": "catalog:",
|
||||
"@bluesky-social/oauth-client-node": "catalog:",
|
||||
"@bufbuild/protobuf": "^2.12.0",
|
||||
"@elastic/elasticsearch": "catalog:",
|
||||
"@fluxer/config": "workspace:*",
|
||||
"@fluxer/constants": "workspace:*",
|
||||
"@fluxer/date_utils": "workspace:*",
|
||||
@@ -29,6 +31,9 @@
|
||||
"@fluxer/logger": "workspace:*",
|
||||
"@fluxer/schema": "workspace:*",
|
||||
"@fluxer/snowflake": "workspace:*",
|
||||
"@hono/node-server": "catalog:",
|
||||
"@messageformat/core": "catalog:",
|
||||
"@messageformat/parser": "catalog:",
|
||||
"@pkgs/cache": "workspace:*",
|
||||
"@pkgs/captcha": "workspace:*",
|
||||
"@pkgs/cassandra": "workspace:*",
|
||||
@@ -49,28 +54,36 @@
|
||||
"@pkgs/worker": "workspace:*",
|
||||
"@simplewebauthn/server": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@vvo/tzdb": "catalog:",
|
||||
"archiver": "catalog:",
|
||||
"argon2": "catalog:",
|
||||
"bowser": "catalog:",
|
||||
"cassandra-driver": "catalog:",
|
||||
"emoji-regex": "catalog:",
|
||||
"fast-xml-parser": "catalog:",
|
||||
"hi-base32": "catalog:",
|
||||
"hono": "catalog:",
|
||||
"html-entities": "catalog:",
|
||||
"idna-uts46-hx": "catalog:",
|
||||
"ioredis": "catalog:",
|
||||
"itty-time": "catalog:",
|
||||
"jose": "catalog:",
|
||||
"livekit-server-sdk": "catalog:",
|
||||
"lodash": "catalog:",
|
||||
"luxon": "catalog:",
|
||||
"maxmind": "catalog:",
|
||||
"mime": "catalog:",
|
||||
"nats": "catalog:",
|
||||
"nodemailer": "catalog:",
|
||||
"pg": "catalog:",
|
||||
"pino": "catalog:",
|
||||
"sharp": "catalog:",
|
||||
"stripe": "catalog:",
|
||||
"tempy": "catalog:",
|
||||
"transliteration": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"uint8array-extras": "catalog:",
|
||||
"validator": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -78,6 +91,7 @@
|
||||
"@types/lodash": "catalog:",
|
||||
"@types/luxon": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"esbuild": "catalog:",
|
||||
"msw": "catalog:",
|
||||
"vite-tsconfig-paths": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {existsSync, readFileSync, rmSync, statSync} from 'node:fs';
|
||||
import {isBuiltin} from 'node:module';
|
||||
import {dirname, join, resolve} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {build} from 'esbuild';
|
||||
|
||||
const API_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const REPO_ROOT = resolve(API_ROOT, '..');
|
||||
const OUT_DIR = join(API_ROOT, 'dist');
|
||||
const CANDIDATE_SUFFIXES = ['', '.ts', '.tsx', '.js', '.mjs', '/index.ts', '/index.tsx', '/index.js', '/src/index.ts'];
|
||||
|
||||
const WORKSPACE_ROOTS = {
|
||||
'@app/': join(API_ROOT, 'src'),
|
||||
'@pkgs/': join(API_ROOT, 'pkgs'),
|
||||
'@fluxer/': join(REPO_ROOT, 'packages'),
|
||||
};
|
||||
|
||||
function resolveWorkspacePath(specifier) {
|
||||
for (const [prefix, root] of Object.entries(WORKSPACE_ROOTS)) {
|
||||
if (!specifier.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
const base = join(root, specifier.slice(prefix.length));
|
||||
for (const suffix of CANDIDATE_SUFFIXES) {
|
||||
const candidate = `${base}${suffix}`;
|
||||
if (existsSync(candidate) && statSync(candidate).isFile()) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
throw new Error(`Unable to resolve workspace import ${specifier}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function packageNameOf(specifier) {
|
||||
const segments = specifier.split('/');
|
||||
return specifier.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0];
|
||||
}
|
||||
|
||||
const declaredDependencies = new Set(
|
||||
Object.keys(JSON.parse(readFileSync(join(API_ROOT, 'package.json'), 'utf-8')).dependencies),
|
||||
);
|
||||
const undeclaredDependencies = new Set();
|
||||
|
||||
const workspacePlugin = {
|
||||
name: 'fluxer-workspace',
|
||||
setup(pluginBuild) {
|
||||
pluginBuild.onResolve({filter: /^[^./]/}, (args) => {
|
||||
const workspacePath = resolveWorkspacePath(args.path);
|
||||
if (workspacePath) {
|
||||
return {path: workspacePath};
|
||||
}
|
||||
const packageName = packageNameOf(args.path);
|
||||
if (!isBuiltin(args.path) && !declaredDependencies.has(packageName)) {
|
||||
undeclaredDependencies.add(packageName);
|
||||
}
|
||||
return {path: args.path, external: true};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
rmSync(OUT_DIR, {recursive: true, force: true});
|
||||
|
||||
await build({
|
||||
entryPoints: [join(API_ROOT, 'src/AppEntrypoint.ts'), join(API_ROOT, 'src/WorkerEntrypoint.ts')],
|
||||
outdir: OUT_DIR,
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
target: 'node24',
|
||||
charset: 'utf8',
|
||||
sourcemap: true,
|
||||
sourcesContent: false,
|
||||
logLevel: 'info',
|
||||
banner: {js: '// SPDX-License-Identifier: AGPL-3.0-or-later'},
|
||||
plugins: [workspacePlugin],
|
||||
});
|
||||
|
||||
if (undeclaredDependencies.size > 0) {
|
||||
const names = [...undeclaredDependencies].sort().join(', ');
|
||||
throw new Error(
|
||||
`The bundle imports packages that fluxer_api does not declare as dependencies: ${names}. ` +
|
||||
'Workspace packages keep their own node_modules, so the bundle cannot reach them from fluxer_api.',
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import type {Hono} from 'hono';
|
||||
import {RateLimitMiddleware} from '../middleware/RateLimitMiddleware';
|
||||
import {RateLimitConfigs} from '../RateLimitConfig';
|
||||
import type {HonoEnv} from '../types/HonoEnv';
|
||||
import {resolveAssetPath} from '../utils/AssetPaths';
|
||||
|
||||
const SPEC_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), 'openapi.json');
|
||||
const SPEC_PATH = resolveAssetPath('openapi', 'openapi.json');
|
||||
const SPEC_BODY = fs.readFileSync(SPEC_PATH, 'utf-8');
|
||||
|
||||
export function OpenAPIController(app: Hono<HonoEnv>): void {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {existsSync} from 'node:fs';
|
||||
import {dirname, join} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
function findPackageRoot(startDir: string): string {
|
||||
let current = startDir;
|
||||
while (!existsSync(join(current, 'package.json'))) {
|
||||
const parent = dirname(current);
|
||||
if (parent === current) {
|
||||
throw new Error(`Unable to locate the fluxer_api package root from ${startDir}`);
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
const PACKAGE_ROOT = findPackageRoot(dirname(fileURLToPath(import.meta.url)));
|
||||
|
||||
export function resolveAssetPath(...segments: Array<string>): string {
|
||||
return join(PACKAGE_ROOT, 'src', 'api', ...segments);
|
||||
}
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
import {randomInt} from 'node:crypto';
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {dirname, resolve} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {UsernameType} from '@fluxer/schema/src/primitives/UserValidators';
|
||||
import {resolveAssetPath} from './AssetPaths';
|
||||
|
||||
const WORDS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'words');
|
||||
const scales = readFileSync(resolve(WORDS_DIR, 'scales.txt'), 'utf-8').trim().split('\n').filter(Boolean);
|
||||
const tails = readFileSync(resolve(WORDS_DIR, 'tails.txt'), 'utf-8').trim().split('\n').filter(Boolean);
|
||||
const scales = readFileSync(resolveAssetPath('words', 'scales.txt'), 'utf-8').trim().split('\n').filter(Boolean);
|
||||
const tails = readFileSync(resolveAssetPath('words', 'tails.txt'), 'utf-8').trim().split('\n').filter(Boolean);
|
||||
|
||||
function capitalize(word: string): string {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||
|
||||
@@ -42,6 +42,7 @@ import type {RequestCache} from '../middleware/RequestCacheMiddleware';
|
||||
import type {Channel} from '../models/Channel';
|
||||
import type {Message} from '../models/Message';
|
||||
import type {Webhook} from '../models/Webhook';
|
||||
import {resolveAssetPath} from '../utils/AssetPaths';
|
||||
import * as RandomUtils from '../utils/RandomUtils';
|
||||
import type {IWebhookRepository} from './IWebhookRepository';
|
||||
import {transform as GitHubTransform} from './transformers/GitHubTransformer';
|
||||
@@ -620,7 +621,7 @@ export class WebhookService {
|
||||
const cacheKey = `webhook:${webhookId}:avatar:${provider}`;
|
||||
const avatarCache = await this.cacheService.get<string | null>(cacheKey);
|
||||
if (avatarCache) return avatarCache;
|
||||
const avatarFile = await fs.readFile(new URL(`../assets/${provider}.webp`, import.meta.url));
|
||||
const avatarFile = await fs.readFile(resolveAssetPath('assets', `${provider}.webp`));
|
||||
const avatar = await this.avatarService.uploadAvatar({
|
||||
prefix: 'avatars',
|
||||
entityId: webhookId,
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
|
||||
import {randomInt} from 'node:crypto';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
import {resolveAssetPath} from '../utils/AssetPaths';
|
||||
|
||||
let tails: Array<string> | undefined;
|
||||
let scales: Array<string> | undefined;
|
||||
@@ -34,9 +30,8 @@ export function generateConnectionId(): string {
|
||||
}
|
||||
|
||||
function initWords(): void {
|
||||
const wordsDir = path.join(__dirname);
|
||||
tails = parseWordsFile(path.join(wordsDir, 'tails.txt'));
|
||||
scales = parseWordsFile(path.join(wordsDir, 'scales.txt'));
|
||||
tails = parseWordsFile(resolveAssetPath('words', 'tails.txt'));
|
||||
scales = parseWordsFile(resolveAssetPath('words', 'scales.txt'));
|
||||
}
|
||||
|
||||
function parseWordsFile(filePath: string): Array<string> {
|
||||
|
||||
@@ -30,6 +30,22 @@
|
||||
".": {
|
||||
"project": ["scripts/**/*.mjs"]
|
||||
},
|
||||
"fluxer_api": {
|
||||
"ignoreDependencies": [
|
||||
"@elastic/elasticsearch",
|
||||
"@hono/node-server",
|
||||
"@messageformat/core",
|
||||
"@messageformat/parser",
|
||||
"@vvo/tzdb",
|
||||
"emoji-regex",
|
||||
"ioredis",
|
||||
"maxmind",
|
||||
"nodemailer",
|
||||
"pg",
|
||||
"pino",
|
||||
"validator"
|
||||
]
|
||||
},
|
||||
"fluxer_app": {
|
||||
"entry": ["lingui.config.js", "scripts/build-sw.mjs", "scripts/Generate*.ts"],
|
||||
"ignoreDependencies": ["@lingui/swc-plugin"]
|
||||
|
||||
Generated
+42
-10
@@ -440,6 +440,9 @@ importers:
|
||||
'@bufbuild/protobuf':
|
||||
specifier: ^2.12.0
|
||||
version: 2.12.0
|
||||
'@elastic/elasticsearch':
|
||||
specifier: 'catalog:'
|
||||
version: 9.3.2
|
||||
'@fluxer/config':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/config
|
||||
@@ -476,6 +479,15 @@ importers:
|
||||
'@fluxer/snowflake':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/snowflake
|
||||
'@hono/node-server':
|
||||
specifier: 'catalog:'
|
||||
version: 1.19.9(patch_hash=8736af0d82dad75a667b7daed93f88c00065adc9a809d526353e290b150827ed)([email protected])
|
||||
'@messageformat/core':
|
||||
specifier: 'catalog:'
|
||||
version: 3.4.0
|
||||
'@messageformat/parser':
|
||||
specifier: 'catalog:'
|
||||
version: 5.1.1
|
||||
'@pkgs/cache':
|
||||
specifier: workspace:*
|
||||
version: link:pkgs/cache
|
||||
@@ -536,6 +548,9 @@ importers:
|
||||
'@types/node':
|
||||
specifier: 'catalog:'
|
||||
version: 25.3.0
|
||||
'@vvo/tzdb':
|
||||
specifier: 'catalog:'
|
||||
version: 6.198.0
|
||||
archiver:
|
||||
specifier: 'catalog:'
|
||||
version: 7.0.1
|
||||
@@ -548,6 +563,9 @@ importers:
|
||||
cassandra-driver:
|
||||
specifier: 'catalog:'
|
||||
version: 4.8.0
|
||||
emoji-regex:
|
||||
specifier: 'catalog:'
|
||||
version: 10.6.0
|
||||
fast-xml-parser:
|
||||
specifier: 'catalog:'
|
||||
version: 5.3.7
|
||||
@@ -563,6 +581,9 @@ importers:
|
||||
idna-uts46-hx:
|
||||
specifier: 'catalog:'
|
||||
version: 6.1.1
|
||||
ioredis:
|
||||
specifier: 'catalog:'
|
||||
version: 5.9.3
|
||||
itty-time:
|
||||
specifier: 'catalog:'
|
||||
version: 2.0.2
|
||||
@@ -578,12 +599,24 @@ importers:
|
||||
luxon:
|
||||
specifier: 'catalog:'
|
||||
version: 3.7.2
|
||||
maxmind:
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.5
|
||||
mime:
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.0
|
||||
nats:
|
||||
specifier: 'catalog:'
|
||||
version: 2.29.3
|
||||
nodemailer:
|
||||
specifier: 'catalog:'
|
||||
version: 8.0.1
|
||||
pg:
|
||||
specifier: 'catalog:'
|
||||
version: 8.16.3
|
||||
pino:
|
||||
specifier: 'catalog:'
|
||||
version: 10.3.1
|
||||
sharp:
|
||||
specifier: 'catalog:'
|
||||
version: 0.34.5
|
||||
@@ -602,6 +635,9 @@ importers:
|
||||
uint8array-extras:
|
||||
specifier: 'catalog:'
|
||||
version: 1.5.0
|
||||
validator:
|
||||
specifier: 'catalog:'
|
||||
version: 13.15.26
|
||||
zod:
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.6
|
||||
@@ -618,6 +654,9 @@ importers:
|
||||
'@typescript/native-preview':
|
||||
specifier: 'catalog:'
|
||||
version: 7.0.0-dev.20260224.1
|
||||
esbuild:
|
||||
specifier: 'catalog:'
|
||||
version: 0.27.3
|
||||
msw:
|
||||
specifier: 'catalog:'
|
||||
version: 2.12.10(@types/[email protected])([email protected])
|
||||
@@ -10874,10 +10913,6 @@ packages:
|
||||
resolution: {integrity: sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==}
|
||||
engines: {node: '>=18.17'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
@@ -13478,7 +13513,7 @@ snapshots:
|
||||
ms: 2.1.3
|
||||
secure-json-parse: 4.1.0
|
||||
tslib: 2.8.1
|
||||
undici: 7.22.0
|
||||
undici: 7.29.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -21182,7 +21217,7 @@ snapshots:
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
esbuild: 0.27.3
|
||||
esbuild: 0.27.2
|
||||
get-tsconfig: 4.13.6
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
@@ -21253,10 +21288,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]2.0: {}
|
||||
|
||||
[email protected]:
|
||||
optional: true
|
||||
[email protected]9.0: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user