1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-01 04:35:42 +02:00

Compare commits

...

37 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
23358a5fe9 Revert incorrect cssCodeSplit change - it still creates separate CSS files
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-12-11 10:27:35 +00:00
copilot-swe-agent[bot]
fbc4da1c48 Change cssCodeSplit from true to false to bundle CSS into JS
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-12-11 09:25:58 +00:00
copilot-swe-agent[bot]
28e196e978 Initial plan 2025-12-11 09:22:59 +00:00
まっちゃてぃー。
2cffd9f0fb fix(sw): オフライン時のfetch timeout処理を実装 (#16952)
* fix(sw): implement fetch timeout handling for navigation and offline content

* fix(sw): increase fetch timeout

* fix(sw): improve fetch timeout handling for i18n content

* fix(sw): 結局、fetchを通るかCacheがhitするはずなので、i18nのところはいらない

* fix(sw): 400番台のエラーを無条件でオフラインページにしていたのを修正

* 間違えた

* i18nもtimeoutが必要

* import sortingを修正

* import sortingを修正

* Fix: Frontend のsharedにはアクセスできないじゃん...

* SPDX

* Update CHANGELOG.md

* Update packages/sw/src/scripts/lang.ts

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>

* Update packages/sw/src/sw.ts

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>

* Update CHANGELOG.md

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>

---------

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
2025-12-10 17:26:30 +09:00
syuilo
988f5ab69f fix(backend): ジョブキューでSentryが有効にならない問題を修正 2025-12-08 15:44:37 +09:00
かっこかり
3afe7c5348 Update CHANGELOG.md [ci skip] 2025-12-08 10:20:07 +09:00
かっこかり
73cc30f50f fix(frontend): ロード時の言語判定結果が保存されない問題を修正 (#16956)
* fix(frontend): ロード時の言語判定結果が保存されない問題を修正

* Update Changelog
2025-12-08 10:17:13 +09:00
github-actions[bot]
da3b3af984 [skip ci] Update CHANGELOG.md (prepend template) 2025-12-06 12:23:00 +00:00
github-actions[bot]
3273ca7512 Release: 2025.12.0 2025-12-06 12:22:55 +00:00
syuilo
b67bfe0763 Update CHANGELOG.md
Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
2025-12-06 21:03:06 +09:00
かっこかり
63d2870755 fix(backend): fix tests (#16947)
* fix(backend): shouldHideNoteByTimeのロジックの誤りを修正

* fix tests
2025-12-06 19:32:13 +09:00
syuilo
61f9c148f0 🎨 2025-12-06 18:46:13 +09:00
syuilo
8927a9e98a Update CHANGELOG.md 2025-12-06 18:27:57 +09:00
おさむのひと
dc77d59f87 Merge commit from fork 2025-12-06 18:25:20 +09:00
github-actions[bot]
2d0dae236f Bump version to 2025.12.0-beta.0 2025-12-06 08:41:10 +00:00
syuilo
a1f0ca4b8f use node 22.15.0 by default
#16944
2025-12-06 17:39:17 +09:00
syuilo
2a996287e3 update pnpm to 10.24.0 2025-12-06 16:44:23 +09:00
renovate[bot]
65dd917bfb fix(deps): update [backend] update dependencies [ci skip] (#16941)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-05 23:55:00 +09:00
renovate[bot]
b0bffd3842 fix(deps): update [frontend] update dependencies [ci skip] (#16942)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-05 23:10:04 +09:00
renovate[bot]
4ee6f90ab2 chore(deps): update [tools] update dependencies to v4.0.14 [ci skip] (#16940)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-05 21:31:04 +09:00
renovate[bot]
50379e52db fix(deps): update dependency nodemailer to v7.0.11 [security] [ci skip] (#16919)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
2025-12-05 20:57:47 +09:00
renovate[bot]
6bb29ab5c3 fix(deps): update dependency @sentry/node to v10.27.0 [security] [ci skip] (#16860)
* fix(deps): update dependency @sentry/node to v10.27.0 [security]

* fix

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
2025-12-05 20:42:36 +09:00
syuilo
fc1e2229e5 fix(frontend): stacking router viewで連続して戻る操作を行うと何も表示されなくなる問題を修正 2025-12-04 19:03:41 +09:00
syuilo
daf2a57b3c Revert "fix(frontend): stacking router viewで連続して戻る操作を行うと何も表示されなくなる問題を修正"
This reverts commit a3c3052d0f.
2025-12-04 19:01:45 +09:00
renovate[bot]
6716950d7f fix(deps): update dependency body-parser to v2.2.1 [security] (#16899)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 17:39:33 +09:00
github-actions[bot]
29a0750eef Bump version to 2025.12.0-alpha.2 2025-12-04 07:51:39 +00:00
syuilo
24bd150967 refactor(backend): 変換後.config.jsonに統一するように+修正など (#16929)
* wip

* Update config.ts

* wip

* convertは元ファイルを変更するようなニュアンスを若干感じるのでcompileに改名

* wip

* Update package.json

* Revert "Update package.json"

This reverts commit e5c2802316.

* wip

* wip

* 謎

* clean up

* wip

* wip

* Revert "wip"

This reverts commit 3aa25ac7cf.

* wip

* wip

* Update dummy.yml

* wip

* Update compile_config.js

* Update compile_config.js

* wip

* Revert "wip"

This reverts commit fd78e097c6.

* Update dummy.yml

* Update compile_config.js
2025-12-04 16:49:25 +09:00
syuilo
a3c3052d0f fix(frontend): stacking router viewで連続して戻る操作を行うと何も表示されなくなる問題を修正 2025-12-04 15:19:15 +09:00
かっこかり
a6f57d99f9 fix(gh): fix federation test (#16936) 2025-12-04 13:36:30 +09:00
syuilo
55ef4c5faa tweak convert_config 2025-12-03 18:20:41 +09:00
syuilo
6293a57de8 fix action 2025-12-03 18:10:08 +09:00
Kagami Sascha Rosylight
5512898463 Merge commit from fork
* Change trustProxy default value to false

* Update trustProxy default value in example.yml

* Update trustProxy default description in example.yml
2025-12-03 16:08:45 +09:00
Copilot
0b77dc8c48 Add backend memory usage comparison action for PRs (#16926)
* Initial plan

* Add backend memory usage comparison action

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* Fix deprecated serverProcess.killed usage

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* Add explicit permissions to save-pr-number job

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* Change PR comment text from Japanese to English

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* Inline memory measurement script to fix base ref compatibility

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* Revert "Inline memory measurement script to fix base ref compatibility"

This reverts commit 6f76a121ef.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-12-03 16:02:49 +09:00
syuilo
9900b3492a add DeepWiki badge to enable auto-refresh 2025-12-03 12:02:18 +09:00
github-actions[bot]
d9c9b95fc0 Bump version to 2025.12.0-alpha.1 2025-12-03 00:15:47 +00:00
syuilo
613900598a New Crowdin updates (#16911)
* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Italian)
2025-12-03 09:01:19 +09:00
おさむのひと
1facca1ac5 enhance(backend): 起動前にconfigをjson化 (#16923)
* enhance(backend): 起動前にconfigをjson化

* fix

* fix

* fix

* fix

* fix

* fix CHANGELOG.md

* fix

* Update CHANGELOG.md

* get original
2025-12-03 09:00:37 +09:00
54 changed files with 2466 additions and 1110 deletions

View File

@@ -110,10 +110,10 @@ port: 3000
# Changes how the server interpret the origin IP of the request. # Changes how the server interpret the origin IP of the request.
# #
# Any format supported by Fastify is accepted. # Any format supported by Fastify is accepted.
# Default: trust all proxies (i.e. trustProxy: true) # Default: do not trust any proxies (i.e. trustProxy: false)
# See: https://fastify.dev/docs/latest/reference/server/#trustproxy # See: https://fastify.dev/docs/latest/reference/server/#trustproxy
# #
# trustProxy: 1 # trustProxy: false
# ┌──────────────────────────┐ # ┌──────────────────────────┐
#───┘ PostgreSQL configuration └──────────────────────────────── #───┘ PostgreSQL configuration └────────────────────────────────

View File

@@ -5,7 +5,7 @@
"workspaceFolder": "/workspace", "workspaceFolder": "/workspace",
"features": { "features": {
"ghcr.io/devcontainers/features/node:1": { "ghcr.io/devcontainers/features/node:1": {
"version": "24.10.0" "version": "22.15.0"
}, },
"ghcr.io/devcontainers-extra/features/pnpm:2": { "ghcr.io/devcontainers-extra/features/pnpm:2": {
"version": "10.10.0" "version": "10.10.0"

View File

@@ -0,0 +1,87 @@
# 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

View File

@@ -0,0 +1,122 @@
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.

View File

@@ -1 +1 @@
24.10.0 22.15.0

View File

@@ -1,16 +1,29 @@
## 2025.12.0 ## Unreleased
### General ### General
- -
### Client ### Client
- - Fix: 特定の条件下でMisskeyが起動せず空白のページが表示されることがある問題を軽減
- Fix: 初回読み込み時などに、言語設定で不整合が発生することがある問題を修正
### Server
- Fix: ジョブキューでSentryが有効にならない問題を修正
## 2025.12.0
### Note
- configの`trustProxy`のデフォルト値を`false`に変更しました。アップデート前に現在のconfigをご確認の上、必要に応じて値を変更してください。
### Client
- Fix: stacking router viewで連続して戻る操作を行うと何も表示されなくなる問題を修正
### Server ### Server
- Enhance: メモリ使用量を削減しました - Enhance: メモリ使用量を削減しました
- Enhance: ActivityPubアクティビティを送信する際のパフォーマンス向上 - Enhance: ActivityPubアクティビティを送信する際のパフォーマンス向上
- Enhance: 依存関係の更新 - Enhance: 依存関係の更新
- Fix: セキュリティに関する修正
## 2025.11.1 ## 2025.11.1

View File

@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.4 # syntax = docker/dockerfile:1.4
ARG NODE_VERSION=24.10.0-bookworm ARG NODE_VERSION=22.15.0-bookworm
# build assets & compile TypeScript # build assets & compile TypeScript

View File

@@ -24,6 +24,8 @@
<a href="https://www.patreon.com/syuilo"> <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> <img src="https://custom-icon-badges.herokuapp.com/badge/become_a-patron-F96854?logoColor=F96854&style=for-the-badge&logo=patreon&labelColor=363B40" alt="become a patron"/></a>
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/misskey-dev/misskey)
</div> </div>
## Thanks ## Thanks

View File

@@ -319,10 +319,10 @@ remoteUserCaution: "Para el usuario remoto, la información está incompleta"
activity: "Actividad" activity: "Actividad"
images: "Imágenes" images: "Imágenes"
image: "Imágenes" image: "Imágenes"
birthday: "Fecha de nacimiento" birthday: "Cumpleaños"
yearsOld: "{age} años" yearsOld: "{age} años"
registeredDate: "Fecha de registro" registeredDate: "Fecha de registro"
location: "Lugar" location: "Ubicación"
theme: "Tema" theme: "Tema"
themeForLightMode: "Tema para usar en Modo Linterna" themeForLightMode: "Tema para usar en Modo Linterna"
themeForDarkMode: "Tema para usar en Modo Oscuro" 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." 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" serverLogs: "Registros del servidor"
deleteAll: "Eliminar todos" deleteAll: "Eliminar todos"
showFixedPostForm: "Mostrar el formulario de las entradas encima de la línea de tiempo" showFixedPostForm: "Visualizar la ventana de publicación en la parte superior de la línea de tiempo."
showFixedPostFormInChannel: "Mostrar el formulario de publicación por encima de la cronología (Canales)" 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" withRepliesByDefaultForNewlyFollowed: "Incluir por defecto respuestas de usuarios recién seguidos en la línea de tiempo"
newNoteRecived: "Tienes una nota nueva" newNoteRecived: "Tienes una nota nueva"
@@ -844,7 +844,7 @@ jumpToSpecifiedDate: "Saltar a una fecha específica"
showingPastTimeline: "Mostrar líneas de tiempo antiguas" showingPastTimeline: "Mostrar líneas de tiempo antiguas"
clear: "Limpiar" clear: "Limpiar"
markAllAsRead: "Marcar todo como leído" markAllAsRead: "Marcar todo como leído"
goBack: "Deseleccionar" goBack: "Anterior"
unlikeConfirm: "¿Quitar como favorito?" unlikeConfirm: "¿Quitar como favorito?"
fullView: "Vista completa" fullView: "Vista completa"
quitFullView: "quitar vista completa" quitFullView: "quitar vista completa"
@@ -1511,7 +1511,7 @@ _emojiPalette:
palettes: "Paleta\n" palettes: "Paleta\n"
enableSyncBetweenDevicesForPalettes: "Activar la sincronización de paletas entre dispositivos" enableSyncBetweenDevicesForPalettes: "Activar la sincronización de paletas entre dispositivos"
paletteForMain: "Paleta principal" paletteForMain: "Paleta principal"
paletteForReaction: "Paleta de reacción" paletteForReaction: "Paleta utilizada para las reacciones"
_settings: _settings:
driveBanner: "Puedes gestionar y configurar la unidad, comprobar su uso y configurar los ajustes de carga de archivos." 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." 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" accountData: "Datos de la cuenta"
accountDataBanner: "Exportación e importación para gestionar los 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." muteAndBlockBanner: "Puedes configurar y gestionar ajustes para ocultar contenidos y restringir acciones a usuarios específicos."
accessibilityBanner: "Puedes personalizar los visuales y el comportamiento del cliente, y configurar los ajustes para optimizar el uso." accessibilityBanner: "Puedes personalizar el aspecto y el comportamiento del cliente y configurar los ajustes para optimizar su 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." 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." 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." preferencesBanner: "Puedes configurar el comportamiento general del cliente según tus preferencias."
@@ -1540,7 +1540,7 @@ _settings:
ifOff: "Si está desactivado" ifOff: "Si está desactivado"
enableSyncThemesBetweenDevices: "Sincronizar los temas instalados entre dispositivos." enableSyncThemesBetweenDevices: "Sincronizar los temas instalados entre dispositivos."
enablePullToRefresh: "Tirar para actualizar" enablePullToRefresh: "Tirar para actualizar"
enablePullToRefresh_description: "Si utiliza un ratón, arrastre mientras pulsa la rueda de desplazamiento." enablePullToRefresh_description: "Si utilizas un ratón, arrastra mientras pulsas 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." 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: "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." 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." started: "El proceso de eliminación ha comenzado."
inProgress: "La eliminación está en proceso." inProgress: "La eliminación está en proceso."
_ad: _ad:
back: "Deseleccionar" back: "Anterior"
reduceFrequencyOfThisAd: "Mostrar menos este anuncio." reduceFrequencyOfThisAd: "Mostrar menos este anuncio."
hide: "No mostrar" hide: "No mostrar"
timezoneinfo: "El día de la semana está determidado por la zona horaria del servidor." timezoneinfo: "El día de la semana está determidado por la zona horaria del servidor."
@@ -2610,10 +2610,10 @@ _profile:
name: "Nombre" name: "Nombre"
username: "Nombre de usuario" username: "Nombre de usuario"
description: "Descripción" description: "Descripción"
youCanIncludeHashtags: "Puedes añadir hashtags" youCanIncludeHashtags: "También puedes incluir hashtags en tu biografía"
metadata: "información adicional" metadata: "información adicional"
metadataEdit: "Editar información adicional" metadataEdit: "Editar información adicional"
metadataDescription: "Muestra la información adicional en el perfil" metadataDescription: "Usando esto puedes mostrar campos de información adicionales en tu perfil."
metadataLabel: "Etiqueta" metadataLabel: "Etiqueta"
metadataContent: "Contenido" metadataContent: "Contenido"
changeAvatar: "Cambiar avatar" changeAvatar: "Cambiar avatar"

View File

@@ -83,6 +83,8 @@ files: "Allegati"
download: "Scarica" download: "Scarica"
driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\", e le Note a cui è stato allegato?" driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\", e le Note a cui è stato allegato?"
unfollowConfirm: "Vuoi davvero togliere il Following a {name}?" 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." 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." importRequested: "Hai richiesto un'importazione. Potrebbe richiedere un po' di tempo."
lists: "Liste" lists: "Liste"
@@ -2350,13 +2352,13 @@ _ago:
yearsAgo: "{n} anni fa" yearsAgo: "{n} anni fa"
invalid: "Niente da visualizzare" invalid: "Niente da visualizzare"
_timeIn: _timeIn:
seconds: "Dopo {n} secondi" seconds: "Tra {n} secondi"
minutes: "Dopo {n} minuti" minutes: "Tra {n} minuti"
hours: "Dopo {n} ore" hours: "Tra {n} ore"
days: "Dopo {n} giorni" days: "Tra {n} giorni"
weeks: "Dopo {n} settimane" weeks: "Tra {n} settimane"
months: "Dopo {n} mesi" months: "Tra {n} mesi"
years: "Dopo {n} anni" years: "Tra {n} anni"
_time: _time:
second: "s" second: "s"
minute: "min" minute: "min"

View File

@@ -1,12 +1,12 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2025.12.0-alpha.0", "version": "2025.12.0",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/misskey-dev/misskey.git" "url": "https://github.com/misskey-dev/misskey.git"
}, },
"packageManager": "pnpm@10.23.0", "packageManager": "pnpm@10.24.0",
"workspaces": [ "workspaces": [
"packages/misskey-js", "packages/misskey-js",
"packages/i18n", "packages/i18n",
@@ -22,14 +22,15 @@
], ],
"private": true, "private": true,
"scripts": { "scripts": {
"compile-config": "cd packages/backend && pnpm compile-config",
"build-pre": "node ./scripts/build-pre.js", "build-pre": "node ./scripts/build-pre.js",
"build-assets": "node ./scripts/build-assets.mjs", "build-assets": "node ./scripts/build-assets.mjs",
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets", "build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
"build-storybook": "pnpm --filter frontend build-storybook", "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", "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 && node ./built/boot/entry.js", "start": "pnpm check:connect && cd packages/backend && pnpm compile-config && node ./built/boot/entry.js",
"start:inspect": "cd packages/backend && node --inspect ./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 node ./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",
"cli": "cd packages/backend && pnpm cli", "cli": "cd packages/backend && pnpm cli",
"init": "pnpm migrate", "init": "pnpm migrate",
"migrate": "cd packages/backend && pnpm migrate", "migrate": "cd packages/backend && pnpm migrate",
@@ -80,7 +81,7 @@
"eslint": "9.39.1", "eslint": "9.39.1",
"globals": "16.5.0", "globals": "16.5.0",
"ncp": "2.0.0", "ncp": "2.0.0",
"pnpm": "10.23.0", "pnpm": "10.24.0",
"start-server-and-test": "2.1.3" "start-server-and-test": "2.1.3"
}, },
"optionalDependencies": { "optionalDependencies": {

View File

@@ -3,14 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { isConcurrentIndexMigrationEnabled } from "./js/migration-config.js"; const isConcurrentIndexMigrationEnabled = process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1';
export class CompositeNoteIndex1745378064470 { export class CompositeNoteIndex1745378064470 {
name = 'CompositeNoteIndex1745378064470'; name = 'CompositeNoteIndex1745378064470';
transaction = isConcurrentIndexMigrationEnabled() ? false : undefined; transaction = isConcurrentIndexMigrationEnabled ? false : undefined;
async up(queryRunner) { async up(queryRunner) {
const concurrently = isConcurrentIndexMigrationEnabled(); const concurrently = isConcurrentIndexMigrationEnabled;
if (concurrently) { 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'`); 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) { async down(queryRunner) {
const mayConcurrently = isConcurrentIndexMigrationEnabled() ? 'CONCURRENTLY' : ''; const mayConcurrently = isConcurrentIndexMigrationEnabled ? 'CONCURRENTLY' : '';
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_724b311e6f883751f261ebe378"`); await queryRunner.query(`DROP INDEX IF EXISTS "IDX_724b311e6f883751f261ebe378"`);
await queryRunner.query(`CREATE INDEX ${mayConcurrently} "IDX_5b87d9d19127bd5d92026017a7" ON "note" ("userId")`); await queryRunner.query(`CREATE INDEX ${mayConcurrently} "IDX_5b87d9d19127bd5d92026017a7" ON "note" ("userId")`);
} }

View File

@@ -3,17 +3,15 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import {loadConfig} from "./js/migration-config.js";
export class MigrateSomeConfigFileSettingsToMeta1746949539915 { export class MigrateSomeConfigFileSettingsToMeta1746949539915 {
name = 'MigrateSomeConfigFileSettingsToMeta1746949539915' name = 'MigrateSomeConfigFileSettingsToMeta1746949539915'
async up(queryRunner) { async up(queryRunner) {
const config = loadConfig();
// $1 cannot be used in ALTER TABLE queries // $1 cannot be used in ALTER TABLE queries
await queryRunner.query(`ALTER TABLE "meta" ADD "proxyRemoteFiles" boolean NOT NULL DEFAULT ${config.proxyRemoteFiles}`); 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 ${config.signToActivityPubGet}`); 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 ${!config.disallowExternalApRedirect}`); await queryRunner.query(`ALTER TABLE "meta" ADD "allowExternalApRedirect" boolean NOT NULL DEFAULT TRUE`);
} }
async down(queryRunner) { async down(queryRunner) {

View File

@@ -1,31 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { path as configYamlPath } from '../../built/config.js';
import * as yaml from 'js-yaml';
import fs from "node:fs";
export function isConcurrentIndexMigrationEnabled() {
return process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1';
}
let loadedConfigCache = undefined;
function loadConfigInternal() {
const config = yaml.load(fs.readFileSync(configYamlPath, '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;
}

View File

@@ -1,7 +1,8 @@
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { loadConfig } from './built/config.js'; import { loadConfig } from './built/config.js';
import { entities } from './built/postgres.js'; import { entities } from './built/postgres.js';
import { isConcurrentIndexMigrationEnabled } from "./migration/js/migration-config.js";
const isConcurrentIndexMigrationEnabled = process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1';
const config = loadConfig(); const config = loadConfig();
@@ -15,5 +16,5 @@ export default new DataSource({
extra: config.db.extra, extra: config.db.extra,
entities: entities, entities: entities,
migrations: ['migration/*.js'], migrations: ['migration/*.js'],
migrationsTransactionMode: isConcurrentIndexMigrationEnabled() ? 'each' : 'all', migrationsTransactionMode: isConcurrentIndexMigrationEnabled ? 'each' : 'all',
}); });

View File

@@ -7,36 +7,37 @@
"node": "^22.15.0 || ^24.10.0" "node": "^22.15.0 || ^24.10.0"
}, },
"scripts": { "scripts": {
"start": "node ./built/boot/entry.js", "start": "pnpm compile-config && node ./built/boot/entry.js",
"start:inspect": "node --inspect ./built/boot/entry.js", "start:inspect": "pnpm compile-config && node --inspect ./built/boot/entry.js",
"start:test": "cross-env NODE_ENV=test node ./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 typeorm migration:run -d ormconfig.js", "migrate": "pnpm compile-config && pnpm typeorm migration:run -d ormconfig.js",
"revert": "pnpm typeorm migration:revert -d ormconfig.js", "revert": "pnpm compile-config && pnpm typeorm migration:revert -d ormconfig.js",
"cli": "node ./built/boot/cli.js", "cli": "pnpm compile-config && node ./built/boot/cli.js",
"check:connect": "node ./scripts/check_connect.js", "check:connect": "pnpm compile-config && node ./scripts/check_connect.js",
"compile-config": "node ./scripts/compile_config.js",
"build": "swc src -d built -D --strip-leading-paths", "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", "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", "watch:swc": "swc src -d built -D -w --strip-leading-paths",
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json", "build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
"watch": "node ./scripts/watch.mjs", "watch": "pnpm compile-config && node ./scripts/watch.mjs",
"restart": "pnpm build && pnpm start", "restart": "pnpm build && pnpm start",
"dev": "node ./scripts/dev.mjs", "dev": "pnpm compile-config && node ./scripts/dev.mjs",
"typecheck": "tsc --noEmit && tsc -p test --noEmit && tsc -p test-federation --noEmit", "typecheck": "tsc --noEmit && tsc -p test --noEmit && tsc -p test-federation --noEmit",
"eslint": "eslint --quiet \"{src,test-federation}/**/*.ts\"", "eslint": "eslint --quiet \"{src,test-federation}/**/*.ts\"",
"lint": "pnpm typecheck && pnpm eslint", "lint": "pnpm typecheck && pnpm eslint",
"jest": "cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.unit.cjs", "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 node ./jest.js --forceExit --config jest.config.e2e.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": "node ./jest.js --forceExit --config jest.config.fed.cjs", "jest:fed": "pnpm compile-config && 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": "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 node ./jest.js --coverage --forceExit --config jest.config.e2e.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 node ./jest.js --clearCache", "jest-clear": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --clearCache",
"test": "pnpm jest", "test": "pnpm jest",
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e", "test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
"test:fed": "pnpm jest:fed", "test:fed": "pnpm jest:fed",
"test-and-coverage": "pnpm jest-and-coverage", "test-and-coverage": "pnpm jest-and-coverage",
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e", "test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
"check-migrations": "node scripts/check_migrations_clean.js", "check-migrations": "node scripts/check_migrations_clean.js",
"generate-api-json": "node ./scripts/generate_api_json.js" "generate-api-json": "pnpm compile-config && node ./scripts/generate_api_json.js"
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-android-arm64": "1.3.11", "@swc/core-android-arm64": "1.3.11",
@@ -70,8 +71,8 @@
"utf-8-validate": "6.0.5" "utf-8-validate": "6.0.5"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.937.0", "@aws-sdk/client-s3": "3.940.0",
"@aws-sdk/lib-storage": "3.937.0", "@aws-sdk/lib-storage": "3.940.0",
"@discordapp/twemoji": "16.0.1", "@discordapp/twemoji": "16.0.1",
"@fastify/accepts": "5.0.3", "@fastify/accepts": "5.0.3",
"@fastify/cookie": "11.0.2", "@fastify/cookie": "11.0.2",
@@ -83,13 +84,13 @@
"@kitajs/html": "4.2.11", "@kitajs/html": "4.2.11",
"@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.2.5", "@misskey-dev/summaly": "5.2.5",
"@napi-rs/canvas": "0.1.82", "@napi-rs/canvas": "0.1.83",
"@nestjs/common": "11.1.9", "@nestjs/common": "11.1.9",
"@nestjs/core": "11.1.9", "@nestjs/core": "11.1.9",
"@nestjs/testing": "11.1.9", "@nestjs/testing": "11.1.9",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sentry/node": "10.26.0", "@sentry/node": "10.27.0",
"@sentry/profiling-node": "10.26.0", "@sentry/profiling-node": "10.27.0",
"@simplewebauthn/server": "13.2.2", "@simplewebauthn/server": "13.2.2",
"@sinonjs/fake-timers": "15.0.0", "@sinonjs/fake-timers": "15.0.0",
"@smithy/node-http-handler": "4.4.5", "@smithy/node-http-handler": "4.4.5",
@@ -103,8 +104,8 @@
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"bcryptjs": "3.0.3", "bcryptjs": "3.0.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"body-parser": "2.2.0", "body-parser": "2.2.1",
"bullmq": "5.64.1", "bullmq": "5.65.0",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"cbor": "10.0.11", "cbor": "10.0.11",
"chalk": "5.6.2", "chalk": "5.6.2",
@@ -120,15 +121,14 @@
"file-type": "21.1.1", "file-type": "21.1.1",
"fluent-ffmpeg": "2.1.3", "fluent-ffmpeg": "2.1.3",
"form-data": "4.0.5", "form-data": "4.0.5",
"got": "14.6.4", "got": "14.6.5",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"http-link-header": "1.1.3", "http-link-header": "1.1.3",
"i18n": "workspace:*", "i18n": "workspace:*",
"ioredis": "5.8.2", "ioredis": "5.8.2",
"ip-cidr": "4.0.2", "ip-cidr": "4.0.2",
"ipaddr.js": "2.2.0", "ipaddr.js": "2.3.0",
"is-svg": "6.1.0", "is-svg": "6.1.0",
"js-yaml": "4.1.1",
"json5": "2.2.3", "json5": "2.2.3",
"jsonld": "9.0.0", "jsonld": "9.0.0",
"jsrsasign": "11.1.0", "jsrsasign": "11.1.0",
@@ -143,7 +143,7 @@
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"node-html-parser": "7.0.1", "node-html-parser": "7.0.1",
"nodemailer": "7.0.10", "nodemailer": "7.0.11",
"nsfwjs": "4.2.0", "nsfwjs": "4.2.0",
"oauth": "0.10.2", "oauth": "0.10.2",
"oauth2orize": "1.12.0", "oauth2orize": "1.12.0",
@@ -151,7 +151,7 @@
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "9.4.1", "otpauth": "9.4.1",
"pg": "8.16.3", "pg": "8.16.3",
"pkce-challenge": "5.0.0", "pkce-challenge": "5.0.1",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"qrcode": "1.5.4", "qrcode": "1.5.4",
@@ -187,7 +187,7 @@
"@jest/globals": "29.7.0", "@jest/globals": "29.7.0",
"@kitajs/ts-html-plugin": "4.1.3", "@kitajs/ts-html-plugin": "4.1.3",
"@nestjs/platform-express": "11.1.9", "@nestjs/platform-express": "11.1.9",
"@sentry/vue": "10.26.0", "@sentry/vue": "10.27.0",
"@simplewebauthn/types": "12.0.0", "@simplewebauthn/types": "12.0.0",
"@swc/jest": "0.2.39", "@swc/jest": "0.2.39",
"@types/accepts": "1.3.7", "@types/accepts": "1.3.7",
@@ -198,7 +198,6 @@
"@types/fluent-ffmpeg": "2.1.28", "@types/fluent-ffmpeg": "2.1.28",
"@types/http-link-header": "1.0.7", "@types/http-link-header": "1.0.7",
"@types/jest": "29.5.14", "@types/jest": "29.5.14",
"@types/js-yaml": "4.0.9",
"@types/jsonld": "1.5.15", "@types/jsonld": "1.5.15",
"@types/jsrsasign": "10.5.15", "@types/jsrsasign": "10.5.15",
"@types/mime-types": "3.0.1", "@types/mime-types": "3.0.1",
@@ -223,8 +222,8 @@
"@types/vary": "1.1.3", "@types/vary": "1.1.3",
"@types/web-push": "3.6.4", "@types/web-push": "3.6.4",
"@types/ws": "8.18.1", "@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.47.0", "@typescript-eslint/parser": "8.48.0",
"aws-sdk-client-mock": "4.1.0", "aws-sdk-client-mock": "4.1.0",
"cross-env": "10.1.0", "cross-env": "10.1.0",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",
@@ -233,6 +232,7 @@
"jest": "29.7.0", "jest": "29.7.0",
"jest-mock": "29.7.0", "jest-mock": "29.7.0",
"jest-util": "29.7.0", "jest-util": "29.7.0",
"js-yaml": "4.1.1",
"nodemon": "3.1.11", "nodemon": "3.1.11",
"pid-port": "2.0.0", "pid-port": "2.0.0",
"simple-oauth2": "5.1.0", "simple-oauth2": "5.1.0",

View File

@@ -0,0 +1,54 @@
/*
* 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 ✓');

View File

@@ -0,0 +1,152 @@
/*
* 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);
});

View File

@@ -6,7 +6,6 @@
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path'; import { dirname, resolve } from 'node:path';
import * as yaml from 'js-yaml';
import { type FastifyServerOptions } from 'fastify'; import { type FastifyServerOptions } from 'fastify';
import type * as Sentry from '@sentry/node'; import type * as Sentry from '@sentry/node';
import type * as SentryVue from '@sentry/vue'; import type * as SentryVue from '@sentry/vue';
@@ -218,21 +217,15 @@ export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch';
const _filename = fileURLToPath(import.meta.url); const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename); 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 = 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');
export function loadConfig(): Config { 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 meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8'));
const frontendManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_vite_/manifest.json'); const frontendManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_vite_/manifest.json');
@@ -244,7 +237,7 @@ export function loadConfig(): Config {
JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8')) JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8'))
: { 'src/boot.ts': { file: null } }; : { 'src/boot.ts': { file: null } };
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; const config = JSON.parse(fs.readFileSync(compiledConfigFilePath, 'utf-8')) as Source;
const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? ''); const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? '');
const version = meta.version; const version = meta.version;

View File

@@ -15,6 +15,7 @@ import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepos
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { DebounceLoader } from '@/misc/loader.js'; import { DebounceLoader } from '@/misc/loader.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js';
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
import type { OnModuleInit } from '@nestjs/common'; import type { OnModuleInit } from '@nestjs/common';
import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { CustomEmojiService } from '../CustomEmojiService.js';
@@ -116,12 +117,7 @@ export class NoteEntityService implements OnModuleInit {
private treatVisibility(packedNote: Packed<'Note'>): Packed<'Note'>['visibility'] { private treatVisibility(packedNote: Packed<'Note'>): Packed<'Note'>['visibility'] {
if (packedNote.visibility === 'public' || packedNote.visibility === 'home') { if (packedNote.visibility === 'public' || packedNote.visibility === 'home') {
const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore; const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore;
if ((followersOnlyBefore != null) if (shouldHideNoteByTime(followersOnlyBefore, packedNote.createdAt)) {
&& (
(followersOnlyBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (followersOnlyBefore * 1000)))
|| (followersOnlyBefore > 0 && (new Date(packedNote.createdAt).getTime() < followersOnlyBefore * 1000))
)
) {
packedNote.visibility = 'followers'; packedNote.visibility = 'followers';
} }
} }
@@ -141,12 +137,7 @@ export class NoteEntityService implements OnModuleInit {
if (!hide) { if (!hide) {
const hiddenBefore = packedNote.user.makeNotesHiddenBefore; const hiddenBefore = packedNote.user.makeNotesHiddenBefore;
if ((hiddenBefore != null) if (shouldHideNoteByTime(hiddenBefore, packedNote.createdAt)) {
&& (
(hiddenBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (hiddenBefore * 1000)))
|| (hiddenBefore > 0 && (new Date(packedNote.createdAt).getTime() < hiddenBefore * 1000))
)
) {
hide = true; hide = true;
} }
} }

View File

@@ -0,0 +1,29 @@
/*
* 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;
}
}

View File

@@ -157,7 +157,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
} }
let Sentry: typeof import('@sentry/node') | undefined; let Sentry: typeof import('@sentry/node') | undefined;
if (Sentry != null) { if (this.config.sentryForBackend) {
import('@sentry/node').then((mod) => { import('@sentry/node').then((mod) => {
Sentry = mod; Sentry = mod;
}); });

View File

@@ -5,21 +5,20 @@
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import { Writable } from 'node:stream'; import { Writable } from 'node:stream';
import { Inject, Injectable, StreamableFile } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { MoreThan } from 'typeorm';
import { format as dateFormat } from 'date-fns'; import { format as dateFormat } from 'date-fns';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js'; import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, PollsRepository, UsersRepository } from '@/models/_.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js'; import { DriveService } from '@/core/DriveService.js';
import { createTemp } from '@/misc/create-temp.js'; import { createTemp } from '@/misc/create-temp.js';
import type { MiPoll } from '@/models/Poll.js'; import type { MiPoll } from '@/models/Poll.js';
import type { MiNote } from '@/models/Note.js'; import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.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 { IdService } from '@/core/IdService.js';
import { NotificationService } from '@/core/NotificationService.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 { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq'; import type * as Bull from 'bullmq';
import type { DbJobDataWithUser } from '../types.js'; import type { DbJobDataWithUser } from '../types.js';
@@ -43,6 +42,7 @@ export class ExportClipsProcessorService {
private driveService: DriveService, private driveService: DriveService,
private queueLoggerService: QueueLoggerService, private queueLoggerService: QueueLoggerService,
private queryService: QueryService,
private idService: IdService, private idService: IdService,
private notificationService: NotificationService, private notificationService: NotificationService,
) { ) {
@@ -100,16 +100,16 @@ export class ExportClipsProcessorService {
}); });
while (true) { while (true) {
const clips = await this.clipsRepository.find({ const query = this.clipsRepository.createQueryBuilder('clip')
where: { .where('clip.userId = :userId', { userId: user.id })
userId: user.id, .orderBy('clip.id', 'ASC')
...(cursor ? { id: MoreThan(cursor) } : {}), .take(100);
},
take: 100, if (cursor) {
order: { query.andWhere('clip.id > :cursor', { cursor });
id: 1, }
},
}); const clips = await query.getMany();
if (clips.length === 0) { if (clips.length === 0) {
job.updateProgress(100); job.updateProgress(100);
@@ -124,7 +124,7 @@ export class ExportClipsProcessorService {
const isFirst = exportedClipsCount === 0; const isFirst = exportedClipsCount === 0;
await writer.write(isFirst ? content : ',\n' + content); await writer.write(isFirst ? content : ',\n' + content);
await this.processClipNotes(writer, clip.id); await this.processClipNotes(writer, clip.id, user.id);
await writer.write(']}'); await writer.write(']}');
exportedClipsCount++; exportedClipsCount++;
@@ -134,22 +134,25 @@ export class ExportClipsProcessorService {
} }
} }
async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string): Promise<void> { async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string, userId: string): Promise<void> {
let exportedClipNotesCount = 0; let exportedClipNotesCount = 0;
let cursor: MiClipNote['id'] | null = null; let cursor: MiClipNote['id'] | null = null;
while (true) { while (true) {
const clipNotes = await this.clipNotesRepository.find({ const query = this.clipNotesRepository.createQueryBuilder('clipNote')
where: { .leftJoinAndSelect('clipNote.note', 'note')
clipId, .leftJoinAndSelect('note.user', 'user')
...(cursor ? { id: MoreThan(cursor) } : {}), .where('clipNote.clipId = :clipId', { clipId })
}, .orderBy('clipNote.id', 'ASC')
take: 100, .take(100);
order: {
id: 1, if (cursor) {
}, query.andWhere('clipNote.id > :cursor', { cursor });
relations: ['note', 'note.user'], }
}) as (MiClipNote & { note: MiNote & { user: MiUser } })[];
this.queryService.generateVisibilityQuery(query, { id: userId });
const clipNotes = await query.getMany() as (MiClipNote & { note: MiNote & { user: MiUser } })[];
if (clipNotes.length === 0) { if (clipNotes.length === 0) {
break; break;
@@ -158,6 +161,11 @@ export class ExportClipsProcessorService {
cursor = clipNotes.at(-1)?.id ?? null; cursor = clipNotes.at(-1)?.id ?? null;
for (const clipNote of clipNotes) { 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; let poll: MiPoll | undefined;
if (clipNote.note.hasPoll) { if (clipNote.note.hasPoll) {
poll = await this.pollsRepository.findOneByOrFail({ noteId: clipNote.note.id }); poll = await this.pollsRepository.findOneByOrFail({ noteId: clipNote.note.id });

View File

@@ -5,7 +5,6 @@
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { MoreThan } from 'typeorm';
import { format as dateFormat } from 'date-fns'; import { format as dateFormat } from 'date-fns';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { MiNoteFavorite, NoteFavoritesRepository, PollsRepository, MiUser, UsersRepository } from '@/models/_.js'; import type { MiNoteFavorite, NoteFavoritesRepository, PollsRepository, MiUser, UsersRepository } from '@/models/_.js';
@@ -17,6 +16,8 @@ import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { NotificationService } from '@/core/NotificationService.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 { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq'; import type * as Bull from 'bullmq';
import type { DbJobDataWithUser } from '../types.js'; import type { DbJobDataWithUser } from '../types.js';
@@ -37,6 +38,7 @@ export class ExportFavoritesProcessorService {
private driveService: DriveService, private driveService: DriveService,
private queueLoggerService: QueueLoggerService, private queueLoggerService: QueueLoggerService,
private queryService: QueryService,
private idService: IdService, private idService: IdService,
private notificationService: NotificationService, private notificationService: NotificationService,
) { ) {
@@ -83,17 +85,20 @@ export class ExportFavoritesProcessorService {
}); });
while (true) { while (true) {
const favorites = await this.noteFavoritesRepository.find({ const query = this.noteFavoritesRepository.createQueryBuilder('favorite')
where: { .leftJoinAndSelect('favorite.note', 'note')
userId: user.id, .leftJoinAndSelect('note.user', 'user')
...(cursor ? { id: MoreThan(cursor) } : {}), .where('favorite.userId = :userId', { userId: user.id })
}, .orderBy('favorite.id', 'ASC')
take: 100, .take(100);
order: {
id: 1, if (cursor) {
}, query.andWhere('favorite.id > :cursor', { cursor });
relations: ['note', 'note.user'], }
}) as (MiNoteFavorite & { note: MiNote & { user: MiUser } })[];
this.queryService.generateVisibilityQuery(query, { id: user.id });
const favorites = await query.getMany() as (MiNoteFavorite & { note: MiNote & { user: MiUser } })[];
if (favorites.length === 0) { if (favorites.length === 0) {
job.updateProgress(100); job.updateProgress(100);
@@ -103,6 +108,11 @@ export class ExportFavoritesProcessorService {
cursor = favorites.at(-1)?.id ?? null; cursor = favorites.at(-1)?.id ?? null;
for (const favorite of favorites) { 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; let poll: MiPoll | undefined;
if (favorite.note.hasPoll) { if (favorite.note.hasPoll) {
poll = await this.pollsRepository.findOneByOrFail({ noteId: favorite.note.id }); poll = await this.pollsRepository.findOneByOrFail({ noteId: favorite.note.id });

View File

@@ -75,7 +75,7 @@ export class ServerService implements OnApplicationShutdown {
@bindThis @bindThis
public async launch(): Promise<void> { public async launch(): Promise<void> {
const fastify = Fastify({ const fastify = Fastify({
trustProxy: this.config.trustProxy ?? true, trustProxy: this.config.trustProxy ?? false,
logger: false, logger: false,
}); });
this.#fastify = fastify; this.#fastify = fastify;

View File

@@ -0,0 +1,2 @@
url: https://example.com/
port: 3000

View File

@@ -0,0 +1,29 @@
{
"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"
]
}

View File

@@ -1,22 +0,0 @@
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

View File

@@ -37,8 +37,8 @@ services:
- internal_network_a - internal_network_a
volumes: volumes:
- type: bind - type: bind
source: ./.config/a.test.default.yml source: ./.config/a.test.config.json
target: /misskey/.config/default.yml target: /misskey/built/._config_.json
read_only: true read_only: true
db.a.test: db.a.test:

View File

@@ -37,8 +37,8 @@ services:
- internal_network_b - internal_network_b
volumes: volumes:
- type: bind - type: bind
source: ./.config/b.test.default.yml source: ./.config/b.test.config.json
target: /misskey/.config/default.yml target: /misskey/built/._config_.json
read_only: true read_only: true
db.b.test: db.b.test:

View File

@@ -21,6 +21,10 @@ services:
- type: bind - type: bind
source: ../../../built source: ../../../built
target: /misskey/built target: /misskey/built
read_only: false
- type: bind
source: ./.config/dummy.yml
target: /misskey/.config/default.yml
read_only: true read_only: true
- type: bind - type: bind
source: ../assets source: ../assets
@@ -42,6 +46,10 @@ services:
source: ../package.json source: ../package.json
target: /misskey/packages/backend/package.json target: /misskey/packages/backend/package.json
read_only: true read_only: true
- type: bind
source: ../scripts/compile_config.js
target: /misskey/packages/backend/scripts/compile_config.js
read_only: true
- type: bind - type: bind
source: ../../misskey-js/built source: ../../misskey-js/built
target: /misskey/packages/misskey-js/built target: /misskey/packages/misskey-js/built

View File

@@ -54,6 +54,10 @@ services:
source: ../jest.js source: ../jest.js
target: /misskey/packages/backend/jest.js target: /misskey/packages/backend/jest.js
read_only: true read_only: true
- type: bind
source: ../scripts/compile_config.js
target: /misskey/packages/backend/scripts/compile_config.js
read_only: true
- type: bind - type: bind
source: ../../misskey-js/built source: ../../misskey-js/built
target: /misskey/packages/misskey-js/built target: /misskey/packages/misskey-js/built

View File

@@ -28,7 +28,7 @@ function generate {
-days 500 -days 500
if [ ! -f .config/docker.env ]; then cp .config/example.docker.env .config/docker.env; fi 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.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.default.yml > .config/$1.default.yml; fi if [ ! -f .config/$1.default.yml ]; then sed "s/\${HOST}/$1/g" .config/example.config.json > .config/$1.config.json; fi
} }
generate a.test generate a.test

View File

@@ -168,7 +168,36 @@ describe('export-clips', () => {
assert.strictEqual(exported[1].clipNotes[0].note.text, 'baz2'); assert.strictEqual(exported[1].clipNotes[0].note.text, 'baz2');
}); });
test('Clipping other user\'s note', async () => { 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);
const res = await api('clips/create', { const res = await api('clips/create', {
name: 'kawaii', name: 'kawaii',
description: 'kawaii', description: 'kawaii',

View File

@@ -0,0 +1,136 @@
/*
* 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);
});
});
});

View File

@@ -12,8 +12,8 @@
"devDependencies": { "devDependencies": {
"@types/estree": "1.0.8", "@types/estree": "1.0.8",
"@types/node": "24.10.1", "@types/node": "24.10.1",
"@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.47.0", "@typescript-eslint/parser": "8.48.0",
"rollup": "4.53.3", "rollup": "4.53.3",
"typescript": "5.9.3" "typescript": "5.9.3"
}, },

View File

@@ -17,7 +17,7 @@
"@rollup/pluginutils": "5.3.0", "@rollup/pluginutils": "5.3.0",
"@twemoji/parser": "16.0.0", "@twemoji/parser": "16.0.0",
"@vitejs/plugin-vue": "6.0.2", "@vitejs/plugin-vue": "6.0.2",
"@vue/compiler-sfc": "3.5.24", "@vue/compiler-sfc": "3.5.25",
"astring": "1.9.0", "astring": "1.9.0",
"buraha": "0.0.1", "buraha": "0.0.1",
"estree-walker": "3.0.3", "estree-walker": "3.0.3",
@@ -29,14 +29,14 @@
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"rollup": "4.53.3", "rollup": "4.53.3",
"sass": "1.94.2", "sass": "1.94.2",
"shiki": "3.15.0", "shiki": "3.17.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.16", "tsc-alias": "1.8.16",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.9.3", "typescript": "5.9.3",
"uuid": "13.0.0", "uuid": "13.0.0",
"vite": "7.2.4", "vite": "7.2.4",
"vue": "3.5.24" "vue": "3.5.25"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/summaly": "5.2.5", "@misskey-dev/summaly": "5.2.5",
@@ -48,21 +48,21 @@
"@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1", "@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.47.0", "@typescript-eslint/parser": "8.48.0",
"@vitest/coverage-v8": "4.0.13", "@vitest/coverage-v8": "4.0.14",
"@vue/runtime-core": "3.5.24", "@vue/runtime-core": "3.5.25",
"acorn": "8.15.0", "acorn": "8.15.0",
"cross-env": "10.1.0", "cross-env": "10.1.0",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",
"eslint-plugin-vue": "10.6.0", "eslint-plugin-vue": "10.6.2",
"fast-glob": "3.3.3", "fast-glob": "3.3.3",
"happy-dom": "20.0.10", "happy-dom": "20.0.11",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"msw": "2.12.2", "msw": "2.12.3",
"nodemon": "3.1.11", "nodemon": "3.1.11",
"prettier": "3.6.2", "prettier": "3.7.1",
"start-server-and-test": "2.1.3", "start-server-and-test": "2.1.3",
"tsx": "4.20.6", "tsx": "4.20.6",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",

View File

@@ -70,6 +70,8 @@
importAppScript(); importAppScript();
}); });
} }
localStorage.setItem('lang', lang);
//#endregion //#endregion
async function addStyle(styleText) { async function addStyle(styleText) {

View File

@@ -22,10 +22,10 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "24.10.1", "@types/node": "24.10.1",
"@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.47.0", "@typescript-eslint/parser": "8.48.0",
"esbuild": "0.27.0", "esbuild": "0.27.0",
"eslint-plugin-vue": "10.6.0", "eslint-plugin-vue": "10.6.2",
"nodemon": "3.1.11", "nodemon": "3.1.11",
"typescript": "5.9.3", "typescript": "5.9.3",
"vue-eslint-parser": "10.2.0" "vue-eslint-parser": "10.2.0"
@@ -36,6 +36,6 @@
"dependencies": { "dependencies": {
"i18n": "workspace:*", "i18n": "workspace:*",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"vue": "3.5.24" "vue": "3.5.25"
} }
} }

View File

@@ -25,12 +25,12 @@
"@rollup/plugin-json": "6.1.0", "@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.3", "@rollup/plugin-replace": "6.0.3",
"@rollup/pluginutils": "5.3.0", "@rollup/pluginutils": "5.3.0",
"@sentry/vue": "10.26.0", "@sentry/vue": "10.27.0",
"@syuilo/aiscript": "1.2.0", "@syuilo/aiscript": "1.2.0",
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0", "@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
"@twemoji/parser": "16.0.0", "@twemoji/parser": "16.0.0",
"@vitejs/plugin-vue": "6.0.2", "@vitejs/plugin-vue": "6.0.2",
"@vue/compiler-sfc": "3.5.24", "@vue/compiler-sfc": "3.5.25",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
"analytics": "0.8.19", "analytics": "0.8.19",
"astring": "1.9.0", "astring": "1.9.0",
@@ -59,7 +59,7 @@
"json5": "2.2.3", "json5": "2.2.3",
"magic-string": "0.30.21", "magic-string": "0.30.21",
"matter-js": "0.20.0", "matter-js": "0.20.0",
"mediabunny": "1.25.1", "mediabunny": "1.25.3",
"mfm-js": "0.25.0", "mfm-js": "0.25.0",
"misskey-bubble-game": "workspace:*", "misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
@@ -71,7 +71,7 @@
"rollup": "4.53.3", "rollup": "4.53.3",
"sanitize-html": "2.17.0", "sanitize-html": "2.17.0",
"sass": "1.94.2", "sass": "1.94.2",
"shiki": "3.15.0", "shiki": "3.17.0",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.181.2", "three": "0.181.2",
@@ -82,7 +82,7 @@
"typescript": "5.9.3", "typescript": "5.9.3",
"v-code-diff": "1.13.1", "v-code-diff": "1.13.1",
"vite": "7.2.4", "vite": "7.2.4",
"vue": "3.5.24", "vue": "3.5.25",
"vuedraggable": "next", "vuedraggable": "next",
"wanakana": "5.3.1" "wanakana": "5.3.1"
}, },
@@ -90,7 +90,7 @@
"@misskey-dev/summaly": "5.2.5", "@misskey-dev/summaly": "5.2.5",
"@storybook/addon-essentials": "8.6.14", "@storybook/addon-essentials": "8.6.14",
"@storybook/addon-interactions": "8.6.14", "@storybook/addon-interactions": "8.6.14",
"@storybook/addon-links": "10.0.8", "@storybook/addon-links": "10.1.0",
"@storybook/addon-mdx-gfm": "8.6.14", "@storybook/addon-mdx-gfm": "8.6.14",
"@storybook/addon-storysource": "8.6.14", "@storybook/addon-storysource": "8.6.14",
"@storybook/blocks": "8.6.14", "@storybook/blocks": "8.6.14",
@@ -98,13 +98,13 @@
"@storybook/core-events": "8.6.14", "@storybook/core-events": "8.6.14",
"@storybook/manager-api": "8.6.14", "@storybook/manager-api": "8.6.14",
"@storybook/preview-api": "8.6.14", "@storybook/preview-api": "8.6.14",
"@storybook/react": "10.0.8", "@storybook/react": "10.1.0",
"@storybook/react-vite": "10.0.8", "@storybook/react-vite": "10.1.0",
"@storybook/test": "8.6.14", "@storybook/test": "8.6.14",
"@storybook/theming": "8.6.14", "@storybook/theming": "8.6.14",
"@storybook/types": "8.6.14", "@storybook/types": "8.6.14",
"@storybook/vue3": "10.0.8", "@storybook/vue3": "10.1.0",
"@storybook/vue3-vite": "10.0.8", "@storybook/vue3-vite": "10.1.0",
"@tabler/icons-webfont": "3.35.0", "@tabler/icons-webfont": "3.35.0",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0", "@types/canvas-confetti": "1.9.0",
@@ -118,35 +118,35 @@
"@types/throttle-debounce": "5.0.2", "@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1", "@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.47.0", "@typescript-eslint/parser": "8.48.0",
"@vitest/coverage-v8": "4.0.13", "@vitest/coverage-v8": "4.0.14",
"@vue/compiler-core": "3.5.24", "@vue/compiler-core": "3.5.25",
"@vue/runtime-core": "3.5.24", "@vue/runtime-core": "3.5.25",
"acorn": "8.15.0", "acorn": "8.15.0",
"cross-env": "10.1.0", "cross-env": "10.1.0",
"cypress": "15.7.0", "cypress": "15.7.0",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",
"eslint-plugin-vue": "10.6.0", "eslint-plugin-vue": "10.6.2",
"fast-glob": "3.3.3", "fast-glob": "3.3.3",
"happy-dom": "20.0.10", "happy-dom": "20.0.11",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"minimatch": "10.1.1", "minimatch": "10.1.1",
"msw": "2.12.2", "msw": "2.12.3",
"msw-storybook-addon": "2.0.6", "msw-storybook-addon": "2.0.6",
"nodemon": "3.1.11", "nodemon": "3.1.11",
"prettier": "3.6.2", "prettier": "3.7.1",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"start-server-and-test": "2.1.3", "start-server-and-test": "2.1.3",
"storybook": "10.0.8", "storybook": "10.1.0",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"tsx": "4.20.6", "tsx": "4.20.6",
"vite-plugin-glsl": "1.5.4", "vite-plugin-glsl": "1.5.4",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vitest": "4.0.13", "vitest": "4.0.14",
"vitest-fetch-mock": "0.4.5", "vitest-fetch-mock": "0.4.5",
"vue-component-type-helpers": "3.1.5", "vue-component-type-helpers": "3.1.5",
"vue-eslint-parser": "10.2.0", "vue-eslint-parser": "10.2.0",

View File

@@ -42,6 +42,8 @@
console.error('invalid lang value detected!!!', typeof lang, lang); console.error('invalid lang value detected!!!', typeof lang, lang);
lang = 'en-US'; lang = 'en-US';
} }
localStorage.setItem('lang', lang);
//#endregion //#endregion
//#region Script //#region Script

View File

@@ -233,16 +233,18 @@ function showMenu(ev: MouseEvent) {
.hide { .hide {
display: block; display: block;
position: absolute; position: absolute;
border-radius: 6px; background-color: rgba(0, 0, 0, 0.3);
background-color: var(--MI_THEME-fg); -webkit-backdrop-filter: var(--MI-blur, blur(15px));
color: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); backdrop-filter: var(--MI-blur, blur(15px));
border-radius: 0 0 0 9px;
color: #fff;
font-size: 12px; font-size: 12px;
opacity: .5; opacity: .5;
padding: 5px 8px; padding: 5px 8px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
top: 12px; top: 0;
right: 12px; right: 0;
} }
.hiddenTextWrapper { .hiddenTextWrapper {
@@ -272,17 +274,17 @@ html[data-color-scheme=light] .visible {
.menu { .menu {
display: block; display: block;
position: absolute; position: absolute;
border-radius: 999px;
background-color: rgba(0, 0, 0, 0.3); background-color: rgba(0, 0, 0, 0.3);
-webkit-backdrop-filter: var(--MI-blur, blur(15px)); -webkit-backdrop-filter: var(--MI-blur, blur(15px));
backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px));
border-radius: 9px 0 0 0;
color: #fff; color: #fff;
font-size: 0.8em; font-size: 0.8em;
width: 28px; width: 28px;
height: 28px; height: 28px;
text-align: center; text-align: center;
bottom: 10px; bottom: 0;
right: 10px; right: 0;
} }
.imageContainer { .imageContainer {

View File

@@ -74,6 +74,7 @@ function mount() {
} }
function back() { function back() {
if (tabs.value.length <= 1) return; // transitionの関係でタブが1つの状態でbackが呼ばれることがある
const prev = tabs.value[tabs.value.length - 2]; const prev = tabs.value[tabs.value.length - 2];
tabs.value = [...tabs.value.slice(0, tabs.value.length - 1)]; tabs.value = [...tabs.value.slice(0, tabs.value.length - 1)];
router?.replaceByPath(prev.fullPath); router?.replaceByPath(prev.fullPath);

View File

@@ -13,8 +13,8 @@
"devDependencies": { "devDependencies": {
"@types/node": "24.10.1", "@types/node": "24.10.1",
"@types/wawoff2": "1.0.2", "@types/wawoff2": "1.0.2",
"@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.47.0" "@typescript-eslint/parser": "8.48.0"
}, },
"dependencies": { "dependencies": {
"@tabler/icons-webfont": "3.35.0", "@tabler/icons-webfont": "3.35.0",

View File

@@ -27,8 +27,8 @@
"@types/matter-js": "0.20.2", "@types/matter-js": "0.20.2",
"@types/node": "24.10.1", "@types/node": "24.10.1",
"@types/seedrandom": "3.0.8", "@types/seedrandom": "3.0.8",
"@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.47.0", "@typescript-eslint/parser": "8.48.0",
"esbuild": "0.27.0", "esbuild": "0.27.0",
"execa": "9.6.0", "execa": "9.6.0",
"glob": "11.1.0", "glob": "11.1.0",

View File

@@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "misskey-js", "name": "misskey-js",
"version": "2025.12.0-alpha.0", "version": "2025.12.0",
"description": "Misskey SDK for JavaScript", "description": "Misskey SDK for JavaScript",
"license": "MIT", "license": "MIT",
"main": "./built/index.js", "main": "./built/index.js",

View File

@@ -25,8 +25,8 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "24.10.1", "@types/node": "24.10.1",
"@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.47.0", "@typescript-eslint/parser": "8.48.0",
"esbuild": "0.27.0", "esbuild": "0.27.0",
"execa": "9.6.0", "execa": "9.6.0",
"glob": "11.1.0", "glob": "11.1.0",

View File

@@ -15,7 +15,7 @@
"misskey-js": "workspace:*" "misskey-js": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/parser": "8.47.0", "@typescript-eslint/parser": "8.48.0",
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74", "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",
"nodemon": "3.1.11", "nodemon": "3.1.11",

6
packages/sw/src/const.ts Normal file
View File

@@ -0,0 +1,6 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const FETCH_TIMEOUT_MS = 10000;

View File

@@ -8,6 +8,7 @@
*/ */
import { get, set } from 'idb-keyval'; import { get, set } from 'idb-keyval';
import { I18n } from '@@/js/i18n.js'; import { I18n } from '@@/js/i18n.js';
import { FETCH_TIMEOUT_MS } from '@/const.js';
import type { Locale } from 'i18n'; import type { Locale } from 'i18n';
class SwLang { class SwLang {
@@ -37,11 +38,21 @@ class SwLang {
// _DEV_がtrueの場合は常に最新化 // _DEV_がtrueの場合は常に最新化
if (!localeRes || _DEV_) { if (!localeRes || _DEV_) {
localeRes = await fetch(localeUrl); const controller = new AbortController();
const clone = localeRes.clone(); const timeout = globalThis.setTimeout(() => {
if (!clone.clone().ok) throw new Error('locale fetching error'); controller.abort('locale-fetch-timeout');
}, FETCH_TIMEOUT_MS);
caches.open(this.cacheName).then(cache => cache.put(localeUrl, clone)); 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);
}
} }
return new I18n<Locale>(await localeRes.json()); return new I18n<Locale>(await localeRes.json());

View File

@@ -5,6 +5,7 @@
import { get } from 'idb-keyval'; import { get } from 'idb-keyval';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { FETCH_TIMEOUT_MS } from '@/const.js';
import type { PushNotificationDataMap } from '@/types.js'; import type { PushNotificationDataMap } from '@/types.js';
import type { I18n } from '@@/js/i18n.js'; import type { I18n } from '@@/js/i18n.js';
import type { Locale } from 'i18n'; import type { Locale } from 'i18n';
@@ -12,6 +13,52 @@ import { createEmptyNotification, createNotification } from '@/scripts/create-no
import { swLang } from '@/scripts/lang.js'; import { swLang } from '@/scripts/lang.js';
import * as swos from '@/scripts/operations.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', () => { globalThis.addEventListener('install', () => {
// ev.waitUntil(globalThis.skipWaiting()); // ev.waitUntil(globalThis.skipWaiting());
}); });
@@ -28,17 +75,6 @@ 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 => { globalThis.addEventListener('fetch', ev => {
let isHTMLRequest = false; let isHTMLRequest = false;
if (ev.request.headers.get('sec-fetch-dest') === 'document') { if (ev.request.headers.get('sec-fetch-dest') === 'document') {
@@ -50,18 +86,7 @@ globalThis.addEventListener('fetch', ev => {
} }
if (!isHTMLRequest) return; if (!isHTMLRequest) return;
ev.respondWith( ev.respondWith(respondToNavigation(ev.request));
fetch(ev.request)
.catch(async () => {
const html = await offlineContentHTML();
return new Response(html, {
status: 200,
headers: {
'content-type': 'text/html',
},
});
}),
);
}); });
globalThis.addEventListener('push', ev => { globalThis.addEventListener('push', ev => {

2235
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@
"devDependencies": { "devDependencies": {
"@types/mdast": "4.0.4", "@types/mdast": "4.0.4",
"@types/node": "24.10.1", "@types/node": "24.10.1",
"@vitest/coverage-v8": "4.0.13", "@vitest/coverage-v8": "4.0.14",
"mdast-util-to-string": "4.0.0", "mdast-util-to-string": "4.0.0",
"remark": "15.0.1", "remark": "15.0.1",
"remark-parse": "11.0.0", "remark-parse": "11.0.0",
@@ -18,7 +18,7 @@
"unified": "11.0.5", "unified": "11.0.5",
"vite": "7.2.4", "vite": "7.2.4",
"vite-node": "5.2.0", "vite-node": "5.2.0",
"vitest": "4.0.13" "vitest": "4.0.14"
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
@@ -915,21 +915,21 @@
"dev": true "dev": true
}, },
"node_modules/@vitest/coverage-v8": { "node_modules/@vitest/coverage-v8": {
"version": "4.0.13", "version": "4.0.14",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.13.tgz", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.14.tgz",
"integrity": "sha512-w77N6bmtJ3CFnL/YHiYotwW/JI3oDlR3K38WEIqegRfdMSScaYxwYKB/0jSNpOTZzUjQkG8HHEz4sdWQMWpQ5g==", "integrity": "sha512-EYHLqN/BY6b47qHH7gtMxAg++saoGmsjWmAq9MlXxAz4M0NcHh9iOyKhBZyU4yxZqOd8Xnqp80/5saeitz4Cng==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@bcoe/v8-coverage": "^1.0.2", "@bcoe/v8-coverage": "^1.0.2",
"@vitest/utils": "4.0.13", "@vitest/utils": "4.0.14",
"ast-v8-to-istanbul": "^0.3.8", "ast-v8-to-istanbul": "^0.3.8",
"debug": "^4.4.3",
"istanbul-lib-coverage": "^3.2.2", "istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1", "istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^5.0.6", "istanbul-lib-source-maps": "^5.0.6",
"istanbul-reports": "^3.2.0", "istanbul-reports": "^3.2.0",
"magicast": "^0.5.1", "magicast": "^0.5.1",
"obug": "^2.1.1",
"std-env": "^3.10.0", "std-env": "^3.10.0",
"tinyrainbow": "^3.0.3" "tinyrainbow": "^3.0.3"
}, },
@@ -937,8 +937,8 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
}, },
"peerDependencies": { "peerDependencies": {
"@vitest/browser": "4.0.13", "@vitest/browser": "4.0.14",
"vitest": "4.0.13" "vitest": "4.0.14"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@vitest/browser": { "@vitest/browser": {
@@ -947,16 +947,16 @@
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "4.0.13", "version": "4.0.14",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.13.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.14.tgz",
"integrity": "sha512-zYtcnNIBm6yS7Gpr7nFTmq8ncowlMdOJkWLqYvhr/zweY6tFbDkDi8BPPOeHxEtK1rSI69H7Fd4+1sqvEGli6w==", "integrity": "sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@standard-schema/spec": "^1.0.0", "@standard-schema/spec": "^1.0.0",
"@types/chai": "^5.2.2", "@types/chai": "^5.2.2",
"@vitest/spy": "4.0.13", "@vitest/spy": "4.0.14",
"@vitest/utils": "4.0.13", "@vitest/utils": "4.0.14",
"chai": "^6.2.1", "chai": "^6.2.1",
"tinyrainbow": "^3.0.3" "tinyrainbow": "^3.0.3"
}, },
@@ -965,13 +965,13 @@
} }
}, },
"node_modules/@vitest/mocker": { "node_modules/@vitest/mocker": {
"version": "4.0.13", "version": "4.0.14",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.13.tgz", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.14.tgz",
"integrity": "sha512-eNCwzrI5djoauklwP1fuslHBjrbR8rqIVbvNlAnkq1OTa6XT+lX68mrtPirNM9TnR69XUPt4puBCx2Wexseylg==", "integrity": "sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "4.0.13", "@vitest/spy": "4.0.14",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"magic-string": "^0.30.21" "magic-string": "^0.30.21"
}, },
@@ -992,9 +992,9 @@
} }
}, },
"node_modules/@vitest/pretty-format": { "node_modules/@vitest/pretty-format": {
"version": "4.0.13", "version": "4.0.14",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.13.tgz", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz",
"integrity": "sha512-ooqfze8URWbI2ozOeLDMh8YZxWDpGXoeY3VOgcDnsUxN0jPyPWSUvjPQWqDGCBks+opWlN1E4oP1UYl3C/2EQA==", "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1005,13 +1005,13 @@
} }
}, },
"node_modules/@vitest/runner": { "node_modules/@vitest/runner": {
"version": "4.0.13", "version": "4.0.14",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.13.tgz", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.14.tgz",
"integrity": "sha512-9IKlAru58wcVaWy7hz6qWPb2QzJTKt+IOVKjAx5vb5rzEFPTL6H4/R9BMvjZ2ppkxKgTrFONEJFtzvnyEpiT+A==", "integrity": "sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/utils": "4.0.13", "@vitest/utils": "4.0.14",
"pathe": "^2.0.3" "pathe": "^2.0.3"
}, },
"funding": { "funding": {
@@ -1019,13 +1019,13 @@
} }
}, },
"node_modules/@vitest/snapshot": { "node_modules/@vitest/snapshot": {
"version": "4.0.13", "version": "4.0.14",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.13.tgz", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.14.tgz",
"integrity": "sha512-hb7Usvyika1huG6G6l191qu1urNPsq1iFc2hmdzQY3F5/rTgqQnwwplyf8zoYHkpt7H6rw5UfIw6i/3qf9oSxQ==", "integrity": "sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "4.0.13", "@vitest/pretty-format": "4.0.14",
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
"pathe": "^2.0.3" "pathe": "^2.0.3"
}, },
@@ -1034,9 +1034,9 @@
} }
}, },
"node_modules/@vitest/spy": { "node_modules/@vitest/spy": {
"version": "4.0.13", "version": "4.0.14",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.13.tgz", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.14.tgz",
"integrity": "sha512-hSu+m4se0lDV5yVIcNWqjuncrmBgwaXa2utFLIrBkQCQkt+pSwyZTPFQAZiiF/63j8jYa8uAeUZ3RSfcdWaYWw==", "integrity": "sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
@@ -1044,13 +1044,13 @@
} }
}, },
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "4.0.13", "version": "4.0.14",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.13.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz",
"integrity": "sha512-ydozWyQ4LZuu8rLp47xFUWis5VOKMdHjXCWhs1LuJsTNKww+pTHQNK4e0assIB9K80TxFyskENL6vCu3j34EYA==", "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "4.0.13", "@vitest/pretty-format": "4.0.14",
"tinyrainbow": "^3.0.3" "tinyrainbow": "^3.0.3"
}, },
"funding": { "funding": {
@@ -2439,24 +2439,24 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "4.0.13", "version": "4.0.14",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.13.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.14.tgz",
"integrity": "sha512-QSD4I0fN6uZQfftryIXuqvqgBxTvJ3ZNkF6RWECd82YGAYAfhcppBLFXzXJHQAAhVFyYEuFTrq6h0hQqjB7jIQ==", "integrity": "sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@vitest/expect": "4.0.13", "@vitest/expect": "4.0.14",
"@vitest/mocker": "4.0.13", "@vitest/mocker": "4.0.14",
"@vitest/pretty-format": "4.0.13", "@vitest/pretty-format": "4.0.14",
"@vitest/runner": "4.0.13", "@vitest/runner": "4.0.14",
"@vitest/snapshot": "4.0.13", "@vitest/snapshot": "4.0.14",
"@vitest/spy": "4.0.13", "@vitest/spy": "4.0.14",
"@vitest/utils": "4.0.13", "@vitest/utils": "4.0.14",
"debug": "^4.4.3",
"es-module-lexer": "^1.7.0", "es-module-lexer": "^1.7.0",
"expect-type": "^1.2.2", "expect-type": "^1.2.2",
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
"obug": "^2.1.1",
"pathe": "^2.0.3", "pathe": "^2.0.3",
"picomatch": "^4.0.3", "picomatch": "^4.0.3",
"std-env": "^3.10.0", "std-env": "^3.10.0",
@@ -2479,12 +2479,11 @@
"peerDependencies": { "peerDependencies": {
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0", "@opentelemetry/api": "^1.9.0",
"@types/debug": "^4.1.12",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.0.13", "@vitest/browser-playwright": "4.0.14",
"@vitest/browser-preview": "4.0.13", "@vitest/browser-preview": "4.0.14",
"@vitest/browser-webdriverio": "4.0.13", "@vitest/browser-webdriverio": "4.0.14",
"@vitest/ui": "4.0.13", "@vitest/ui": "4.0.14",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*" "jsdom": "*"
}, },
@@ -2495,9 +2494,6 @@
"@opentelemetry/api": { "@opentelemetry/api": {
"optional": true "optional": true
}, },
"@types/debug": {
"optional": true
},
"@types/node": { "@types/node": {
"optional": true "optional": true
}, },

View File

@@ -11,7 +11,7 @@
"devDependencies": { "devDependencies": {
"@types/mdast": "4.0.4", "@types/mdast": "4.0.4",
"@types/node": "24.10.1", "@types/node": "24.10.1",
"@vitest/coverage-v8": "4.0.13", "@vitest/coverage-v8": "4.0.14",
"mdast-util-to-string": "4.0.0", "mdast-util-to-string": "4.0.0",
"remark": "15.0.1", "remark": "15.0.1",
"remark-parse": "11.0.0", "remark-parse": "11.0.0",
@@ -19,6 +19,6 @@
"unified": "11.0.5", "unified": "11.0.5",
"vite": "7.2.4", "vite": "7.2.4",
"vite-node": "5.2.0", "vite-node": "5.2.0",
"vitest": "4.0.13" "vitest": "4.0.14"
} }
} }