Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f8d6b286b | ||
|
|
731a1daaf2 | ||
|
|
791684efc0 | ||
|
|
570a795785 | ||
|
|
36bec1cadb | ||
|
|
811d6dc8b3 | ||
|
|
a7e4518131 | ||
|
|
d6d5606d68 | ||
|
|
bf50e2ddde | ||
|
|
ffdc78b354 | ||
|
|
69c8d8e102 | ||
|
|
5e6fe5bef6 |
+2
-2
@@ -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 └────────────────────────────────
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"workspaceFolder": "/workspace",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "22.15.0"
|
||||
"version": "24.10.0"
|
||||
},
|
||||
"ghcr.io/devcontainers-extra/features/pnpm:2": {
|
||||
"version": "10.10.0"
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
+1
-1
@@ -1 +1 @@
|
||||
22.15.0
|
||||
24.10.0
|
||||
|
||||
+3
-18
@@ -1,31 +1,16 @@
|
||||
## 2025.12.1
|
||||
## 2025.12.0
|
||||
|
||||
### General
|
||||
-
|
||||
|
||||
### Client
|
||||
- Fix: 特定の条件下でMisskeyが起動せず空白のページが表示されることがある問題を軽減
|
||||
- Fix: 初回読み込み時などに、言語設定で不整合が発生することがある問題を修正
|
||||
- Fix: 削除されたノートのリノートが正しく動作されない問題を修正
|
||||
- Fix: チャンネルオーナーが削除済みの時にチャンネルのヘッダーメニューが表示されない不具合を修正
|
||||
|
||||
### Server
|
||||
- Fix: ジョブキューでSentryが有効にならない問題を修正
|
||||
|
||||
|
||||
## 2025.12.0
|
||||
|
||||
### Note
|
||||
- configの`trustProxy`のデフォルト値を`false`に変更しました。アップデート前に現在のconfigをご確認の上、必要に応じて値を変更してください。
|
||||
|
||||
### Client
|
||||
- Fix: stacking router viewで連続して戻る操作を行うと何も表示されなくなる問題を修正
|
||||
-
|
||||
|
||||
### Server
|
||||
- Enhance: メモリ使用量を削減しました
|
||||
- Enhance: ActivityPubアクティビティを送信する際のパフォーマンス向上
|
||||
- Enhance: 依存関係の更新
|
||||
- Fix: セキュリティに関する修正
|
||||
|
||||
|
||||
## 2025.11.1
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.4
|
||||
|
||||
ARG NODE_VERSION=22.15.0-bookworm
|
||||
ARG NODE_VERSION=24.10.0-bookworm
|
||||
|
||||
# build assets & compile TypeScript
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
[](https://deepwiki.com/misskey-dev/misskey)
|
||||
|
||||
</div>
|
||||
|
||||
## Thanks
|
||||
|
||||
+10
-10
@@ -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
@@ -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"
|
||||
|
||||
+18
-16
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.12.1-alpha.0",
|
||||
"version": "2025.12.0-alpha.0",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/misskey-dev/misskey.git"
|
||||
},
|
||||
"packageManager": "pnpm@10.24.0",
|
||||
"packageManager": "pnpm@10.23.0",
|
||||
"workspaces": [
|
||||
"packages/misskey-js",
|
||||
"packages/i18n",
|
||||
@@ -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"
|
||||
@@ -60,6 +60,8 @@
|
||||
"cssnano": "7.1.2",
|
||||
"esbuild": "0.27.0",
|
||||
"execa": "9.6.0",
|
||||
"fast-glob": "3.3.3",
|
||||
"glob": "13.0.0",
|
||||
"ignore-walk": "8.0.0",
|
||||
"js-yaml": "4.1.1",
|
||||
"postcss": "8.5.6",
|
||||
@@ -79,7 +81,7 @@
|
||||
"eslint": "9.39.1",
|
||||
"globals": "16.5.0",
|
||||
"ncp": "2.0.0",
|
||||
"pnpm": "10.24.0",
|
||||
"pnpm": "10.23.0",
|
||||
"start-server-and-test": "2.1.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
const isConcurrentIndexMigrationEnabled = process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1';
|
||||
import { isConcurrentIndexMigrationEnabled } from "./js/migration-config.js";
|
||||
|
||||
export class CompositeNoteIndex1745378064470 {
|
||||
name = 'CompositeNoteIndex1745378064470';
|
||||
transaction = isConcurrentIndexMigrationEnabled ? false : undefined;
|
||||
transaction = isConcurrentIndexMigrationEnabled() ? false : undefined;
|
||||
|
||||
async up(queryRunner) {
|
||||
const concurrently = isConcurrentIndexMigrationEnabled;
|
||||
const concurrently = isConcurrentIndexMigrationEnabled();
|
||||
|
||||
if (concurrently) {
|
||||
const hasValidIndex = await queryRunner.query(`SELECT indisvalid FROM pg_index INNER JOIN pg_class ON pg_index.indexrelid = pg_class.oid WHERE pg_class.relname = 'IDX_724b311e6f883751f261ebe378'`);
|
||||
@@ -29,7 +29,7 @@ export class CompositeNoteIndex1745378064470 {
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
const mayConcurrently = isConcurrentIndexMigrationEnabled ? 'CONCURRENTLY' : '';
|
||||
const mayConcurrently = isConcurrentIndexMigrationEnabled() ? 'CONCURRENTLY' : '';
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_724b311e6f883751f261ebe378"`);
|
||||
await queryRunner.query(`CREATE INDEX ${mayConcurrently} "IDX_5b87d9d19127bd5d92026017a7" ON "note" ("userId")`);
|
||||
}
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import {loadConfig} from "./js/migration-config.js";
|
||||
|
||||
export class MigrateSomeConfigFileSettingsToMeta1746949539915 {
|
||||
name = 'MigrateSomeConfigFileSettingsToMeta1746949539915'
|
||||
|
||||
async up(queryRunner) {
|
||||
const config = loadConfig();
|
||||
// $1 cannot be used in ALTER TABLE queries
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "proxyRemoteFiles" boolean NOT NULL DEFAULT TRUE`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "signToActivityPubGet" boolean NOT NULL DEFAULT TRUE`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "allowExternalApRedirect" boolean NOT NULL DEFAULT TRUE`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "proxyRemoteFiles" boolean NOT NULL DEFAULT ${config.proxyRemoteFiles}`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "signToActivityPubGet" boolean NOT NULL DEFAULT ${config.signToActivityPubGet}`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "allowExternalApRedirect" boolean NOT NULL DEFAULT ${!config.disallowExternalApRedirect}`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { path as configJsonPath } from '../../built/config.js';
|
||||
import fs from "node:fs";
|
||||
|
||||
export function isConcurrentIndexMigrationEnabled() {
|
||||
return process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1';
|
||||
}
|
||||
|
||||
let loadedConfigCache = undefined;
|
||||
|
||||
function loadConfigInternal() {
|
||||
const config = JSON.parse(fs.readFileSync(configJsonPath, 'utf-8'));
|
||||
|
||||
return {
|
||||
disallowExternalApRedirect: Boolean(config.disallowExternalApRedirect ?? false),
|
||||
proxyRemoteFiles: Boolean(config.proxyRemoteFiles ?? false),
|
||||
signToActivityPubGet: Boolean(config.signToActivityPubGet ?? true),
|
||||
}
|
||||
}
|
||||
|
||||
export function loadConfig() {
|
||||
if (loadedConfigCache === undefined) {
|
||||
loadedConfigCache = loadConfigInternal();
|
||||
}
|
||||
return loadedConfigCache;
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { loadConfig } from './built/config.js';
|
||||
import { entities } from './built/postgres.js';
|
||||
|
||||
const isConcurrentIndexMigrationEnabled = process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1';
|
||||
import { isConcurrentIndexMigrationEnabled } from "./migration/js/migration-config.js";
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
@@ -16,5 +15,5 @@ export default new DataSource({
|
||||
extra: config.db.extra,
|
||||
entities: entities,
|
||||
migrations: ['migration/*.js'],
|
||||
migrationsTransactionMode: isConcurrentIndexMigrationEnabled ? 'each' : 'all',
|
||||
migrationsTransactionMode: isConcurrentIndexMigrationEnabled() ? 'each' : 'all',
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
@@ -71,10 +70,11 @@
|
||||
"utf-8-validate": "6.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.940.0",
|
||||
"@aws-sdk/lib-storage": "3.940.0",
|
||||
"@aws-sdk/client-s3": "3.937.0",
|
||||
"@aws-sdk/lib-storage": "3.937.0",
|
||||
"@discordapp/twemoji": "16.0.1",
|
||||
"@fastify/accepts": "5.0.3",
|
||||
"@fastify/cookie": "11.0.2",
|
||||
"@fastify/cors": "11.1.0",
|
||||
"@fastify/express": "4.0.2",
|
||||
"@fastify/http-proxy": "11.3.0",
|
||||
@@ -83,13 +83,13 @@
|
||||
"@kitajs/html": "4.2.11",
|
||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
"@napi-rs/canvas": "0.1.83",
|
||||
"@napi-rs/canvas": "0.1.82",
|
||||
"@nestjs/common": "11.1.9",
|
||||
"@nestjs/core": "11.1.9",
|
||||
"@nestjs/testing": "11.1.9",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sentry/node": "10.27.0",
|
||||
"@sentry/profiling-node": "10.27.0",
|
||||
"@sentry/node": "10.26.0",
|
||||
"@sentry/profiling-node": "10.26.0",
|
||||
"@simplewebauthn/server": "13.2.2",
|
||||
"@sinonjs/fake-timers": "15.0.0",
|
||||
"@smithy/node-http-handler": "4.4.5",
|
||||
@@ -103,9 +103,10 @@
|
||||
"async-mutex": "0.5.0",
|
||||
"bcryptjs": "3.0.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "2.2.1",
|
||||
"bullmq": "5.65.0",
|
||||
"body-parser": "2.2.0",
|
||||
"bullmq": "5.64.1",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "10.0.11",
|
||||
"chalk": "5.6.2",
|
||||
"chalk-template": "1.1.2",
|
||||
"chokidar": "4.0.3",
|
||||
@@ -119,16 +120,17 @@
|
||||
"file-type": "21.1.1",
|
||||
"fluent-ffmpeg": "2.1.3",
|
||||
"form-data": "4.0.5",
|
||||
"got": "14.6.5",
|
||||
"got": "14.6.4",
|
||||
"hpagent": "1.2.0",
|
||||
"http-link-header": "1.1.3",
|
||||
"i18n": "workspace:*",
|
||||
"ioredis": "5.8.2",
|
||||
"ip-cidr": "4.0.2",
|
||||
"ipaddr.js": "2.3.0",
|
||||
"ipaddr.js": "2.2.0",
|
||||
"is-svg": "6.1.0",
|
||||
"json5": "2.2.3",
|
||||
"jsonld": "9.0.0",
|
||||
"jsrsasign": "11.1.0",
|
||||
"juice": "11.0.3",
|
||||
"meilisearch": "0.54.0",
|
||||
"mfm-js": "0.25.0",
|
||||
@@ -140,14 +142,15 @@
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"node-html-parser": "7.0.1",
|
||||
"nodemailer": "7.0.11",
|
||||
"nodemailer": "7.0.10",
|
||||
"nsfwjs": "4.2.0",
|
||||
"oauth": "0.10.2",
|
||||
"oauth2orize": "1.12.0",
|
||||
"oauth2orize-pkce": "0.1.2",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.4.1",
|
||||
"pg": "8.16.3",
|
||||
"pkce-challenge": "5.0.1",
|
||||
"pkce-challenge": "5.0.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
"qrcode": "1.5.4",
|
||||
@@ -170,6 +173,7 @@
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.5",
|
||||
"tsc-alias": "1.8.16",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typeorm": "0.3.27",
|
||||
"typescript": "5.9.3",
|
||||
"ulid": "3.0.1",
|
||||
@@ -182,7 +186,7 @@
|
||||
"@jest/globals": "29.7.0",
|
||||
"@kitajs/ts-html-plugin": "4.1.3",
|
||||
"@nestjs/platform-express": "11.1.9",
|
||||
"@sentry/vue": "10.27.0",
|
||||
"@sentry/vue": "10.26.0",
|
||||
"@simplewebauthn/types": "12.0.0",
|
||||
"@swc/jest": "0.2.39",
|
||||
"@types/accepts": "1.3.7",
|
||||
@@ -194,10 +198,12 @@
|
||||
"@types/http-link-header": "1.0.7",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/jsonld": "1.5.15",
|
||||
"@types/jsrsasign": "10.5.15",
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/ms": "2.1.0",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/nodemailer": "7.0.4",
|
||||
"@types/oauth": "0.9.6",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
"@types/pg": "8.15.6",
|
||||
@@ -215,17 +221,16 @@
|
||||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.4",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.48.0",
|
||||
"@typescript-eslint/parser": "8.48.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"aws-sdk-client-mock": "4.1.0",
|
||||
"cbor": "10.0.11",
|
||||
"cross-env": "10.1.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"execa": "9.6.0",
|
||||
"fkill": "10.0.1",
|
||||
"jest": "29.7.0",
|
||||
"jest-mock": "29.7.0",
|
||||
"js-yaml": "4.1.1",
|
||||
"jest-util": "29.7.0",
|
||||
"nodemon": "3.1.11",
|
||||
"pid-port": "2.0.0",
|
||||
"simple-oauth2": "5.1.0",
|
||||
|
||||
@@ -1,54 +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(_dirname, '../../../built/.config.json');
|
||||
|
||||
// TODO: yamlのパースに失敗したときのエラーハンドリング
|
||||
|
||||
/**
|
||||
* YAMLファイルをJSONファイルに変換
|
||||
* @param {string} ymlPath - YAMLファイルのパス
|
||||
*/
|
||||
function yamlToJson(ymlPath) {
|
||||
if (!fs.existsSync(ymlPath)) {
|
||||
console.warn(`YAML file not found: ${ymlPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${ymlPath} → ${OUTPUT_PATH}`);
|
||||
|
||||
const yamlContent = fs.readFileSync(ymlPath, 'utf-8');
|
||||
const jsonContent = yaml.load(yamlContent);
|
||||
if (!fs.existsSync(dirname(OUTPUT_PATH))) {
|
||||
fs.mkdirSync(dirname(OUTPUT_PATH), { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(OUTPUT_PATH, JSON.stringify({
|
||||
'_NOTE_': 'This file is auto-generated from YAML file. DO NOT EDIT.',
|
||||
...jsonContent,
|
||||
}), 'utf-8');
|
||||
}
|
||||
|
||||
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 ✓');
|
||||
@@ -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);
|
||||
});
|
||||
@@ -217,15 +217,17 @@ export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch';
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
||||
const compiledConfigFilePathForTest = resolve(_dirname, '../../../built/._config_.json');
|
||||
/**
|
||||
* Path of configuration directory
|
||||
*/
|
||||
const dir = `${_dirname}/../../../.config`;
|
||||
|
||||
export const compiledConfigFilePath = fs.existsSync(compiledConfigFilePathForTest) ? compiledConfigFilePathForTest : resolve(_dirname, '../../../built/.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;
|
||||
|
||||
@@ -15,7 +15,6 @@ import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepos
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { DebounceLoader } from '@/misc/loader.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js';
|
||||
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
||||
@@ -117,7 +116,12 @@ export class NoteEntityService implements OnModuleInit {
|
||||
private treatVisibility(packedNote: Packed<'Note'>): Packed<'Note'>['visibility'] {
|
||||
if (packedNote.visibility === 'public' || packedNote.visibility === 'home') {
|
||||
const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore;
|
||||
if (shouldHideNoteByTime(followersOnlyBefore, packedNote.createdAt)) {
|
||||
if ((followersOnlyBefore != null)
|
||||
&& (
|
||||
(followersOnlyBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (followersOnlyBefore * 1000)))
|
||||
|| (followersOnlyBefore > 0 && (new Date(packedNote.createdAt).getTime() < followersOnlyBefore * 1000))
|
||||
)
|
||||
) {
|
||||
packedNote.visibility = 'followers';
|
||||
}
|
||||
}
|
||||
@@ -137,7 +141,12 @@ export class NoteEntityService implements OnModuleInit {
|
||||
|
||||
if (!hide) {
|
||||
const hiddenBefore = packedNote.user.makeNotesHiddenBefore;
|
||||
if (shouldHideNoteByTime(hiddenBefore, packedNote.createdAt)) {
|
||||
if ((hiddenBefore != null)
|
||||
&& (
|
||||
(hiddenBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (hiddenBefore * 1000)))
|
||||
|| (hiddenBefore > 0 && (new Date(packedNote.createdAt).getTime() < hiddenBefore * 1000))
|
||||
)
|
||||
) {
|
||||
hide = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* ノートが指定された時間条件に基づいて非表示対象かどうかを判定する
|
||||
* @param hiddenBefore 非表示条件(負の値: 作成からの経過秒数、正の値: UNIXタイムスタンプ秒、null: 判定しない)
|
||||
* @param createdAt ノートの作成日時(ISO 8601形式の文字列 または Date オブジェクト)
|
||||
* @returns 非表示にすべき場合は true
|
||||
*/
|
||||
export function shouldHideNoteByTime(hiddenBefore: number | null | undefined, createdAt: string | Date): boolean {
|
||||
if (hiddenBefore == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const createdAtTime = typeof createdAt === 'string' ? new Date(createdAt).getTime() : createdAt.getTime();
|
||||
|
||||
if (hiddenBefore <= 0) {
|
||||
// 負の値: 作成からの経過時間(秒)で判定
|
||||
const elapsedSeconds = (Date.now() - createdAtTime) / 1000;
|
||||
const hideAfterSeconds = Math.abs(hiddenBefore);
|
||||
return elapsedSeconds >= hideAfterSeconds;
|
||||
} else {
|
||||
// 正の値: 絶対的なタイムスタンプ(秒)で判定
|
||||
const createdAtSeconds = createdAtTime / 1000;
|
||||
return createdAtSeconds <= hiddenBefore;
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
let Sentry: typeof import('@sentry/node') | undefined;
|
||||
if (this.config.sentryForBackend) {
|
||||
if (Sentry != null) {
|
||||
import('@sentry/node').then((mod) => {
|
||||
Sentry = mod;
|
||||
});
|
||||
|
||||
@@ -5,20 +5,21 @@
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import { Writable } from 'node:stream';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable, StreamableFile } from '@nestjs/common';
|
||||
import { MoreThan } from 'typeorm';
|
||||
import { format as dateFormat } from 'date-fns';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, PollsRepository, UsersRepository } from '@/models/_.js';
|
||||
import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { createTemp } from '@/misc/create-temp.js';
|
||||
import type { MiPoll } from '@/models/Poll.js';
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
@@ -42,7 +43,6 @@ export class ExportClipsProcessorService {
|
||||
|
||||
private driveService: DriveService,
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
private queryService: QueryService,
|
||||
private idService: IdService,
|
||||
private notificationService: NotificationService,
|
||||
) {
|
||||
@@ -100,16 +100,16 @@ export class ExportClipsProcessorService {
|
||||
});
|
||||
|
||||
while (true) {
|
||||
const query = this.clipsRepository.createQueryBuilder('clip')
|
||||
.where('clip.userId = :userId', { userId: user.id })
|
||||
.orderBy('clip.id', 'ASC')
|
||||
.take(100);
|
||||
|
||||
if (cursor) {
|
||||
query.andWhere('clip.id > :cursor', { cursor });
|
||||
}
|
||||
|
||||
const clips = await query.getMany();
|
||||
const clips = await this.clipsRepository.find({
|
||||
where: {
|
||||
userId: user.id,
|
||||
...(cursor ? { id: MoreThan(cursor) } : {}),
|
||||
},
|
||||
take: 100,
|
||||
order: {
|
||||
id: 1,
|
||||
},
|
||||
});
|
||||
|
||||
if (clips.length === 0) {
|
||||
job.updateProgress(100);
|
||||
@@ -124,7 +124,7 @@ export class ExportClipsProcessorService {
|
||||
const isFirst = exportedClipsCount === 0;
|
||||
await writer.write(isFirst ? content : ',\n' + content);
|
||||
|
||||
await this.processClipNotes(writer, clip.id, user.id);
|
||||
await this.processClipNotes(writer, clip.id);
|
||||
|
||||
await writer.write(']}');
|
||||
exportedClipsCount++;
|
||||
@@ -134,25 +134,22 @@ export class ExportClipsProcessorService {
|
||||
}
|
||||
}
|
||||
|
||||
async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string, userId: string): Promise<void> {
|
||||
async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string): Promise<void> {
|
||||
let exportedClipNotesCount = 0;
|
||||
let cursor: MiClipNote['id'] | null = null;
|
||||
|
||||
while (true) {
|
||||
const query = this.clipNotesRepository.createQueryBuilder('clipNote')
|
||||
.leftJoinAndSelect('clipNote.note', 'note')
|
||||
.leftJoinAndSelect('note.user', 'user')
|
||||
.where('clipNote.clipId = :clipId', { clipId })
|
||||
.orderBy('clipNote.id', 'ASC')
|
||||
.take(100);
|
||||
|
||||
if (cursor) {
|
||||
query.andWhere('clipNote.id > :cursor', { cursor });
|
||||
}
|
||||
|
||||
this.queryService.generateVisibilityQuery(query, { id: userId });
|
||||
|
||||
const clipNotes = await query.getMany() as (MiClipNote & { note: MiNote & { user: MiUser } })[];
|
||||
const clipNotes = await this.clipNotesRepository.find({
|
||||
where: {
|
||||
clipId,
|
||||
...(cursor ? { id: MoreThan(cursor) } : {}),
|
||||
},
|
||||
take: 100,
|
||||
order: {
|
||||
id: 1,
|
||||
},
|
||||
relations: ['note', 'note.user'],
|
||||
}) as (MiClipNote & { note: MiNote & { user: MiUser } })[];
|
||||
|
||||
if (clipNotes.length === 0) {
|
||||
break;
|
||||
@@ -161,11 +158,6 @@ export class ExportClipsProcessorService {
|
||||
cursor = clipNotes.at(-1)?.id ?? null;
|
||||
|
||||
for (const clipNote of clipNotes) {
|
||||
const noteCreatedAt = this.idService.parse(clipNote.note.id).date;
|
||||
if (shouldHideNoteByTime(clipNote.note.user.makeNotesHiddenBefore, noteCreatedAt)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let poll: MiPoll | undefined;
|
||||
if (clipNote.note.hasPoll) {
|
||||
poll = await this.pollsRepository.findOneByOrFail({ noteId: clipNote.note.id });
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { MoreThan } from 'typeorm';
|
||||
import { format as dateFormat } from 'date-fns';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiNoteFavorite, NoteFavoritesRepository, PollsRepository, MiUser, UsersRepository } from '@/models/_.js';
|
||||
@@ -16,8 +17,6 @@ import type { MiNote } from '@/models/Note.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
@@ -38,7 +37,6 @@ export class ExportFavoritesProcessorService {
|
||||
|
||||
private driveService: DriveService,
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
private queryService: QueryService,
|
||||
private idService: IdService,
|
||||
private notificationService: NotificationService,
|
||||
) {
|
||||
@@ -85,20 +83,17 @@ export class ExportFavoritesProcessorService {
|
||||
});
|
||||
|
||||
while (true) {
|
||||
const query = this.noteFavoritesRepository.createQueryBuilder('favorite')
|
||||
.leftJoinAndSelect('favorite.note', 'note')
|
||||
.leftJoinAndSelect('note.user', 'user')
|
||||
.where('favorite.userId = :userId', { userId: user.id })
|
||||
.orderBy('favorite.id', 'ASC')
|
||||
.take(100);
|
||||
|
||||
if (cursor) {
|
||||
query.andWhere('favorite.id > :cursor', { cursor });
|
||||
}
|
||||
|
||||
this.queryService.generateVisibilityQuery(query, { id: user.id });
|
||||
|
||||
const favorites = await query.getMany() as (MiNoteFavorite & { note: MiNote & { user: MiUser } })[];
|
||||
const favorites = await this.noteFavoritesRepository.find({
|
||||
where: {
|
||||
userId: user.id,
|
||||
...(cursor ? { id: MoreThan(cursor) } : {}),
|
||||
},
|
||||
take: 100,
|
||||
order: {
|
||||
id: 1,
|
||||
},
|
||||
relations: ['note', 'note.user'],
|
||||
}) as (MiNoteFavorite & { note: MiNote & { user: MiUser } })[];
|
||||
|
||||
if (favorites.length === 0) {
|
||||
job.updateProgress(100);
|
||||
@@ -108,11 +103,6 @@ export class ExportFavoritesProcessorService {
|
||||
cursor = favorites.at(-1)?.id ?? null;
|
||||
|
||||
for (const favorite of favorites) {
|
||||
const noteCreatedAt = this.idService.parse(favorite.note.id).date;
|
||||
if (shouldHideNoteByTime(favorite.note.user.makeNotesHiddenBefore, noteCreatedAt)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let poll: MiPoll | undefined;
|
||||
if (favorite.note.hasPoll) {
|
||||
poll = await this.pollsRepository.findOneByOrFail({ noteId: favorite.note.id });
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
url: https://example.com/
|
||||
port: 3000
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"url": "https://${HOST}/",
|
||||
"port": 3000,
|
||||
"db": {
|
||||
"host": "db.${HOST}",
|
||||
"port": 5432,
|
||||
"db": "misskey",
|
||||
"user": "postgres",
|
||||
"pass": "postgres"
|
||||
},
|
||||
"dbReplications": false,
|
||||
"trustProxy": true,
|
||||
"redis": {
|
||||
"host": "redis.test",
|
||||
"port": 6379
|
||||
},
|
||||
"id": "aidx",
|
||||
"proxyBypassHosts": [
|
||||
"api.deepl.com",
|
||||
"api-free.deepl.com",
|
||||
"www.recaptcha.net",
|
||||
"hcaptcha.com",
|
||||
"challenges.cloudflare.com"
|
||||
],
|
||||
"allowedPrivateNetworks": [
|
||||
"127.0.0.1/32",
|
||||
"172.20.0.0/16"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
url: https://${HOST}/
|
||||
port: 3000
|
||||
db:
|
||||
host: db.${HOST}
|
||||
port: 5432
|
||||
db: misskey
|
||||
user: postgres
|
||||
pass: postgres
|
||||
dbReplications: false
|
||||
redis:
|
||||
host: redis.test
|
||||
port: 6379
|
||||
id: 'aidx'
|
||||
proxyBypassHosts:
|
||||
- api.deepl.com
|
||||
- api-free.deepl.com
|
||||
- www.recaptcha.net
|
||||
- hcaptcha.com
|
||||
- challenges.cloudflare.com
|
||||
allowedPrivateNetworks:
|
||||
- 127.0.0.1/32
|
||||
- 172.20.0.0/16
|
||||
@@ -37,8 +37,8 @@ services:
|
||||
- internal_network_a
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./.config/a.test.config.json
|
||||
target: /misskey/built/._config_.json
|
||||
source: ./.config/a.test.default.yml
|
||||
target: /misskey/.config/default.yml
|
||||
read_only: true
|
||||
|
||||
db.a.test:
|
||||
|
||||
@@ -37,8 +37,8 @@ services:
|
||||
- internal_network_b
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./.config/b.test.config.json
|
||||
target: /misskey/built/._config_.json
|
||||
source: ./.config/b.test.default.yml
|
||||
target: /misskey/.config/default.yml
|
||||
read_only: true
|
||||
|
||||
db.b.test:
|
||||
|
||||
@@ -21,10 +21,6 @@ services:
|
||||
- type: bind
|
||||
source: ../../../built
|
||||
target: /misskey/built
|
||||
read_only: false
|
||||
- type: bind
|
||||
source: ./.config/dummy.yml
|
||||
target: /misskey/.config/default.yml
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: ../assets
|
||||
@@ -46,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
|
||||
|
||||
@@ -28,7 +28,7 @@ function generate {
|
||||
-days 500
|
||||
if [ ! -f .config/docker.env ]; then cp .config/example.docker.env .config/docker.env; fi
|
||||
if [ ! -f .config/$1.conf ]; then sed "s/\${HOST}/$1/g" .config/example.conf > .config/$1.conf; fi
|
||||
if [ ! -f .config/$1.default.yml ]; then sed "s/\${HOST}/$1/g" .config/example.config.json > .config/$1.config.json; fi
|
||||
if [ ! -f .config/$1.default.yml ]; then sed "s/\${HOST}/$1/g" .config/example.default.yml > .config/$1.default.yml; fi
|
||||
}
|
||||
|
||||
generate a.test
|
||||
|
||||
@@ -168,36 +168,7 @@ describe('export-clips', () => {
|
||||
assert.strictEqual(exported[1].clipNotes[0].note.text, 'baz2');
|
||||
});
|
||||
|
||||
test('Clipping other user\'s note (followers only notes are excluded when not following)', async () => {
|
||||
const res = await api('clips/create', {
|
||||
name: 'kawaii',
|
||||
description: 'kawaii',
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const clip = res.body;
|
||||
|
||||
const note = await post(bob, {
|
||||
text: 'baz',
|
||||
visibility: 'followers',
|
||||
});
|
||||
|
||||
const res2 = await api('clips/add-note', {
|
||||
clipId: clip.id,
|
||||
noteId: note.id,
|
||||
}, alice);
|
||||
assert.strictEqual(res2.status, 204);
|
||||
|
||||
const res3 = await api('i/export-clips', {}, alice);
|
||||
assert.strictEqual(res3.status, 204);
|
||||
|
||||
const exported = await pollFirstDriveFile();
|
||||
assert.strictEqual(exported[0].clipNotes.length, 0);
|
||||
});
|
||||
|
||||
test('Clipping other user\'s note (followers only notes are included when following)', async () => {
|
||||
// Alice follows Bob
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
|
||||
test('Clipping other user\'s note', async () => {
|
||||
const res = await api('clips/create', {
|
||||
name: 'kawaii',
|
||||
description: 'kawaii',
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { describe, expect, test, beforeEach, afterEach } from '@jest/globals';
|
||||
import * as lolex from '@sinonjs/fake-timers';
|
||||
import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js';
|
||||
|
||||
describe('misc:should-hide-note-by-time', () => {
|
||||
let clock: lolex.InstalledClock;
|
||||
const epoch = Date.UTC(2000, 0, 1, 0, 0, 0);
|
||||
|
||||
beforeEach(() => {
|
||||
clock = lolex.install({
|
||||
// https://github.com/sinonjs/sinon/issues/2620
|
||||
toFake: Object.keys(lolex.timers).filter((key) => !['nextTick', 'queueMicrotask'].includes(key)) as lolex.FakeMethod[],
|
||||
now: new Date(epoch),
|
||||
shouldClearNativeTimers: true,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clock.uninstall();
|
||||
});
|
||||
|
||||
describe('hiddenBefore が null または undefined の場合', () => {
|
||||
test('hiddenBefore が null のときは false を返す(非表示機能が有効でない)', () => {
|
||||
const createdAt = new Date(epoch - 86400000); // 1 day ago
|
||||
expect(shouldHideNoteByTime(null, createdAt)).toBe(false);
|
||||
});
|
||||
|
||||
test('hiddenBefore が undefined のときは false を返す(非表示機能が有効でない)', () => {
|
||||
const createdAt = new Date(epoch - 86400000); // 1 day ago
|
||||
expect(shouldHideNoteByTime(undefined, createdAt)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('相対時間モード (hiddenBefore <= 0)', () => {
|
||||
test('閾値内に作成されたノートは false を返す(作成からの経過時間がまだ短い→表示)', () => {
|
||||
const hiddenBefore = -86400; // 1 day in seconds
|
||||
const createdAt = new Date(epoch - 3600000); // 1 hour ago
|
||||
expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(false);
|
||||
});
|
||||
|
||||
test('閾値を超えて作成されたノートは true を返す(指定期間以上経過している→非表示)', () => {
|
||||
const hiddenBefore = -86400; // 1 day in seconds
|
||||
const createdAt = new Date(epoch - 172800000); // 2 days ago
|
||||
expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(true);
|
||||
});
|
||||
|
||||
test('ちょうど閾値で作成されたノートは true を返す(閾値に達したら非表示)', () => {
|
||||
const hiddenBefore = -86400; // 1 day in seconds
|
||||
const createdAt = new Date(epoch - 86400000); // exactly 1 day ago
|
||||
expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(true);
|
||||
});
|
||||
|
||||
test('異なる相対時間値で判定できる(1時間設定と3時間設定の異なる結果)', () => {
|
||||
const createdAt = new Date(epoch - 7200000); // 2 hours ago
|
||||
expect(shouldHideNoteByTime(-3600, createdAt)).toBe(true); // 1時間経過→非表示
|
||||
expect(shouldHideNoteByTime(-10800, createdAt)).toBe(false); // 3時間未経過→表示
|
||||
});
|
||||
|
||||
test('ISO 8601 形式の文字列の createdAt に対応できる(文字列でも正しく判定)', () => {
|
||||
const createdAtString = new Date(epoch - 86400000).toISOString();
|
||||
const hiddenBefore = -86400; // 1 day in seconds
|
||||
expect(shouldHideNoteByTime(hiddenBefore, createdAtString)).toBe(true);
|
||||
});
|
||||
|
||||
test('hiddenBefore が 0 の場合に対応できる(0秒以上経過で非表示→ほぼ全て非表示)', () => {
|
||||
const hiddenBefore = 0;
|
||||
const createdAt = new Date(epoch - 1); // 1ms ago
|
||||
expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('絶対時間モード (hiddenBefore > 0)', () => {
|
||||
test('閾値タイムスタンプより後に作成されたノートは false を返す(指定日時より後→表示)', () => {
|
||||
const thresholdSeconds = Math.floor(epoch / 1000);
|
||||
const createdAt = new Date(epoch + 3600000); // 1 hour from epoch
|
||||
expect(shouldHideNoteByTime(thresholdSeconds, createdAt)).toBe(false);
|
||||
});
|
||||
|
||||
test('閾値タイムスタンプより前に作成されたノートは true を返す(指定日時より前→非表示)', () => {
|
||||
const thresholdSeconds = Math.floor(epoch / 1000);
|
||||
const createdAt = new Date(epoch - 3600000); // 1 hour ago
|
||||
expect(shouldHideNoteByTime(thresholdSeconds, createdAt)).toBe(true);
|
||||
});
|
||||
|
||||
test('ちょうど閾値タイムスタンプで作成されたノートは true を返す(指定日時に達したら非表示)', () => {
|
||||
const thresholdSeconds = Math.floor(epoch / 1000);
|
||||
const createdAt = new Date(epoch); // exactly epoch
|
||||
expect(shouldHideNoteByTime(thresholdSeconds, createdAt)).toBe(true);
|
||||
});
|
||||
|
||||
test('ISO 8601 形式の文字列の createdAt に対応できる(文字列でも正しく判定)', () => {
|
||||
const thresholdSeconds = Math.floor(epoch / 1000);
|
||||
const createdAtString = new Date(epoch - 3600000).toISOString();
|
||||
expect(shouldHideNoteByTime(thresholdSeconds, createdAtString)).toBe(true);
|
||||
});
|
||||
|
||||
test('異なる閾値タイムスタンプで判定できる(2021年設定と現在より1時間前設定の異なる結果)', () => {
|
||||
const thresholdSeconds = Math.floor((epoch - 86400000) / 1000); // 1 day ago
|
||||
const createdAtBefore = new Date(epoch - 172800000); // 2 days ago
|
||||
const createdAtAfter = new Date(epoch - 3600000); // 1 hour ago
|
||||
expect(shouldHideNoteByTime(thresholdSeconds, createdAtBefore)).toBe(true); // 閾値より前→非表示
|
||||
expect(shouldHideNoteByTime(thresholdSeconds, createdAtAfter)).toBe(false); // 閾値より後→表示
|
||||
});
|
||||
});
|
||||
|
||||
describe('エッジケース', () => {
|
||||
test('相対時間モードで非常に古いノートに対応できる(非常に古い→閾値超→非表示)', () => {
|
||||
const hiddenBefore = -1; // hide notes older than 1 second
|
||||
const createdAt = new Date(epoch - 1000000); // very old
|
||||
expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(true);
|
||||
});
|
||||
|
||||
test('相対時間モードで非常に新しいノートに対応できる(非常に新しい→閾値未満→表示)', () => {
|
||||
const hiddenBefore = -86400; // 1 day
|
||||
const createdAt = new Date(epoch - 1); // 1ms ago
|
||||
expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(false);
|
||||
});
|
||||
|
||||
test('大きなタイムスタンプ値に対応できる(未来の日時を指定→現在のノートは全て非表示)', () => {
|
||||
const thresholdSeconds = Math.floor(epoch / 1000) + 86400; // 1 day from epoch
|
||||
const createdAt = new Date(epoch); // created epoch
|
||||
expect(shouldHideNoteByTime(thresholdSeconds, createdAt)).toBe(true);
|
||||
});
|
||||
|
||||
test('小さな相対時間値に対応できる(1秒設定で2秒前→非表示)', () => {
|
||||
const hiddenBefore = -1; // 1 second
|
||||
const createdAt = new Date(epoch - 2000); // 2 seconds ago
|
||||
expect(shouldHideNoteByTime(hiddenBefore, createdAt)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -12,8 +12,8 @@
|
||||
"devDependencies": {
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/node": "24.10.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.48.0",
|
||||
"@typescript-eslint/parser": "8.48.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"rollup": "4.53.3",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
"@rollup/pluginutils": "5.3.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@vitejs/plugin-vue": "6.0.2",
|
||||
"@vue/compiler-sfc": "3.5.24",
|
||||
"astring": "1.9.0",
|
||||
"buraha": "0.0.1",
|
||||
"estree-walker": "3.0.3",
|
||||
"frontend-shared": "workspace:*",
|
||||
@@ -27,11 +29,14 @@
|
||||
"punycode.js": "2.3.1",
|
||||
"rollup": "4.53.3",
|
||||
"sass": "1.94.2",
|
||||
"shiki": "3.17.0",
|
||||
"shiki": "3.15.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tsc-alias": "1.8.16",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.9.3",
|
||||
"uuid": "13.0.0",
|
||||
"vite": "7.2.4",
|
||||
"vue": "3.5.25"
|
||||
"vue": "3.5.24"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
@@ -43,23 +48,23 @@
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.48.0",
|
||||
"@typescript-eslint/parser": "8.48.0",
|
||||
"@vitest/coverage-v8": "4.0.14",
|
||||
"@vue/runtime-core": "3.5.25",
|
||||
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"@vitest/coverage-v8": "4.0.13",
|
||||
"@vue/runtime-core": "3.5.24",
|
||||
"acorn": "8.15.0",
|
||||
"cross-env": "10.1.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.6.2",
|
||||
"happy-dom": "20.0.11",
|
||||
"eslint-plugin-vue": "10.6.0",
|
||||
"fast-glob": "3.3.3",
|
||||
"happy-dom": "20.0.10",
|
||||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.8",
|
||||
"msw": "2.12.3",
|
||||
"msw": "2.12.2",
|
||||
"nodemon": "3.1.11",
|
||||
"prettier": "3.7.1",
|
||||
"prettier": "3.6.2",
|
||||
"start-server-and-test": "2.1.3",
|
||||
"tsx": "4.20.6",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vue-component-type-helpers": "3.1.5",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
|
||||
@@ -70,8 +70,6 @@
|
||||
importAppScript();
|
||||
});
|
||||
}
|
||||
|
||||
localStorage.setItem('lang', lang);
|
||||
//#endregion
|
||||
|
||||
async function addStyle(styleText) {
|
||||
|
||||
@@ -3,13 +3,14 @@ import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import * as esbuild from 'esbuild';
|
||||
import { build } from 'esbuild';
|
||||
import { globSync } from 'glob';
|
||||
import { execa } from 'execa';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
|
||||
|
||||
const entryPoints = fs.globSync('./js/**/**.{ts,tsx}');
|
||||
const entryPoints = globSync('./js/**/**.{ts,tsx}');
|
||||
|
||||
/** @type {import('esbuild').BuildOptions} */
|
||||
const options = {
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.10.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.48.0",
|
||||
"@typescript-eslint/parser": "8.48.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"esbuild": "0.27.0",
|
||||
"eslint-plugin-vue": "10.6.2",
|
||||
"eslint-plugin-vue": "10.6.0",
|
||||
"nodemon": "3.1.11",
|
||||
"typescript": "5.9.3",
|
||||
"vue-eslint-parser": "10.2.0"
|
||||
@@ -36,6 +36,6 @@
|
||||
"dependencies": {
|
||||
"i18n": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
"vue": "3.5.25"
|
||||
"vue": "3.5.24"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync, globSync } from 'node:fs';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { basename, dirname } from 'node:path/posix';
|
||||
import { GENERATOR, type State, generate } from 'astring';
|
||||
import type * as estree from 'estree';
|
||||
import glob from 'fast-glob';
|
||||
import { format } from 'prettier';
|
||||
|
||||
interface SatisfiesExpression extends estree.BaseExpression {
|
||||
@@ -438,37 +439,38 @@ function toStories(component: string): Promise<string> {
|
||||
|
||||
// glob('src/{components,pages,ui,widgets}/**/*.vue')
|
||||
(async () => {
|
||||
const components = [
|
||||
globSync('src/components/global/Mk*.vue'),
|
||||
globSync('src/components/global/RouterView.vue'),
|
||||
globSync('src/components/MkAbuseReportWindow.vue'),
|
||||
globSync('src/components/MkAccountMoved.vue'),
|
||||
globSync('src/components/MkAchievements.vue'),
|
||||
globSync('src/components/MkAnalogClock.vue'),
|
||||
globSync('src/components/MkAnimBg.vue'),
|
||||
globSync('src/components/MkAnnouncementDialog.vue'),
|
||||
globSync('src/components/MkAntennaEditor.vue'),
|
||||
globSync('src/components/MkAntennaEditorDialog.vue'),
|
||||
globSync('src/components/MkAsUi.vue'),
|
||||
globSync('src/components/MkAutocomplete.vue'),
|
||||
globSync('src/components/MkAvatars.vue'),
|
||||
globSync('src/components/Mk[B-E]*.vue'),
|
||||
globSync('src/components/MkFlashPreview.vue'),
|
||||
globSync('src/components/MkGalleryPostPreview.vue'),
|
||||
globSync('src/components/MkSignupServerRules.vue'),
|
||||
globSync('src/components/MkUserSetupDialog.vue'),
|
||||
globSync('src/components/MkUserSetupDialog.*.vue'),
|
||||
globSync('src/components/MkImgPreviewDialog.vue'),
|
||||
globSync('src/components/MkInstanceCardMini.vue'),
|
||||
globSync('src/components/MkInviteCode.vue'),
|
||||
globSync('src/components/MkTagItem.vue'),
|
||||
globSync('src/components/MkRoleSelectDialog.vue'),
|
||||
globSync('src/components/grid/MkGrid.vue'),
|
||||
globSync('src/pages/admin/custom-emojis-manager2.vue'),
|
||||
globSync('src/pages/admin/overview.ap-requests.vue'),
|
||||
globSync('src/pages/user/home.vue'),
|
||||
globSync('src/pages/search.vue'),
|
||||
].flat();
|
||||
const globs = await Promise.all([
|
||||
glob('src/components/global/Mk*.vue'),
|
||||
glob('src/components/global/RouterView.vue'),
|
||||
glob('src/components/MkAbuseReportWindow.vue'),
|
||||
glob('src/components/MkAccountMoved.vue'),
|
||||
glob('src/components/MkAchievements.vue'),
|
||||
glob('src/components/MkAnalogClock.vue'),
|
||||
glob('src/components/MkAnimBg.vue'),
|
||||
glob('src/components/MkAnnouncementDialog.vue'),
|
||||
glob('src/components/MkAntennaEditor.vue'),
|
||||
glob('src/components/MkAntennaEditorDialog.vue'),
|
||||
glob('src/components/MkAsUi.vue'),
|
||||
glob('src/components/MkAutocomplete.vue'),
|
||||
glob('src/components/MkAvatars.vue'),
|
||||
glob('src/components/Mk[B-E]*.vue'),
|
||||
glob('src/components/MkFlashPreview.vue'),
|
||||
glob('src/components/MkGalleryPostPreview.vue'),
|
||||
glob('src/components/MkSignupServerRules.vue'),
|
||||
glob('src/components/MkUserSetupDialog.vue'),
|
||||
glob('src/components/MkUserSetupDialog.*.vue'),
|
||||
glob('src/components/MkImgPreviewDialog.vue'),
|
||||
glob('src/components/MkInstanceCardMini.vue'),
|
||||
glob('src/components/MkInviteCode.vue'),
|
||||
glob('src/components/MkTagItem.vue'),
|
||||
glob('src/components/MkRoleSelectDialog.vue'),
|
||||
glob('src/components/grid/MkGrid.vue'),
|
||||
glob('src/pages/admin/custom-emojis-manager2.vue'),
|
||||
glob('src/pages/admin/overview.ap-requests.vue'),
|
||||
glob('src/pages/user/home.vue'),
|
||||
glob('src/pages/search.vue'),
|
||||
]);
|
||||
const components = globs.flat();
|
||||
await Promise.all(components.map(async (component) => {
|
||||
const stories = component.replace(/\.vue$/, '.stories.ts');
|
||||
await writeFile(stories, await toStories(component));
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type PluginOption
|
||||
} from 'vite';
|
||||
import fs from 'node:fs';
|
||||
import { glob } from 'glob';
|
||||
import JSON5 from 'json5';
|
||||
import MagicString, { SourceMap } from 'magic-string';
|
||||
import path from 'node:path'
|
||||
@@ -723,7 +724,7 @@ export function pluginCreateSearchIndexVirtualModule(options: Options, asigner:
|
||||
|
||||
async load(id) {
|
||||
if (id == '\0' + allSearchIndexFile) {
|
||||
const files = options.targetFilePaths.map((filePathPattern) => fs.globSync(filePathPattern)).flat();
|
||||
const files = await Promise.all(options.targetFilePaths.map(async (filePathPattern) => await glob(filePathPattern))).then(paths => paths.flat());
|
||||
let generatedFile = '';
|
||||
let arrayElements = '';
|
||||
for (let file of files) {
|
||||
|
||||
@@ -25,13 +25,15 @@
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-replace": "6.0.3",
|
||||
"@rollup/pluginutils": "5.3.0",
|
||||
"@sentry/vue": "10.27.0",
|
||||
"@sentry/vue": "10.26.0",
|
||||
"@syuilo/aiscript": "1.2.0",
|
||||
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@vitejs/plugin-vue": "6.0.2",
|
||||
"@vue/compiler-sfc": "3.5.24",
|
||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
|
||||
"analytics": "0.8.19",
|
||||
"astring": "1.9.0",
|
||||
"broadcast-channel": "7.2.0",
|
||||
"buraha": "0.0.1",
|
||||
"canvas-confetti": "1.9.4",
|
||||
@@ -44,6 +46,7 @@
|
||||
"compare-versions": "6.1.1",
|
||||
"cropperjs": "2.1.0",
|
||||
"date-fns": "4.1.0",
|
||||
"estree-walker": "3.0.3",
|
||||
"eventemitter3": "5.0.1",
|
||||
"execa": "9.6.0",
|
||||
"exifreader": "4.32.0",
|
||||
@@ -54,8 +57,9 @@
|
||||
"ios-haptics": "0.1.4",
|
||||
"is-file-animated": "1.0.2",
|
||||
"json5": "2.2.3",
|
||||
"magic-string": "0.30.21",
|
||||
"matter-js": "0.20.0",
|
||||
"mediabunny": "1.25.3",
|
||||
"mediabunny": "1.25.1",
|
||||
"mfm-js": "0.25.0",
|
||||
"misskey-bubble-game": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
@@ -67,14 +71,18 @@
|
||||
"rollup": "4.53.3",
|
||||
"sanitize-html": "2.17.0",
|
||||
"sass": "1.94.2",
|
||||
"shiki": "3.17.0",
|
||||
"shiki": "3.15.0",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.181.2",
|
||||
"throttle-debounce": "5.0.2",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tsc-alias": "1.8.16",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.9.3",
|
||||
"v-code-diff": "1.13.1",
|
||||
"vite": "7.2.4",
|
||||
"vue": "3.5.25",
|
||||
"vue": "3.5.24",
|
||||
"vuedraggable": "next",
|
||||
"wanakana": "5.3.1"
|
||||
},
|
||||
@@ -82,7 +90,7 @@
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
"@storybook/addon-essentials": "8.6.14",
|
||||
"@storybook/addon-interactions": "8.6.14",
|
||||
"@storybook/addon-links": "10.1.0",
|
||||
"@storybook/addon-links": "10.0.8",
|
||||
"@storybook/addon-mdx-gfm": "8.6.14",
|
||||
"@storybook/addon-storysource": "8.6.14",
|
||||
"@storybook/blocks": "8.6.14",
|
||||
@@ -90,13 +98,13 @@
|
||||
"@storybook/core-events": "8.6.14",
|
||||
"@storybook/manager-api": "8.6.14",
|
||||
"@storybook/preview-api": "8.6.14",
|
||||
"@storybook/react": "10.1.0",
|
||||
"@storybook/react-vite": "10.1.0",
|
||||
"@storybook/react": "10.0.8",
|
||||
"@storybook/react-vite": "10.0.8",
|
||||
"@storybook/test": "8.6.14",
|
||||
"@storybook/theming": "8.6.14",
|
||||
"@storybook/types": "8.6.14",
|
||||
"@storybook/vue3": "10.1.0",
|
||||
"@storybook/vue3-vite": "10.1.0",
|
||||
"@storybook/vue3": "10.0.8",
|
||||
"@storybook/vue3-vite": "10.0.8",
|
||||
"@tabler/icons-webfont": "3.35.0",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/canvas-confetti": "1.9.0",
|
||||
@@ -109,37 +117,36 @@
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.48.0",
|
||||
"@typescript-eslint/parser": "8.48.0",
|
||||
"@vitest/coverage-v8": "4.0.14",
|
||||
"@vue/compiler-core": "3.5.25",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"@vitest/coverage-v8": "4.0.13",
|
||||
"@vue/compiler-core": "3.5.24",
|
||||
"@vue/runtime-core": "3.5.24",
|
||||
"acorn": "8.15.0",
|
||||
"astring": "1.9.0",
|
||||
"cross-env": "10.1.0",
|
||||
"cypress": "15.7.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.6.2",
|
||||
"estree-walker": "3.0.3",
|
||||
"happy-dom": "20.0.11",
|
||||
"eslint-plugin-vue": "10.6.0",
|
||||
"fast-glob": "3.3.3",
|
||||
"happy-dom": "20.0.10",
|
||||
"intersection-observer": "0.12.2",
|
||||
"magic-string": "0.30.21",
|
||||
"micromatch": "4.0.8",
|
||||
"minimatch": "10.1.1",
|
||||
"msw": "2.12.3",
|
||||
"msw": "2.12.2",
|
||||
"msw-storybook-addon": "2.0.6",
|
||||
"nodemon": "3.1.11",
|
||||
"prettier": "3.7.1",
|
||||
"prettier": "3.6.2",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"seedrandom": "3.0.5",
|
||||
"start-server-and-test": "2.1.3",
|
||||
"storybook": "10.1.0",
|
||||
"storybook": "10.0.8",
|
||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||
"tsx": "4.20.6",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-glsl": "1.5.4",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vitest": "4.0.14",
|
||||
"vitest": "4.0.13",
|
||||
"vitest-fetch-mock": "0.4.5",
|
||||
"vue-component-type-helpers": "3.1.5",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
|
||||
@@ -42,8 +42,6 @@
|
||||
console.error('invalid lang value detected!!!', typeof lang, lang);
|
||||
lang = 'en-US';
|
||||
}
|
||||
|
||||
localStorage.setItem('lang', lang);
|
||||
//#endregion
|
||||
|
||||
//#region Script
|
||||
|
||||
@@ -233,18 +233,16 @@ function showMenu(ev: MouseEvent) {
|
||||
.hide {
|
||||
display: block;
|
||||
position: absolute;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
||||
backdrop-filter: var(--MI-blur, blur(15px));
|
||||
border-radius: 0 0 0 9px;
|
||||
color: #fff;
|
||||
border-radius: 6px;
|
||||
background-color: var(--MI_THEME-fg);
|
||||
color: hsl(from var(--MI_THEME-accent) h s calc(l + 10));
|
||||
font-size: 12px;
|
||||
opacity: .5;
|
||||
padding: 5px 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
right: 0;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.hiddenTextWrapper {
|
||||
@@ -274,17 +272,17 @@ html[data-color-scheme=light] .visible {
|
||||
.menu {
|
||||
display: block;
|
||||
position: absolute;
|
||||
border-radius: 999px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
||||
backdrop-filter: var(--MI-blur, blur(15px));
|
||||
border-radius: 9px 0 0 0;
|
||||
color: #fff;
|
||||
font-size: 0.8em;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
text-align: center;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
|
||||
@@ -38,10 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ti ti-device-tv"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isRenote && note.renote == null" :class="$style.deleted">
|
||||
{{ i18n.ts.deletedNote }}
|
||||
</div>
|
||||
<div v-else-if="renoteCollapsed" :class="$style.collapsedRenoteTarget">
|
||||
<div v-if="renoteCollapsed" :class="$style.collapsedRenoteTarget">
|
||||
<MkAvatar :class="$style.collapsedRenoteTargetAvatar" :user="appearNote.user" link preview/>
|
||||
<Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :nyaize="'respect'" :class="$style.collapsedRenoteTargetText" @click="renoteCollapsed = false"/>
|
||||
</div>
|
||||
@@ -1147,14 +1144,4 @@ function emitUpdReaction(emoji: string, delta: number) {
|
||||
opacity: .8;
|
||||
font-size: 95%;
|
||||
}
|
||||
|
||||
.deleted {
|
||||
text-align: center;
|
||||
padding: 32px;
|
||||
margin: 6px 32px 28px;
|
||||
--color: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.15));
|
||||
background-size: auto auto;
|
||||
background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px);
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -43,10 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isRenote && note.renote == null" :class="$style.deleted">
|
||||
{{ i18n.ts.deletedNote }}
|
||||
</div>
|
||||
<article v-else :class="$style.note" @contextmenu.stop="onContextmenu">
|
||||
<article :class="$style.note" @contextmenu.stop="onContextmenu">
|
||||
<header :class="$style.noteHeader">
|
||||
<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
|
||||
<div :class="$style.noteHeaderBody">
|
||||
@@ -947,14 +944,4 @@ function loadConversation() {
|
||||
text-align: center;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.deleted {
|
||||
text-align: center;
|
||||
padding: 32px;
|
||||
margin: 6px 32px 32px;
|
||||
--color: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.15));
|
||||
background-size: auto auto;
|
||||
background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px);
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -74,7 +74,6 @@ function mount() {
|
||||
}
|
||||
|
||||
function back() {
|
||||
if (tabs.value.length <= 1) return; // transitionの関係でタブが1つの状態でbackが呼ばれることがある
|
||||
const prev = tabs.value[tabs.value.length - 2];
|
||||
tabs.value = [...tabs.value.slice(0, tabs.value.length - 1)];
|
||||
router?.replaceByPath(prev.fullPath);
|
||||
|
||||
@@ -77,7 +77,7 @@ export const components = {
|
||||
SearchIcon: SearchIcon,
|
||||
};
|
||||
|
||||
declare module 'vue' {
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
I18n: typeof I18n;
|
||||
RouterView: typeof RouterView;
|
||||
|
||||
@@ -257,7 +257,7 @@ async function search() {
|
||||
}
|
||||
|
||||
const headerActions = computed(() => {
|
||||
if (channel.value) {
|
||||
if (channel.value && channel.value.userId) {
|
||||
const headerItems: PageHeaderItem[] = [];
|
||||
|
||||
headerItems.push({
|
||||
|
||||
@@ -63,7 +63,7 @@ async function setAntenna() {
|
||||
})),
|
||||
} : undefined),
|
||||
],
|
||||
default: antennas.find(x => x.id === props.column.antennaId)?.id,
|
||||
default: props.column.antennaId,
|
||||
});
|
||||
|
||||
if (canceled || antennaIdOrOperation == null) return;
|
||||
|
||||
@@ -63,7 +63,7 @@ async function setChannel() {
|
||||
items: channels.map(x => ({
|
||||
value: x.id, label: x.name,
|
||||
})),
|
||||
default: channels.find(x => x.id === props.column.channelId)?.id,
|
||||
default: props.column.channelId,
|
||||
});
|
||||
if (canceled || chosenChannelId == null) return;
|
||||
const chosenChannel = channels.find(x => x.id === chosenChannelId)!;
|
||||
|
||||
@@ -70,7 +70,7 @@ async function setList() {
|
||||
})),
|
||||
} : undefined),
|
||||
],
|
||||
default: lists.find(x => x.id === props.column.listId)?.id,
|
||||
default: props.column.listId,
|
||||
});
|
||||
if (canceled || listIdOrOperation == null) return;
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ async function setRole() {
|
||||
items: roles.map(x => ({
|
||||
value: x.id, label: x.name,
|
||||
})),
|
||||
default: roles.find(x => x.id === props.column.roleId)?.id,
|
||||
default: props.column.roleId,
|
||||
});
|
||||
if (canceled || roleId == null) return;
|
||||
const role = roles.find(x => x.id === roleId)!;
|
||||
|
||||
@@ -104,7 +104,6 @@ async function setType() {
|
||||
}, {
|
||||
value: 'global', label: i18n.ts._timelines.global,
|
||||
}],
|
||||
default: props.column.tl,
|
||||
});
|
||||
if (canceled) {
|
||||
if (props.column.tl == null) {
|
||||
|
||||
+22
-6
@@ -10,6 +10,8 @@ import { watch as chokidarWatch } from 'chokidar';
|
||||
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';
|
||||
|
||||
@@ -21,7 +23,7 @@ const _rootPackage = JSON.parse(fs.readFileSync(resolve(_rootPackageDir, 'packag
|
||||
const _frontendLocalesDir = resolve(_dirname, '../../built/_frontend_dist_/locales');
|
||||
const _localesDir = resolve(_rootPackageDir, 'locales');
|
||||
|
||||
const entryPoints = fs.globSync('./src/**/**.{ts,tsx}');
|
||||
const entryPoints = globSync('./src/**/**.{ts,tsx}');
|
||||
|
||||
const options: BuildOptions = {
|
||||
entryPoints,
|
||||
@@ -48,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');
|
||||
|
||||
@@ -56,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).`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +102,7 @@ async function buildSrc(): Promise<void> {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
copyLocales();
|
||||
compileLocales();
|
||||
await writeFrontendLocalesJson();
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
@@ -122,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);
|
||||
});
|
||||
|
||||
@@ -35,11 +35,10 @@
|
||||
"chokidar": "4.0.3",
|
||||
"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[] = [
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -13,12 +13,13 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "24.10.1",
|
||||
"@types/wawoff2": "1.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.48.0",
|
||||
"@typescript-eslint/parser": "8.48.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tabler/icons-webfont": "3.35.0",
|
||||
"harfbuzzjs": "0.4.13",
|
||||
"tiny-glob": "0.2.9",
|
||||
"tsx": "4.20.6",
|
||||
"typescript": "5.9.3",
|
||||
"wawoff2": "2.0.1"
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { promises as fsp, existsSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { promises as fsp, existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
import glob from 'tiny-glob';
|
||||
import { generateSubsettedFont } from './subsetter.js';
|
||||
|
||||
const filesToScan = {
|
||||
@@ -46,8 +47,8 @@ async function main() {
|
||||
const iconsToPack = new Set<string>();
|
||||
|
||||
const cwd = path.resolve(process.cwd(), '../../');
|
||||
const files = fsp.glob(dir, { cwd });
|
||||
for await (const file of files) {
|
||||
const files = await glob(dir, { cwd });
|
||||
for (const file of files) {
|
||||
//console.log(`Scanning ${file}`);
|
||||
const content = await fsp.readFile(path.resolve(cwd, file), 'utf-8');
|
||||
const classRegex = /ti-[a-z0-9-]+/g;
|
||||
|
||||
@@ -3,13 +3,14 @@ import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import * as esbuild from 'esbuild';
|
||||
import { build } from 'esbuild';
|
||||
import { globSync } from 'glob';
|
||||
import { execa } from 'execa';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
|
||||
|
||||
const entryPoints = fs.globSync('./src/**/**.{ts,tsx}');
|
||||
const entryPoints = globSync('./src/**/**.{ts,tsx}');
|
||||
|
||||
/** @type {import('esbuild').BuildOptions} */
|
||||
const options = {
|
||||
|
||||
@@ -27,10 +27,11 @@
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.48.0",
|
||||
"@typescript-eslint/parser": "8.48.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"esbuild": "0.27.0",
|
||||
"execa": "9.6.0",
|
||||
"glob": "11.1.0",
|
||||
"nodemon": "3.1.11",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
|
||||
@@ -3,13 +3,14 @@ import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import * as esbuild from 'esbuild';
|
||||
import { build } from 'esbuild';
|
||||
import { globSync } from 'glob';
|
||||
import { execa } from 'execa';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
|
||||
|
||||
const entryPoints = fs.globSync('./src/**/**.{ts,tsx}');
|
||||
const entryPoints = globSync('./src/**/**.{ts,tsx}');
|
||||
|
||||
/** @type {import('esbuild').BuildOptions} */
|
||||
const options = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "misskey-js",
|
||||
"version": "2025.12.1-alpha.0",
|
||||
"version": "2025.12.0-alpha.0",
|
||||
"description": "Misskey SDK for JavaScript",
|
||||
"license": "MIT",
|
||||
"main": "./built/index.js",
|
||||
@@ -44,6 +44,7 @@
|
||||
"@vitest/coverage-v8": "4.0.13",
|
||||
"esbuild": "0.27.0",
|
||||
"execa": "9.6.0",
|
||||
"glob": "13.0.0",
|
||||
"ncp": "2.0.0",
|
||||
"nodemon": "3.1.11",
|
||||
"tsd": "0.33.0",
|
||||
|
||||
@@ -3,13 +3,14 @@ import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import * as esbuild from 'esbuild';
|
||||
import { build } from 'esbuild';
|
||||
import { globSync } from 'glob';
|
||||
import { execa } from 'execa';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
|
||||
|
||||
const entryPoints = fs.globSync('./src/**/**.{ts,tsx}');
|
||||
const entryPoints = globSync('./src/**/**.{ts,tsx}');
|
||||
|
||||
/** @type {import('esbuild').BuildOptions} */
|
||||
const options = {
|
||||
|
||||
@@ -25,10 +25,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.10.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.48.0",
|
||||
"@typescript-eslint/parser": "8.48.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"esbuild": "0.27.0",
|
||||
"execa": "9.6.0",
|
||||
"glob": "11.1.0",
|
||||
"nodemon": "3.1.11",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"misskey-js": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "8.48.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"nodemon": "3.1.11",
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const FETCH_TIMEOUT_MS = 10000;
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
import { get, set } from 'idb-keyval';
|
||||
import { I18n } from '@@/js/i18n.js';
|
||||
import { FETCH_TIMEOUT_MS } from '@/const.js';
|
||||
import type { Locale } from 'i18n';
|
||||
|
||||
class SwLang {
|
||||
@@ -38,21 +37,11 @@ class SwLang {
|
||||
|
||||
// _DEV_がtrueの場合は常に最新化
|
||||
if (!localeRes || _DEV_) {
|
||||
const controller = new AbortController();
|
||||
const timeout = globalThis.setTimeout(() => {
|
||||
controller.abort('locale-fetch-timeout');
|
||||
}, FETCH_TIMEOUT_MS);
|
||||
localeRes = await fetch(localeUrl);
|
||||
const clone = localeRes.clone();
|
||||
if (!clone.clone().ok) throw new Error('locale fetching error');
|
||||
|
||||
try {
|
||||
localeRes = await fetch(localeUrl, { signal: controller.signal });
|
||||
|
||||
const clone = localeRes.clone();
|
||||
if (!clone.clone().ok) throw new Error('locale fetching error');
|
||||
|
||||
caches.open(this.cacheName).then(cache => cache.put(localeUrl, clone));
|
||||
} finally {
|
||||
globalThis.clearTimeout(timeout);
|
||||
}
|
||||
caches.open(this.cacheName).then(cache => cache.put(localeUrl, clone));
|
||||
}
|
||||
|
||||
return new I18n<Locale>(await localeRes.json());
|
||||
|
||||
+25
-63
@@ -5,7 +5,6 @@
|
||||
|
||||
import { get } from 'idb-keyval';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { FETCH_TIMEOUT_MS } from '@/const.js';
|
||||
import type { PushNotificationDataMap } from '@/types.js';
|
||||
import type { I18n } from '@@/js/i18n.js';
|
||||
import type { Locale } from 'i18n';
|
||||
@@ -13,67 +12,8 @@ import { createEmptyNotification, createNotification } from '@/scripts/create-no
|
||||
import { swLang } from '@/scripts/lang.js';
|
||||
import * as swos from '@/scripts/operations.js';
|
||||
|
||||
async function respondToNavigation(request: Request): Promise<Response> {
|
||||
const controller = new AbortController();
|
||||
const timeout = globalThis.setTimeout(() => {
|
||||
controller.abort('navigation-timeout');
|
||||
}, FETCH_TIMEOUT_MS);
|
||||
|
||||
try {
|
||||
const response = await fetch(request, { signal: controller.signal });
|
||||
|
||||
if (response?.status && response.status < 500) return response;
|
||||
if (response?.type === 'opaqueredirect') return response;
|
||||
} catch (error) {
|
||||
if (_DEV_) {
|
||||
console.warn('navigation fetch failed; showing offline page', error);
|
||||
}
|
||||
} finally {
|
||||
globalThis.clearTimeout(timeout);
|
||||
}
|
||||
|
||||
// Only show offline page when network request actually fails
|
||||
const html = await offlineContentHTML();
|
||||
return new Response(html, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'content-type': 'text/html',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function offlineContentHTML() {
|
||||
let i18n: Partial<I18n<Locale>>;
|
||||
try {
|
||||
i18n = await (swLang.i18n ?? await swLang.fetchLocale()) as Partial<I18n<Locale>>;
|
||||
} catch {
|
||||
i18n = {};
|
||||
}
|
||||
|
||||
const messages = {
|
||||
title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server',
|
||||
header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server',
|
||||
reload: i18n.ts?.reload ?? 'Reload',
|
||||
};
|
||||
|
||||
return `<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8"><meta content="width=device-width,initial-scale=1"name="viewport"><title>${messages.title}</title><style>body{background-color:#0c1210;color:#dee7e4;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:24px;box-sizing:border-box}.icon{max-width:120px;width:100%;height:auto;margin-bottom:20px;}.message{text-align:center;font-size:20px;font-weight:700;margin-bottom:20px}.version{text-align:center;font-size:90%;margin-bottom:20px}button{padding:7px 14px;min-width:100px;font-weight:700;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;border-radius:99rem;background-color:#b4e900;color:#192320;border:none;cursor:pointer;-webkit-tap-highlight-color:transparent}button:hover{background-color:#c6ff03}</style></head><body><svg class="icon"fill="none"height="24"stroke="currentColor"stroke-linecap="round"stroke-linejoin="round"stroke-width="2"viewBox="0 0 24 24"width="24"xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z"fill="none"stroke="none"/><path d="M9.58 5.548c.24 -.11 .492 -.207 .752 -.286c1.88 -.572 3.956 -.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486c0 .957 -.383 1.824 -1.003 2.454m-2.997 1.033h-11.343c-2.572 -.004 -4.657 -2.011 -4.657 -4.487c0 -2.475 2.085 -4.482 4.657 -4.482c.13 -.582 .37 -1.128 .7 -1.62"/><path d="M3 3l18 18"/></svg><div class="message">${messages.header}</div><div class="version">v${_VERSION_}</div><button onclick="reloadPage()">${messages.reload}</button><script>function reloadPage(){location.reload(!0)}</script></body></html>`;
|
||||
}
|
||||
|
||||
globalThis.addEventListener('install', (ev) => {
|
||||
// 次の問題が発生するため、ServiceWorkerAutoPreload をオプトアウトする必要がある
|
||||
// https://issues.chromium.org/issues/466790291
|
||||
if ('addRoutes' in ev) {
|
||||
// doc: https://developer.mozilla.org/en-US/docs/Web/API/InstallEvent/addRoutes
|
||||
// @ts-expect-error 実験的なAPIなので型定義がない
|
||||
ev.addRoutes({
|
||||
condition: {
|
||||
// doc: https://developer.mozilla.org/ja/docs/Web/API/URLPattern
|
||||
// @ts-expect-error 実験的なAPIなので型定義がない
|
||||
urlPattern: new URLPattern({}),
|
||||
},
|
||||
source: 'fetch-event',
|
||||
});
|
||||
}
|
||||
globalThis.addEventListener('install', () => {
|
||||
// ev.waitUntil(globalThis.skipWaiting());
|
||||
});
|
||||
|
||||
globalThis.addEventListener('activate', ev => {
|
||||
@@ -88,6 +28,17 @@ globalThis.addEventListener('activate', ev => {
|
||||
);
|
||||
});
|
||||
|
||||
async function offlineContentHTML() {
|
||||
const i18n = await (swLang.i18n ?? swLang.fetchLocale()) as Partial<I18n<Locale>>;
|
||||
const messages = {
|
||||
title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server',
|
||||
header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server',
|
||||
reload: i18n.ts?.reload ?? 'Reload',
|
||||
};
|
||||
|
||||
return `<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8"><meta content="width=device-width,initial-scale=1"name="viewport"><title>${messages.title}</title><style>body{background-color:#0c1210;color:#dee7e4;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:24px;box-sizing:border-box}.icon{max-width:120px;width:100%;height:auto;margin-bottom:20px;}.message{text-align:center;font-size:20px;font-weight:700;margin-bottom:20px}.version{text-align:center;font-size:90%;margin-bottom:20px}button{padding:7px 14px;min-width:100px;font-weight:700;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;border-radius:99rem;background-color:#b4e900;color:#192320;border:none;cursor:pointer;-webkit-tap-highlight-color:transparent}button:hover{background-color:#c6ff03}</style></head><body><svg class="icon"fill="none"height="24"stroke="currentColor"stroke-linecap="round"stroke-linejoin="round"stroke-width="2"viewBox="0 0 24 24"width="24"xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z"fill="none"stroke="none"/><path d="M9.58 5.548c.24 -.11 .492 -.207 .752 -.286c1.88 -.572 3.956 -.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486c0 .957 -.383 1.824 -1.003 2.454m-2.997 1.033h-11.343c-2.572 -.004 -4.657 -2.011 -4.657 -4.487c0 -2.475 2.085 -4.482 4.657 -4.482c.13 -.582 .37 -1.128 .7 -1.62"/><path d="M3 3l18 18"/></svg><div class="message">${messages.header}</div><div class="version">v${_VERSION_}</div><button onclick="reloadPage()">${messages.reload}</button><script>function reloadPage(){location.reload(!0)}</script></body></html>`;
|
||||
}
|
||||
|
||||
globalThis.addEventListener('fetch', ev => {
|
||||
let isHTMLRequest = false;
|
||||
if (ev.request.headers.get('sec-fetch-dest') === 'document') {
|
||||
@@ -99,7 +50,18 @@ globalThis.addEventListener('fetch', ev => {
|
||||
}
|
||||
|
||||
if (!isHTMLRequest) return;
|
||||
ev.respondWith(respondToNavigation(ev.request));
|
||||
ev.respondWith(
|
||||
fetch(ev.request)
|
||||
.catch(async () => {
|
||||
const html = await offlineContentHTML();
|
||||
return new Response(html, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'content-type': 'text/html',
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
globalThis.addEventListener('push', ev => {
|
||||
|
||||
Generated
+938
-1478
File diff suppressed because it is too large
Load Diff
+55
-51
@@ -10,7 +10,7 @@
|
||||
"devDependencies": {
|
||||
"@types/mdast": "4.0.4",
|
||||
"@types/node": "24.10.1",
|
||||
"@vitest/coverage-v8": "4.0.14",
|
||||
"@vitest/coverage-v8": "4.0.13",
|
||||
"mdast-util-to-string": "4.0.0",
|
||||
"remark": "15.0.1",
|
||||
"remark-parse": "11.0.0",
|
||||
@@ -18,7 +18,7 @@
|
||||
"unified": "11.0.5",
|
||||
"vite": "7.2.4",
|
||||
"vite-node": "5.2.0",
|
||||
"vitest": "4.0.14"
|
||||
"vitest": "4.0.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
@@ -915,21 +915,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vitest/coverage-v8": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.14.tgz",
|
||||
"integrity": "sha512-EYHLqN/BY6b47qHH7gtMxAg++saoGmsjWmAq9MlXxAz4M0NcHh9iOyKhBZyU4yxZqOd8Xnqp80/5saeitz4Cng==",
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.13.tgz",
|
||||
"integrity": "sha512-w77N6bmtJ3CFnL/YHiYotwW/JI3oDlR3K38WEIqegRfdMSScaYxwYKB/0jSNpOTZzUjQkG8HHEz4sdWQMWpQ5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bcoe/v8-coverage": "^1.0.2",
|
||||
"@vitest/utils": "4.0.14",
|
||||
"@vitest/utils": "4.0.13",
|
||||
"ast-v8-to-istanbul": "^0.3.8",
|
||||
"debug": "^4.4.3",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
"istanbul-lib-source-maps": "^5.0.6",
|
||||
"istanbul-reports": "^3.2.0",
|
||||
"magicast": "^0.5.1",
|
||||
"obug": "^2.1.1",
|
||||
"std-env": "^3.10.0",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
@@ -937,8 +937,8 @@
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vitest/browser": "4.0.14",
|
||||
"vitest": "4.0.14"
|
||||
"@vitest/browser": "4.0.13",
|
||||
"vitest": "4.0.13"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vitest/browser": {
|
||||
@@ -947,16 +947,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.14.tgz",
|
||||
"integrity": "sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==",
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.13.tgz",
|
||||
"integrity": "sha512-zYtcnNIBm6yS7Gpr7nFTmq8ncowlMdOJkWLqYvhr/zweY6tFbDkDi8BPPOeHxEtK1rSI69H7Fd4+1sqvEGli6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.0.14",
|
||||
"@vitest/utils": "4.0.14",
|
||||
"@vitest/spy": "4.0.13",
|
||||
"@vitest/utils": "4.0.13",
|
||||
"chai": "^6.2.1",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
@@ -965,13 +965,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.14.tgz",
|
||||
"integrity": "sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==",
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.13.tgz",
|
||||
"integrity": "sha512-eNCwzrI5djoauklwP1fuslHBjrbR8rqIVbvNlAnkq1OTa6XT+lX68mrtPirNM9TnR69XUPt4puBCx2Wexseylg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.0.14",
|
||||
"@vitest/spy": "4.0.13",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
@@ -992,9 +992,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz",
|
||||
"integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==",
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.13.tgz",
|
||||
"integrity": "sha512-ooqfze8URWbI2ozOeLDMh8YZxWDpGXoeY3VOgcDnsUxN0jPyPWSUvjPQWqDGCBks+opWlN1E4oP1UYl3C/2EQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1005,13 +1005,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.14.tgz",
|
||||
"integrity": "sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==",
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.13.tgz",
|
||||
"integrity": "sha512-9IKlAru58wcVaWy7hz6qWPb2QzJTKt+IOVKjAx5vb5rzEFPTL6H4/R9BMvjZ2ppkxKgTrFONEJFtzvnyEpiT+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.0.14",
|
||||
"@vitest/utils": "4.0.13",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -1019,13 +1019,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.14.tgz",
|
||||
"integrity": "sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==",
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.13.tgz",
|
||||
"integrity": "sha512-hb7Usvyika1huG6G6l191qu1urNPsq1iFc2hmdzQY3F5/rTgqQnwwplyf8zoYHkpt7H6rw5UfIw6i/3qf9oSxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.0.14",
|
||||
"@vitest/pretty-format": "4.0.13",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
@@ -1034,9 +1034,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.14.tgz",
|
||||
"integrity": "sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==",
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.13.tgz",
|
||||
"integrity": "sha512-hSu+m4se0lDV5yVIcNWqjuncrmBgwaXa2utFLIrBkQCQkt+pSwyZTPFQAZiiF/63j8jYa8uAeUZ3RSfcdWaYWw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -1044,13 +1044,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz",
|
||||
"integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==",
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.13.tgz",
|
||||
"integrity": "sha512-ydozWyQ4LZuu8rLp47xFUWis5VOKMdHjXCWhs1LuJsTNKww+pTHQNK4e0assIB9K80TxFyskENL6vCu3j34EYA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.0.14",
|
||||
"@vitest/pretty-format": "4.0.13",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -2439,24 +2439,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.14.tgz",
|
||||
"integrity": "sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==",
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.13.tgz",
|
||||
"integrity": "sha512-QSD4I0fN6uZQfftryIXuqvqgBxTvJ3ZNkF6RWECd82YGAYAfhcppBLFXzXJHQAAhVFyYEuFTrq6h0hQqjB7jIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.0.14",
|
||||
"@vitest/mocker": "4.0.14",
|
||||
"@vitest/pretty-format": "4.0.14",
|
||||
"@vitest/runner": "4.0.14",
|
||||
"@vitest/snapshot": "4.0.14",
|
||||
"@vitest/spy": "4.0.14",
|
||||
"@vitest/utils": "4.0.14",
|
||||
"@vitest/expect": "4.0.13",
|
||||
"@vitest/mocker": "4.0.13",
|
||||
"@vitest/pretty-format": "4.0.13",
|
||||
"@vitest/runner": "4.0.13",
|
||||
"@vitest/snapshot": "4.0.13",
|
||||
"@vitest/spy": "4.0.13",
|
||||
"@vitest/utils": "4.0.13",
|
||||
"debug": "^4.4.3",
|
||||
"es-module-lexer": "^1.7.0",
|
||||
"expect-type": "^1.2.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"obug": "^2.1.1",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3",
|
||||
"std-env": "^3.10.0",
|
||||
@@ -2479,11 +2479,12 @@
|
||||
"peerDependencies": {
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.0.14",
|
||||
"@vitest/browser-preview": "4.0.14",
|
||||
"@vitest/browser-webdriverio": "4.0.14",
|
||||
"@vitest/ui": "4.0.14",
|
||||
"@vitest/browser-playwright": "4.0.13",
|
||||
"@vitest/browser-preview": "4.0.13",
|
||||
"@vitest/browser-webdriverio": "4.0.13",
|
||||
"@vitest/ui": "4.0.13",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*"
|
||||
},
|
||||
@@ -2494,6 +2495,9 @@
|
||||
"@opentelemetry/api": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/debug": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"devDependencies": {
|
||||
"@types/mdast": "4.0.4",
|
||||
"@types/node": "24.10.1",
|
||||
"@vitest/coverage-v8": "4.0.14",
|
||||
"@vitest/coverage-v8": "4.0.13",
|
||||
"mdast-util-to-string": "4.0.0",
|
||||
"remark": "15.0.1",
|
||||
"remark-parse": "11.0.0",
|
||||
@@ -19,6 +19,6 @@
|
||||
"unified": "11.0.5",
|
||||
"vite": "7.2.4",
|
||||
"vite-node": "5.2.0",
|
||||
"vitest": "4.0.14"
|
||||
"vitest": "4.0.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
+3
-2
@@ -3,10 +3,11 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { createWriteStream, promises as fsp } from 'node:fs';
|
||||
import { createWriteStream } from 'node:fs';
|
||||
import { mkdir } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import glob from 'fast-glob';
|
||||
import walk from 'ignore-walk';
|
||||
import { Pack } from 'tar/pack';
|
||||
import meta from '../package.json' with { type: "json" };
|
||||
@@ -24,7 +25,7 @@ export default async function build() {
|
||||
const pack = new Pack({ cwd, gzip: true });
|
||||
const patterns = await walk({ path: cwd, ignoreFiles: ['.gitignore'] });
|
||||
|
||||
for await (const entry of fsp.glob(patterns, { cwd, ignore, dot: true })) {
|
||||
for await (const entry of glob.stream(patterns, { cwd, ignore, dot: true })) {
|
||||
pack.add(entry);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user