Compare commits

..
24 changed files with 121 additions and 518 deletions
+2 -2
View File
@@ -110,10 +110,10 @@ port: 3000
# Changes how the server interpret the origin IP of the request.
#
# Any format supported by Fastify is accepted.
# Default: do not trust any proxies (i.e. trustProxy: false)
# Default: trust all proxies (i.e. trustProxy: true)
# See: https://fastify.dev/docs/latest/reference/server/#trustproxy
#
# trustProxy: false
# trustProxy: 1
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────
-87
View File
@@ -1,87 +0,0 @@
# this name is used in report-backend-memory.yml so be careful when change name
name: Get backend memory usage
on:
pull_request:
branches:
- master
- develop
paths:
- packages/backend/**
- packages/misskey-js/**
- .github/workflows/get-backend-memory.yml
jobs:
get-memory-usage:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
memory-json-name: [memory-base.json, memory-head.json]
include:
- memory-json-name: memory-base.json
ref: ${{ github.base_ref }}
- memory-json-name: memory-head.json
ref: refs/pull/${{ github.event.number }}/merge
services:
postgres:
image: postgres:18
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:7
ports:
- 56312:6379
steps:
- uses: actions/checkout@v4.3.0
with:
ref: ${{ matrix.ref }}
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
- name: Use Node.js
uses: actions/setup-node@v4.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config/default.yml
- name: Compile Configure
run: pnpm compile-config
- name: Build
run: pnpm build
- name: Run migrations
run: pnpm --filter backend migrate
- name: Measure memory usage
run: |
# Start the server and measure memory usage
node packages/backend/scripts/measure-memory.mjs > ${{ matrix.memory-json-name }}
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: memory-artifact-${{ matrix.memory-json-name }}
path: ${{ matrix.memory-json-name }}
save-pr-number:
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Save PR number
env:
PR_NUMBER: ${{ github.event.number }}
run: |
echo "$PR_NUMBER" > ./pr_number
- uses: actions/upload-artifact@v4
with:
name: memory-artifact-pr-number
path: pr_number
-122
View File
@@ -1,122 +0,0 @@
name: Report backend memory
on:
workflow_run:
types: [completed]
workflows:
- Get backend memory usage # get-backend-memory.yml
jobs:
compare-memory:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
pull-requests: write
steps:
- name: Download artifact
uses: actions/github-script@v7.1.0
with:
script: |
const fs = require('fs');
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name.startsWith("memory-artifact-") || artifact.name == "memory-artifact"
});
await Promise.all(matchArtifacts.map(async (artifact) => {
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: artifact.id,
archive_format: 'zip',
});
await fs.promises.writeFile(`${process.env.GITHUB_WORKSPACE}/${artifact.name}.zip`, Buffer.from(download.data));
}));
- name: Extract all artifacts
run: |
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec unzip {} -d artifacts ';'
ls -la artifacts/
- name: Load PR Number
id: load-pr-num
run: echo "pr-number=$(cat artifacts/pr_number)" >> "$GITHUB_OUTPUT"
- name: Output base
run: cat ./artifacts/memory-base.json
- name: Output head
run: cat ./artifacts/memory-head.json
- name: Compare memory usage
id: compare
run: |
BASE_MEMORY=$(cat ./artifacts/memory-base.json)
HEAD_MEMORY=$(cat ./artifacts/memory-head.json)
BASE_RSS=$(echo "$BASE_MEMORY" | jq -r '.memory.rss // 0')
HEAD_RSS=$(echo "$HEAD_MEMORY" | jq -r '.memory.rss // 0')
# Calculate difference
if [ "$BASE_RSS" -gt 0 ] && [ "$HEAD_RSS" -gt 0 ]; then
DIFF=$((HEAD_RSS - BASE_RSS))
DIFF_PERCENT=$(echo "scale=2; ($DIFF * 100) / $BASE_RSS" | bc)
# Convert to MB for readability
BASE_MB=$(echo "scale=2; $BASE_RSS / 1048576" | bc)
HEAD_MB=$(echo "scale=2; $HEAD_RSS / 1048576" | bc)
DIFF_MB=$(echo "scale=2; $DIFF / 1048576" | bc)
echo "base_mb=$BASE_MB" >> "$GITHUB_OUTPUT"
echo "head_mb=$HEAD_MB" >> "$GITHUB_OUTPUT"
echo "diff_mb=$DIFF_MB" >> "$GITHUB_OUTPUT"
echo "diff_percent=$DIFF_PERCENT" >> "$GITHUB_OUTPUT"
echo "has_data=true" >> "$GITHUB_OUTPUT"
# Determine if this is a significant change (more than 5% increase)
if [ "$(echo "$DIFF_PERCENT > 5" | bc)" -eq 1 ]; then
echo "significant_increase=true" >> "$GITHUB_OUTPUT"
else
echo "significant_increase=false" >> "$GITHUB_OUTPUT"
fi
else
echo "has_data=false" >> "$GITHUB_OUTPUT"
fi
- id: build-comment
name: Build memory comment
run: |
HEADER="## Backend Memory Usage Comparison"
FOOTER="[See workflow logs for details](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})"
echo "$HEADER" > ./output.md
echo >> ./output.md
if [ "${{ steps.compare.outputs.has_data }}" == "true" ]; then
echo "| Metric | base | head | Diff |" >> ./output.md
echo "|--------|------|------|------|" >> ./output.md
echo "| RSS | ${{ steps.compare.outputs.base_mb }} MB | ${{ steps.compare.outputs.head_mb }} MB | ${{ steps.compare.outputs.diff_mb }} MB (${{ steps.compare.outputs.diff_percent }}%) |" >> ./output.md
echo >> ./output.md
if [ "${{ steps.compare.outputs.significant_increase }}" == "true" ]; then
echo "⚠️ **Warning**: Memory usage has increased by more than 5%. Please verify this is not an unintended change." >> ./output.md
echo >> ./output.md
fi
else
echo "Could not retrieve memory usage data." >> ./output.md
echo >> ./output.md
fi
echo "$FOOTER" >> ./output.md
- uses: thollander/actions-comment-pull-request@v2
with:
pr_number: ${{ steps.load-pr-num.outputs.pr-number }}
comment_tag: show_memory_diff
filePath: ./output.md
- name: Tell error to PR
uses: thollander/actions-comment-pull-request@v2
if: failure() && steps.load-pr-num.outputs.pr-number
with:
pr_number: ${{ steps.load-pr-num.outputs.pr-number }}
comment_tag: show_memory_diff_error
message: |
An error occurred while comparing backend memory usage. See [workflow logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
-2
View File
@@ -24,8 +24,6 @@
<a href="https://www.patreon.com/syuilo">
<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-patron-F96854?logoColor=F96854&style=for-the-badge&logo=patreon&labelColor=363B40" alt="become a patron"/></a>
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/misskey-dev/misskey)
</div>
## Thanks
+10 -10
View File
@@ -319,10 +319,10 @@ remoteUserCaution: "Para el usuario remoto, la información está incompleta"
activity: "Actividad"
images: "Imágenes"
image: "Imágenes"
birthday: "Cumpleaños"
birthday: "Fecha de nacimiento"
yearsOld: "{age} años"
registeredDate: "Fecha de registro"
location: "Ubicación"
location: "Lugar"
theme: "Tema"
themeForLightMode: "Tema para usar en Modo Linterna"
themeForDarkMode: "Tema para usar en Modo Oscuro"
@@ -579,7 +579,7 @@ objectStorageSetPublicRead: "Seleccionar \"public-read\" al subir "
s3ForcePathStyleDesc: "Si s3ForcePathStyle esta habilitado el nombre del bucket debe ser especificado como parte de la URL en lugar del nombre de host en la URL. Puede ser necesario activar esta opción cuando se utilice, por ejemplo, Minio en un servidor propio."
serverLogs: "Registros del servidor"
deleteAll: "Eliminar todos"
showFixedPostForm: "Visualizar la ventana de publicación en la parte superior de la línea de tiempo."
showFixedPostForm: "Mostrar el formulario de las entradas encima de la línea de tiempo"
showFixedPostFormInChannel: "Mostrar el formulario de publicación por encima de la cronología (Canales)"
withRepliesByDefaultForNewlyFollowed: "Incluir por defecto respuestas de usuarios recién seguidos en la línea de tiempo"
newNoteRecived: "Tienes una nota nueva"
@@ -844,7 +844,7 @@ jumpToSpecifiedDate: "Saltar a una fecha específica"
showingPastTimeline: "Mostrar líneas de tiempo antiguas"
clear: "Limpiar"
markAllAsRead: "Marcar todo como leído"
goBack: "Anterior"
goBack: "Deseleccionar"
unlikeConfirm: "¿Quitar como favorito?"
fullView: "Vista completa"
quitFullView: "quitar vista completa"
@@ -1511,7 +1511,7 @@ _emojiPalette:
palettes: "Paleta\n"
enableSyncBetweenDevicesForPalettes: "Activar la sincronización de paletas entre dispositivos"
paletteForMain: "Paleta principal"
paletteForReaction: "Paleta utilizada para las reacciones"
paletteForReaction: "Paleta de reacción"
_settings:
driveBanner: "Puedes gestionar y configurar la unidad, comprobar su uso y configurar los ajustes de carga de archivos."
pluginBanner: "Puedes ampliar las funciones del cliente con plugins. Puedes instalar plugins, configurarlos y gestionarlos individualmente."
@@ -1523,7 +1523,7 @@ _settings:
accountData: "Datos de la cuenta"
accountDataBanner: "Exportación e importación para gestionar los datos de la cuenta."
muteAndBlockBanner: "Puedes configurar y gestionar ajustes para ocultar contenidos y restringir acciones a usuarios específicos."
accessibilityBanner: "Puedes personalizar el aspecto y el comportamiento del cliente y configurar los ajustes para optimizar su uso."
accessibilityBanner: "Puedes personalizar los visuales y el comportamiento del cliente, y configurar los ajustes para optimizar el uso."
privacyBanner: "Puedes configurar opciones relacionadas con la privacidad de la cuenta, como la visibilidad del contenido, la posibilidad de descubrir la cuenta y la aprobación de seguimiento."
securityBanner: "Puedes configurar opciones relacionadas con la seguridad de la cuenta, como la contraseña, los métodos de inicio de sesión, las aplicaciones de autenticación y Passkeys."
preferencesBanner: "Puedes configurar el comportamiento general del cliente según tus preferencias."
@@ -1540,7 +1540,7 @@ _settings:
ifOff: "Si está desactivado"
enableSyncThemesBetweenDevices: "Sincronizar los temas instalados entre dispositivos."
enablePullToRefresh: "Tirar para actualizar"
enablePullToRefresh_description: "Si utilizas un ratón, arrastra mientras pulsas la rueda de desplazamiento."
enablePullToRefresh_description: "Si utiliza un ratón, arrastre mientras pulsa la rueda de desplazamiento."
realtimeMode_description: "Establece una conexión con el servidor y actualiza el contenido en tiempo real. Esto puede aumentar el tráfico y el consumo de memoria."
contentsUpdateFrequency: "Frecuencia de adquisición del contenido."
contentsUpdateFrequency_description: "Cuanto mayor sea el valor, más se actualiza el contenido, pero disminuye el rendimiento y aumenta el tráfico y el consumo de memoria."
@@ -2156,7 +2156,7 @@ _accountDelete:
started: "El proceso de eliminación ha comenzado."
inProgress: "La eliminación está en proceso."
_ad:
back: "Anterior"
back: "Deseleccionar"
reduceFrequencyOfThisAd: "Mostrar menos este anuncio."
hide: "No mostrar"
timezoneinfo: "El día de la semana está determidado por la zona horaria del servidor."
@@ -2610,10 +2610,10 @@ _profile:
name: "Nombre"
username: "Nombre de usuario"
description: "Descripción"
youCanIncludeHashtags: "También puedes incluir hashtags en tu biografía"
youCanIncludeHashtags: "Puedes añadir hashtags"
metadata: "información adicional"
metadataEdit: "Editar información adicional"
metadataDescription: "Usando esto puedes mostrar campos de información adicionales en tu perfil."
metadataDescription: "Muestra la información adicional en el perfil"
metadataLabel: "Etiqueta"
metadataContent: "Contenido"
changeAvatar: "Cambiar avatar"
+7 -9
View File
@@ -83,8 +83,6 @@ files: "Allegati"
download: "Scarica"
driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\", e le Note a cui è stato allegato?"
unfollowConfirm: "Vuoi davvero togliere il Following a {name}?"
cancelFollowRequestConfirm: "Vuoi annullare la tua richiesta di follow inviata a {name}?"
rejectFollowRequestConfirm: "Vuoi rifiutare la richiesta di follow ricevuta da {name}?"
exportRequested: "Hai richiesto un'esportazione, e potrebbe volerci tempo. Quando sarà compiuta, il file verrà aggiunto direttamente al Drive."
importRequested: "Hai richiesto un'importazione. Potrebbe richiedere un po' di tempo."
lists: "Liste"
@@ -2352,13 +2350,13 @@ _ago:
yearsAgo: "{n} anni fa"
invalid: "Niente da visualizzare"
_timeIn:
seconds: "Tra {n} secondi"
minutes: "Tra {n} minuti"
hours: "Tra {n} ore"
days: "Tra {n} giorni"
weeks: "Tra {n} settimane"
months: "Tra {n} mesi"
years: "Tra {n} anni"
seconds: "Dopo {n} secondi"
minutes: "Dopo {n} minuti"
hours: "Dopo {n} ore"
days: "Dopo {n} giorni"
weeks: "Dopo {n} settimane"
months: "Dopo {n} mesi"
years: "Dopo {n} anni"
_time:
second: "s"
minute: "min"
+14 -14
View File
@@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2025.12.0-alpha.1",
"version": "2025.12.0-alpha.0",
"codename": "nasubi",
"repository": {
"type": "git",
@@ -22,32 +22,32 @@
],
"private": true,
"scripts": {
"compile-config": "cd packages/backend && pnpm compile-config",
"compile-config": "node ./scripts/compile_config.js",
"build-pre": "node ./scripts/build-pre.js",
"build-assets": "node ./scripts/build-assets.mjs",
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
"build-storybook": "pnpm --filter frontend build-storybook",
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
"start": "pnpm check:connect && cd packages/backend && pnpm compile-config && node ./built/boot/entry.js",
"start:inspect": "cd packages/backend && pnpm compile-config && node --inspect ./built/boot/entry.js",
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./built/boot/entry.js",
"start": "pnpm compile-config && pnpm check:connect && cd packages/backend && pnpm start",
"start:inspect": "cd packages/backend && pnpm start:inspect",
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cross-env NODE_ENV=test pnpm compile-config && cd packages/backend && pnpm start:test",
"cli": "cd packages/backend && pnpm cli",
"init": "pnpm migrate",
"migrate": "cd packages/backend && pnpm migrate",
"revert": "cd packages/backend && pnpm revert",
"check:connect": "cd packages/backend && pnpm check:connect",
"migrate": "pnpm compile-config && cd packages/backend && pnpm migrate",
"revert": "pnpm compile-config && cd packages/backend && pnpm revert",
"check:connect": "pnpm compile-config && cd packages/backend && pnpm check:connect",
"migrateandstart": "pnpm migrate && pnpm start",
"watch": "pnpm dev",
"dev": "node scripts/dev.mjs",
"lint": "pnpm -r lint",
"cy:open": "pnpm cypress open --config-file=cypress.config.ts",
"cy:run": "pnpm cypress run",
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
"e2e-dev-container": "ncp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
"jest": "cd packages/backend && pnpm jest",
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
"test": "pnpm -r test",
"test-and-coverage": "pnpm -r test-and-coverage",
"e2e": "cross-env NODE_ENV=test pnpm compile-config && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
"e2e-dev-container": "ncp ./.config/cypress-devcontainer.yml ./.config/test.yml && cross-env NODE_ENV=test pnpm compile-config && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
"jest": "cross-env NODE_ENV=test pnpm compile-config && cd packages/backend && pnpm jest",
"jest-and-coverage": "cross-env NODE_ENV=test pnpm compile-config && cd packages/backend && pnpm jest-and-coverage",
"test": "cross-env NODE_ENV=test pnpm compile-config && pnpm -r test",
"test-and-coverage": "cross-env NODE_ENV=test pnpm compile-config && pnpm -r test-and-coverage",
"clean": "node ./scripts/clean.js",
"clean-all": "node ./scripts/clean-all.js",
"cleanall": "pnpm clean-all"
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { compiledConfigFilePath } from '../../built/config.js';
import { path as configJsonPath } from '../../built/config.js';
import fs from "node:fs";
export function isConcurrentIndexMigrationEnabled() {
@@ -13,7 +13,7 @@ export function isConcurrentIndexMigrationEnabled() {
let loadedConfigCache = undefined;
function loadConfigInternal() {
const config = JSON.parse(fs.readFileSync(compiledConfigFilePath, 'utf-8'));
const config = JSON.parse(fs.readFileSync(configJsonPath, 'utf-8'));
return {
disallowExternalApRedirect: Boolean(config.disallowExternalApRedirect ?? false),
+16 -18
View File
@@ -7,37 +7,36 @@
"node": "^22.15.0 || ^24.10.0"
},
"scripts": {
"start": "pnpm compile-config && node ./built/boot/entry.js",
"start:inspect": "pnpm compile-config && node --inspect ./built/boot/entry.js",
"start:test": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./built/boot/entry.js",
"migrate": "pnpm compile-config && pnpm typeorm migration:run -d ormconfig.js",
"revert": "pnpm compile-config && pnpm typeorm migration:revert -d ormconfig.js",
"cli": "pnpm compile-config && node ./built/boot/cli.js",
"check:connect": "pnpm compile-config && node ./scripts/check_connect.js",
"compile-config": "node ./scripts/compile_config.js",
"start": "node ./built/boot/entry.js",
"start:inspect": "node --inspect ./built/boot/entry.js",
"start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js",
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
"cli": "node ./built/boot/cli.js",
"check:connect": "node ./scripts/check_connect.js",
"build": "swc src -d built -D --strip-leading-paths",
"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc --strip-leading-paths",
"watch:swc": "swc src -d built -D -w --strip-leading-paths",
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
"watch": "pnpm compile-config && node ./scripts/watch.mjs",
"watch": "node ./scripts/watch.mjs",
"restart": "pnpm build && pnpm start",
"dev": "pnpm compile-config && node ./scripts/dev.mjs",
"dev": "node ./scripts/dev.mjs",
"typecheck": "tsc --noEmit && tsc -p test --noEmit && tsc -p test-federation --noEmit",
"eslint": "eslint --quiet \"{src,test-federation}/**/*.ts\"",
"lint": "pnpm typecheck && pnpm eslint",
"jest": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.unit.cjs",
"jest:e2e": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.e2e.cjs",
"jest:fed": "pnpm compile-config && node ./jest.js --forceExit --config jest.config.fed.cjs",
"jest-and-coverage": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.unit.cjs",
"jest-and-coverage:e2e": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.e2e.cjs",
"jest-clear": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --clearCache",
"jest": "cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.unit.cjs",
"jest:e2e": "cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.e2e.cjs",
"jest:fed": "node ./jest.js --forceExit --config jest.config.fed.cjs",
"jest-and-coverage": "cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.unit.cjs",
"jest-and-coverage:e2e": "cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.e2e.cjs",
"jest-clear": "cross-env NODE_ENV=test node ./jest.js --clearCache",
"test": "pnpm jest",
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
"test:fed": "pnpm jest:fed",
"test-and-coverage": "pnpm jest-and-coverage",
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
"check-migrations": "node scripts/check_migrations_clean.js",
"generate-api-json": "pnpm compile-config && node ./scripts/generate_api_json.js"
"generate-api-json": "node ./scripts/generate_api_json.js"
},
"optionalDependencies": {
"@swc/core-android-arm64": "1.3.11",
@@ -232,7 +231,6 @@
"jest": "29.7.0",
"jest-mock": "29.7.0",
"jest-util": "29.7.0",
"js-yaml": "4.1.1",
"nodemon": "3.1.11",
"pid-port": "2.0.0",
"simple-oauth2": "5.1.0",
@@ -1,50 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* YAMLファイルをJSONファイルに変換するスクリプト
* ビルド前に実行し、ランタイムにjs-yamlを含まないようにする
*/
import fs from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import yaml from 'js-yaml';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
const configDir = resolve(_dirname, '../../../.config');
const OUTPUT_PATH = resolve(configDir, '.config.json');
// TODO: yamlのパースに失敗したときのエラーハンドリング
/**
* YAMLファイルをJSONファイルに変換
* @param {string} ymlPath - YAMLファイルのパス
*/
function yamlToJson(ymlPath) {
if (!fs.existsSync(ymlPath)) {
console.log(`skipped: ${ymlPath} is not found`);
return;
}
const yamlContent = fs.readFileSync(ymlPath, 'utf-8');
const jsonContent = yaml.load(yamlContent);
fs.writeFileSync(OUTPUT_PATH, JSON.stringify({
'_NOTE_': 'This file is auto-generated from YAML file. DO NOT EDIT.',
...jsonContent,
}), 'utf-8');
console.log(`✓ ${ymlPath} → ${OUTPUT_PATH}`);
}
if (process.env.MISSKEY_CONFIG_YML) {
const customYmlPath = resolve(configDir, process.env.MISSKEY_CONFIG_YML);
yamlToJson(customYmlPath);
} else {
yamlToJson(resolve(configDir, process.env.NODE_ENV === 'test' ? 'test.yml' : 'default.yml'));
}
console.log('Configuration compiled');
-152
View File
@@ -1,152 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* This script starts the Misskey backend server, waits for it to be ready,
* measures memory usage, and outputs the result as JSON.
*
* Usage: node scripts/measure-memory.mjs
*/
import { fork } from 'node:child_process';
import { setTimeout } from 'node:timers/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const STARTUP_TIMEOUT = 120000; // 120 seconds timeout for server startup
const MEMORY_SETTLE_TIME = 10000; // Wait 10 seconds after startup for memory to settle
async function measureMemory() {
const startTime = Date.now();
// Start the Misskey backend server using fork to enable IPC
const serverProcess = fork(join(__dirname, '../built/boot/entry.js'), [], {
cwd: join(__dirname, '..'),
env: {
...process.env,
NODE_ENV: 'test',
},
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
});
let serverReady = false;
// Listen for the 'ok' message from the server indicating it's ready
serverProcess.on('message', (message) => {
if (message === 'ok') {
serverReady = true;
}
});
// Handle server output
serverProcess.stdout?.on('data', (data) => {
process.stderr.write(`[server stdout] ${data}`);
});
serverProcess.stderr?.on('data', (data) => {
process.stderr.write(`[server stderr] ${data}`);
});
// Handle server error
serverProcess.on('error', (err) => {
process.stderr.write(`[server error] ${err}\n`);
});
// Wait for server to be ready or timeout
const startupStartTime = Date.now();
while (!serverReady) {
if (Date.now() - startupStartTime > STARTUP_TIMEOUT) {
serverProcess.kill('SIGTERM');
throw new Error('Server startup timeout');
}
await setTimeout(100);
}
const startupTime = Date.now() - startupStartTime;
process.stderr.write(`Server started in ${startupTime}ms\n`);
// Wait for memory to settle
await setTimeout(MEMORY_SETTLE_TIME);
// Get memory usage from the server process via /proc
const pid = serverProcess.pid;
let memoryInfo;
try {
const fs = await import('node:fs/promises');
// Read /proc/[pid]/status for detailed memory info
const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8');
const vmRssMatch = status.match(/VmRSS:\s+(\d+)\s+kB/);
const vmDataMatch = status.match(/VmData:\s+(\d+)\s+kB/);
const vmSizeMatch = status.match(/VmSize:\s+(\d+)\s+kB/);
memoryInfo = {
rss: vmRssMatch ? parseInt(vmRssMatch[1], 10) * 1024 : null,
heapUsed: vmDataMatch ? parseInt(vmDataMatch[1], 10) * 1024 : null,
vmSize: vmSizeMatch ? parseInt(vmSizeMatch[1], 10) * 1024 : null,
};
} catch (err) {
// Fallback: use ps command
process.stderr.write(`Warning: Could not read /proc/${pid}/status: ${err}\n`);
const { execSync } = await import('node:child_process');
try {
const ps = execSync(`ps -o rss= -p ${pid}`, { encoding: 'utf-8' });
const rssKb = parseInt(ps.trim(), 10);
memoryInfo = {
rss: rssKb * 1024,
heapUsed: null,
vmSize: null,
};
} catch {
memoryInfo = {
rss: null,
heapUsed: null,
vmSize: null,
error: 'Could not measure memory',
};
}
}
// Stop the server
serverProcess.kill('SIGTERM');
// Wait for process to exit
let exited = false;
await new Promise((resolve) => {
serverProcess.on('exit', () => {
exited = true;
resolve(undefined);
});
// Force kill after 10 seconds if not exited
setTimeout(10000).then(() => {
if (!exited) {
serverProcess.kill('SIGKILL');
}
resolve(undefined);
});
});
const result = {
timestamp: new Date().toISOString(),
startupTimeMs: startupTime,
memory: memoryInfo,
};
// Output as JSON to stdout
console.log(JSON.stringify(result, null, 2));
}
measureMemory().catch((err) => {
console.error(JSON.stringify({
error: err.message,
timestamp: new Date().toISOString(),
}));
process.exit(1);
});
+9 -7
View File
@@ -217,15 +217,17 @@ export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
const configDir = `${_dirname}/../../../.config`;
/**
* Path of configuration directory
*/
const dir = `${_dirname}/../../../.config`;
export const compiledConfigFilePath = resolve(configDir, '.config.json');
/**
* Path of configuration file
*/
export const path = resolve(dir, 'config.json');
export function loadConfig(): Config {
if (!fs.existsSync(compiledConfigFilePath)) {
throw new Error('Compiled configuration file not found. Try running \'pnpm compile-config\'.');
}
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8'));
const frontendManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_vite_/manifest.json');
@@ -237,7 +239,7 @@ export function loadConfig(): Config {
JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8'))
: { 'src/boot.ts': { file: null } };
const config = JSON.parse(fs.readFileSync(compiledConfigFilePath, 'utf-8')) as Source;
const config = JSON.parse(fs.readFileSync(path, 'utf-8')) as Source;
const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? '');
const version = meta.version;
+1 -1
View File
@@ -75,7 +75,7 @@ export class ServerService implements OnApplicationShutdown {
@bindThis
public async launch(): Promise<void> {
const fastify = Fastify({
trustProxy: this.config.trustProxy ?? false,
trustProxy: this.config.trustProxy ?? true,
logger: false,
});
this.#fastify = fastify;
@@ -40,10 +40,6 @@ services:
source: ./.config/a.test.default.yml
target: /misskey/.config/default.yml
read_only: true
- type: bind
source: ../scripts/compile_config.js
target: /misskey/packages/backend/scripts/compile_config.js
read_only: true
db.a.test:
extends:
@@ -40,10 +40,6 @@ services:
source: ./.config/b.test.default.yml
target: /misskey/.config/default.yml
read_only: true
- type: bind
source: ../scripts/compile_config.js
target: /misskey/packages/backend/scripts/compile_config.js
read_only: true
db.b.test:
extends:
@@ -42,10 +42,6 @@ services:
source: ../package.json
target: /misskey/packages/backend/package.json
read_only: true
- type: bind
source: ../scripts/compile_config.js
target: /misskey/packages/backend/scripts/compile_config.js
read_only: true
- type: bind
source: ../../misskey-js/built
target: /misskey/packages/misskey-js/built
@@ -54,10 +54,6 @@ services:
source: ../jest.js
target: /misskey/packages/backend/jest.js
read_only: true
- type: bind
source: ../scripts/compile_config.js
target: /misskey/packages/backend/scripts/compile_config.js
read_only: true
- type: bind
source: ../../misskey-js/built
target: /misskey/packages/misskey-js/built
+20 -5
View File
@@ -11,6 +11,7 @@ import * as esbuild from 'esbuild';
import { build } from 'esbuild';
import { execa } from 'execa';
import { globSync } from 'glob';
import * as yaml from 'js-yaml';
import { generateLocaleInterface } from './scripts/generateLocaleInterface.js';
import type { BuildOptions, BuildResult, Plugin, PluginBuild } from 'esbuild';
@@ -49,7 +50,18 @@ if (args.includes('--watch')) {
await buildSrc();
}
function copyLocales(): void {
/**
* 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く
*/
function clean(text: string) {
return text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), '');
}
/**
* Convert locale YAML files to JSON during build
* This allows runtime to avoid loading js-yaml
*/
function compileLocales(): void {
const srcDir = _localesDir;
const destDir = resolve(_dirname, 'built/locales');
@@ -57,9 +69,12 @@ function copyLocales(): void {
const files = fs.readdirSync(srcDir).filter(f => f.endsWith('.yml'));
for (const file of files) {
fs.copyFileSync(resolve(srcDir, file), resolve(destDir, file));
const yamlContent = clean(fs.readFileSync(resolve(srcDir, file), 'utf-8'));
const jsonContent = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
const jsonFile = file.replace(/\.yml$/, '.json');
fs.writeFileSync(resolve(destDir, jsonFile), JSON.stringify(jsonContent), 'utf-8');
}
console.log(`[${_package.name}] locales copied (${files.length} files).`);
console.log(`[${_package.name}] locales compiled to JSON (${files.length} files).`);
}
/**
@@ -87,7 +102,7 @@ async function buildSrc(): Promise<void> {
process.exit(1);
});
copyLocales();
compileLocales();
await writeFrontendLocalesJson();
if (process.env.NODE_ENV === 'production') {
@@ -123,7 +138,7 @@ async function watchSrc(): Promise<void> {
localesWatcher.on('all', async (event, path) => {
if (!path.endsWith('.yml')) return;
console.log(`[${_package.name}] locales changed: ${event} ${path}`);
copyLocales();
compileLocales();
await writeFrontendLocalesJson();
await generateLocaleInterface(_localesDir);
});
+1 -3
View File
@@ -36,11 +36,9 @@
"esbuild": "0.27.0",
"execa": "9.6.0",
"glob": "11.1.0",
"js-yaml": "4.1.1",
"nodemon": "3.1.11",
"tsx": "4.20.6",
"typescript": "5.9.3"
},
"dependencies": {
"js-yaml": "4.1.1"
}
}
@@ -62,7 +62,7 @@ function createMembers(record: LocaleRecord): ts.TypeElement[] {
}
export async function generateLocaleInterface(localesDir: string): Promise<void> {
const locale = yaml.load(fs.readFileSync(`${localesDir}/ja-JP.yml`, 'utf-8').toString()) as LocaleRecord;
const locale = yaml.load(fs.readFileSync(`${localesDir}/ja-JP.yml`, 'utf-8').toString(), { schema: yaml.JSON_SCHEMA }) as LocaleRecord;
const members = createMembers(locale);
const elements: ts.Statement[] = [
+1 -9
View File
@@ -8,7 +8,6 @@
*/
import * as fs from 'node:fs';
import * as yaml from 'js-yaml';
import { languages, primaries } from './const.js';
import type { Locale } from './autogen/locale.js';
import type { ILocale, ParameterizedString } from './types.js';
@@ -35,13 +34,6 @@ function merge<T extends ILocale>(...args: (T | ILocale | undefined)[]): T {
}), {} as ILocale) as T;
}
/**
* 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く
*/
function clean (text: string) {
return text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), '');
}
/**
* 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す
*/
@@ -62,7 +54,7 @@ function build(): Record<Language, Locale> {
// https://github.com/misskey-dev/misskey/pull/14057#issuecomment-2192833785
const metaUrl = import.meta.url;
const locales = languages.reduce<Locales>((a, lang) => {
a[lang] = (yaml.load(clean(fs.readFileSync(new URL(`./locales/${lang}.yml`, metaUrl), 'utf-8'))) ?? {}) as ILocale;
a[lang] = (JSON.parse(fs.readFileSync(new URL(`./locales/${lang}.json`, metaUrl), 'utf-8')) ?? {}) as ILocale;
return a;
}, {} as Locales);
+1 -1
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
"version": "2025.12.0-alpha.1",
"version": "2025.12.0-alpha.0",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
+3 -7
View File
@@ -571,9 +571,6 @@ importers:
jest-util:
specifier: 29.7.0
version: 29.7.0
js-yaml:
specifier: 4.1.1
version: 4.1.1
nodemon:
specifier: 3.1.11
version: 3.1.11
@@ -1328,10 +1325,6 @@ importers:
version: 10.2.0(eslint@9.39.1)
packages/i18n:
dependencies:
js-yaml:
specifier: 4.1.1
version: 4.1.1
devDependencies:
'@types/js-yaml':
specifier: 4.0.9
@@ -1357,6 +1350,9 @@ importers:
glob:
specifier: 11.1.0
version: 11.1.0
js-yaml:
specifier: 4.1.1
version: 4.1.1
nodemon:
specifier: 3.1.11
version: 3.1.11
+33
View File
@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
import * as yaml from 'js-yaml';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
const dir = `${_dirname}/../.config`;
const configYmlPath = process.env.MISSKEY_CONFIG_YML
? resolve(dir, process.env.MISSKEY_CONFIG_YML)
: process.env.NODE_ENV === 'test'
? resolve(dir, 'test.yml')
: resolve(dir, 'default.yml');
const configJsonPath = resolve(dir, 'config.json');
if (!fs.existsSync(configYmlPath)) {
console.error(`Configuration file not found: ${configYmlPath}`);
process.exit(1);
}
const yamlContent = fs.readFileSync(configYmlPath, 'utf-8');
const config = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
fs.writeFileSync(configJsonPath, JSON.stringify(config), 'utf-8');
console.log(`Compiled config: ${configYmlPath} -> ${configJsonPath}`);