Add: OGPバナー画像自動生成 (node-canvas) + 全記事一括生成
All checks were successful
Deploy Docusaurus Site / deploy (push) Successful in 28s

This commit is contained in:
koide 2026-02-28 01:03:39 +00:00
parent cd5a2316ed
commit 8c109a1d9a
59 changed files with 554 additions and 3 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -4,6 +4,7 @@ title: ローカルサーバーでマイク・カメラを使う方法
description: HTTP + IPアドレスでブラウザのマイク・カメラが動かない問題の解決策
hide_table_of_contents: false
displayed_sidebar: null
image: ./banner.png
---
# ローカルサーバーでマイク・カメラを使う方法

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -4,6 +4,7 @@ title: DGX SparkにAnythingLLMを導入してローカルLLMエージェント
description: Docker + Ollama + AnythingLLMで完全ローカルなLLMエージェント環境を構築する方法
hide_table_of_contents: false
displayed_sidebar: null
image: ./banner.png
---
# DGX Spark に AnythingLLM を導入してローカルLLMエージェントを構築する

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -4,6 +4,7 @@ title: DGX SparkでClaude Code + Qwen3-Coder-Nextをローカル実行する
description: Claude Codeを完全ローカル化Qwen3-Coder-Next80B MoEで動かす方法を解説
hide_table_of_contents: false
displayed_sidebar: null
image: ./banner.png
---
# DGX SparkでClaude Codeを完全ローカル化Qwen3-Coder-Nextで動かしてみた

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View File

@ -4,6 +4,7 @@ title: ローカルClaude Code + Playwright CLIでブラウザ自動化エージ
description: Claude Code + Playwright CLIで安定したブラウザ自動化環境を構築する方法
hide_table_of_contents: false
displayed_sidebar: null
image: ./banner.png
---
# ローカルClaude Code + Playwright CLIで「自分だけのブラウザ自動化エージェント」を作る

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -4,6 +4,7 @@ displayed_sidebar: null
sidebar_position: 3
title: DGX Spark デュアル構成ガイド
description: 2台のDGX Sparkを接続して256GBメモリ環境を構築する方法を初心者向けに解説
image: ./banner.png
---
# DGX Spark デュアル構成ガイド

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -4,6 +4,7 @@ title: DGX SparkでMiniMax-M2.5-REAP-172Bを動かす
description: DGX Sparkデュアル構成で172Bパラメータの最新LLMを動かす手順
hide_table_of_contents: false
displayed_sidebar: null
image: ./banner.png
---
## MiniMax-M2.5-REAP-172Bとは

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -4,6 +4,7 @@ title: DGX SparkでQwen3-Coder-Next80B MoEを動かす
description: NVIDIA DGX Sparkの128GB統合メモリでQwen3-Coder-Next80B MoEをFP8量子化で動かす方法を解説
hide_table_of_contents: false
displayed_sidebar: null
image: ./banner.png
---
# DGX SparkでQwen3-Coder-Nextを動かす

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -4,6 +4,7 @@ title: DGX SparkでVibeVoice ASRを動かす — リアルタイム日本語音
description: Microsoft VibeVoice ASRをDGX Spark環境でDockerベースで動かし、バッチ処理とリアルタイム音声認識を実現する方法
hide_table_of_contents: false
displayed_sidebar: null
image: ./banner.png
---
# DGX Spark で VibeVoice ASR を動かす — リアルタイム日本語音声認識

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -4,6 +4,7 @@ title: DGX SparkでGUI操作動画解析システムを作る
description: NVIDIA DGX SparkでQwen3-VLとLanceDBを使ったGUI操作動画解析システムの構築方法
hide_table_of_contents: false
displayed_sidebar: null
image: ./banner.png
---
# DGX Sparkで作るGUI操作動画解析システム

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -4,6 +4,7 @@ title: ローカルGitea × Webhook連携で、AIとの開発をもっと楽に
description: GiteaのIssue/PR WebhookとOpenClawを連携し、実運用で壊れにくく回す開発プロセスを構築する実践記録
hide_table_of_contents: false
displayed_sidebar: null
image: ./banner.png
---
# ローカルGitea × Webhook連携で、AIとの開発をもっと楽にしよう

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -4,6 +4,7 @@ title: SearXNGでローカル検索APIを構築する
description: プライバシー重視のメタ検索エンジンSearXNGをセルフホストして、無料で検索APIを手に入れる方法
hide_table_of_contents: false
displayed_sidebar: null
image: ./banner.png
---
# SearXNGでローカル検索APIを構築する

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/20 AIヘッドライン朝刊
description: 2026年2月20日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/20 AIヘッドライン朝刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/20 AIヘッドライン夕刊
description: 2026年2月20日のAI関連ニュースまとめ - GPT-OSS Swallow、Rork Max AI、LavaSRなど
image: ./banner.png
---
# 02/20 AIヘッドライン夕刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/21 AIヘッドライン夕刊
description: 2026年2月21日夕方のAI関連ニュースまとめ - Claude Code Security、Taalas HC1チップ、cmux、tornado
image: ./banner.png
---
# 02/21 AIヘッドライン夕刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/21 AIヘッドライン朝刊
description: 2026年2月21日のAI関連ニュースまとめ - llmfit、UltraRAG 3.0、Zvec、AudioX-MAF-MMDiT、YCエージェント経済
image: ./banner.png
---
# 02/21 AIヘッドライン朝刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/22 AIヘッドライン夕刊
description: 2026年2月22日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/22 AIヘッドライン夕刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/23 AIヘッドライン夕刊
description: 2026年2月23日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/23 AIヘッドライン夕刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/23 AIヘッドライン朝刊
description: 2026年2月23日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/23 AIヘッドライン朝刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/24 AIヘッドライン夕刊
description: 2026年2月24日夕刻のAI関連ニュースまとめ
image: ./banner.png
---
# 02/24 AIヘッドライン夕刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/24 AIヘッドライン朝刊
description: 2026年2月24日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/24 AIヘッドライン朝刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/25 AIヘッドライン夕刊
description: 2026年2月25日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/25 AIヘッドライン夕刊
@ -112,4 +113,4 @@ AI時代のクリエイティブ価値の根本的な変化を示唆。アート
---
*情報は2026年2月25日時点のものです。*
*情報は2026年2月25日時点のものです。*

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/25 AIヘッドライン朝刊
description: 2026年2月25日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/25 AIヘッドライン朝刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/26 AIヘッドライン夕刊
description: 2026年2月26日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/26 AIヘッドライン夕刊
@ -118,4 +119,4 @@ AIXAI Xプロジェクトの詳細
---
*情報は2026年02月26日時点のものです。*
*情報は2026年02月26日時点のものです。*

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/26 AIヘッドライン朝刊
description: 2026年2月26日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/26 AIヘッドライン朝刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/27 AIヘッドライン夕刊
description: 2026年2月27日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/27 AIヘッドライン夕刊
@ -88,4 +89,4 @@ GGUF形式での効率的な推論が可能で、関数呼び出しとツール
---
*情報は2026年02月27日時点のものです。*
*情報は2026年02月27日時点のものです。*

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/27 AIヘッドライン朝刊
description: 2026年2月27日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/27 AIヘッドライン朝刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -2,6 +2,7 @@
sidebar_position: 100
title: 02/28 AIヘッドライン朝刊
description: 2026年2月28日のAI関連ニュースまとめ
image: ./banner.png
---
# 02/28 AIヘッドライン朝刊

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -1,6 +1,7 @@
---
sidebar_position: 2
title: Ollama がローカルAIのハブとしてめちゃ最強な件
image: ./banner.png
---
# Ollama がローカルAIのハブとしてめちゃ最強な件

303
package-lock.json generated
View File

@ -11,7 +11,9 @@
"@docusaurus/core": "3.9.2",
"@docusaurus/preset-classic": "3.9.2",
"@mdx-js/react": "^3.0.0",
"canvas": "^3.2.1",
"clsx": "^2.0.0",
"gray-matter": "^4.0.3",
"prism-react-renderer": "^2.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
@ -6210,6 +6212,26 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.9.19",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
@ -6246,6 +6268,17 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/body-parser": {
"version": "1.20.4",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
@ -6387,6 +6420,30 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@ -6563,6 +6620,20 @@
],
"license": "CC-BY-4.0"
},
"node_modules/canvas": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.1.tgz",
"integrity": "sha512-ej1sPFR5+0YWtaVp6S1N1FVz69TQCqmrkGeRvQxZeAB1nAIcjNTHVwrZtYtWFFBmQsF40/uDLehsW5KuYC99mg==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-addon-api": "^7.0.0",
"prebuild-install": "^7.1.3"
},
"engines": {
"node": "^18.12.0 || >= 20.9.0"
}
},
"node_modules/ccount": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
@ -6700,6 +6771,12 @@
"fsevents": "~2.3.2"
}
},
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
"node_modules/chrome-trace-event": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
@ -7841,6 +7918,15 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/detect-node": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
@ -8077,6 +8163,15 @@
"node": ">= 0.8"
}
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/enhanced-resolve": {
"version": "5.19.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
@ -8457,6 +8552,15 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"license": "(MIT OR WTFPL)",
"engines": {
"node": ">=6"
}
},
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
@ -8900,6 +9004,12 @@
"node": ">= 0.6"
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/fs-extra": {
"version": "11.3.3",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
@ -9001,6 +9111,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
"node_modules/github-slugger": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz",
@ -9770,6 +9886,26 @@
"postcss": "^8.1.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -12964,6 +13100,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
"node_modules/mrmime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@ -13010,6 +13152,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
@ -13035,6 +13183,24 @@
"tslib": "^2.0.3"
}
},
"node_modules/node-abi": {
"version": "3.87.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
"integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"license": "MIT"
},
"node_modules/node-emoji": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz",
@ -13253,6 +13419,15 @@
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
@ -15108,6 +15283,33 @@
"postcss": "^8.4.31"
}
},
"node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^2.0.0",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/pretty-error": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
@ -15217,6 +15419,16 @@
"node": ">= 0.10"
}
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -16596,6 +16808,51 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/sirv": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
@ -17036,6 +17293,34 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/terser": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz",
@ -17256,6 +17541,18 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"license": "0BSD"
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/type-fest": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
@ -18336,6 +18633,12 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/write-file-atomic": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",

View File

@ -18,7 +18,9 @@
"@docusaurus/core": "3.9.2",
"@docusaurus/preset-classic": "3.9.2",
"@mdx-js/react": "^3.0.0",
"canvas": "^3.2.1",
"clsx": "^2.0.0",
"gray-matter": "^4.0.3",
"prism-react-renderer": "^2.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"

218
scripts/generate-banner.js Normal file
View File

@ -0,0 +1,218 @@
#!/usr/bin/env node
// Banner image generator for docs.techswan.online
// Usage:
// node scripts/generate-banner.js <slug> # single article
// node scripts/generate-banner.js --all # all missing
// node scripts/generate-banner.js --all --force # regenerate all
const { createCanvas, registerFont } = require('canvas');
const matter = require('gray-matter');
const fs = require('fs');
const path = require('path');
// --- Config ---
const W = 1200, H = 630;
const MAX_TITLE_WIDTH = 1000;
const FONT_SIZES = [52, 44, 38];
const MAX_LINES = 3;
const FONT_PATH = '/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc';
if (fs.existsSync(FONT_PATH)) {
registerFont(FONT_PATH, { family: 'Noto Sans CJK JP', weight: 'bold' });
}
const FONT_REGULAR = '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc';
if (fs.existsSync(FONT_REGULAR)) {
registerFont(FONT_REGULAR, { family: 'Noto Sans CJK JP', weight: 'normal' });
}
const CATEGORIES = {
'docs': { label: 'Blog', color: '#0ea5e9' },
'docs-tech': { label: 'Tech', color: '#10b981' },
};
const ROOT = path.resolve(__dirname, '..');
// --- Text wrapping ---
function tokenize(text) {
// Split into tokens: consecutive ASCII word chars as one token, each CJK char as one token
const tokens = [];
const re = /([A-Za-z0-9._\-]+)|(\s+)|([\s\S])/g;
let m;
while ((m = re.exec(text)) !== null) {
if (m[1]) tokens.push({ text: m[1], breakable: false });
else if (m[2]) tokens.push({ text: ' ', breakable: true });
else if (m[3]) tokens.push({ text: m[3], breakable: true });
}
return tokens;
}
function wrapText(ctx, text, maxWidth, fontSize, fontFamily) {
ctx.font = `bold ${fontSize}px "${fontFamily}", sans-serif`;
const tokens = tokenize(text);
const lines = [];
let currentLine = '';
for (const token of tokens) {
if (token.text === ' ' && currentLine === '') continue; // skip leading space
const testLine = currentLine + token.text;
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && currentLine !== '') {
lines.push(currentLine);
currentLine = token.breakable && token.text === ' ' ? '' : token.text;
} else {
currentLine = testLine;
}
}
if (currentLine) lines.push(currentLine);
return lines;
}
// --- Drawing ---
function drawBanner(title, category) {
const canvas = createCanvas(W, H);
const ctx = canvas.getContext('2d');
const cat = CATEGORIES[category] || CATEGORIES['docs'];
// Background gradient
const grad = ctx.createLinearGradient(0, 0, W, H);
grad.addColorStop(0, '#1a1a2e');
grad.addColorStop(1, '#16213e');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, W, H);
// Decorative circles
ctx.fillStyle = 'rgba(233,69,96,0.15)';
ctx.beginPath(); ctx.arc(1100, 100, 150, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = 'rgba(14,165,233,0.1)';
ctx.beginPath(); ctx.arc(1050, 500, 200, 0, Math.PI * 2); ctx.fill();
// Accent line
ctx.fillStyle = '#e94560';
ctx.fillRect(60, 80, 6, 120);
// Title - try font sizes until it fits
ctx.fillStyle = '#ffffff';
ctx.textBaseline = 'top';
let lines, fontSize;
for (fontSize of FONT_SIZES) {
lines = wrapText(ctx, title, MAX_TITLE_WIDTH, fontSize, 'Noto Sans CJK JP');
if (lines.length <= MAX_LINES) break;
}
// If still too many lines, truncate
if (lines.length > MAX_LINES) {
lines = lines.slice(0, MAX_LINES);
lines[MAX_LINES - 1] = lines[MAX_LINES - 1].replace(/.$/, '…');
}
const lineHeight = Math.round(fontSize * 1.35);
const titleStartY = 90;
ctx.font = `bold ${fontSize}px "Noto Sans CJK JP", sans-serif`;
lines.forEach((line, i) => {
ctx.fillText(line, 90, titleStartY + i * lineHeight);
});
// Category badge
const badgeY = Math.max(titleStartY + lines.length * lineHeight + 40, 350);
ctx.fillStyle = cat.color;
ctx.beginPath();
ctx.roundRect(60, badgeY, 100, 36, 8);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 18px "Noto Sans CJK JP", sans-serif';
ctx.textBaseline = 'middle';
ctx.fillText(cat.label, 60 + 50 - ctx.measureText(cat.label).width / 2, badgeY + 18);
// Footer
ctx.textBaseline = 'top';
ctx.fillStyle = 'rgba(255,255,255,0.6)';
ctx.font = '20px "Noto Sans CJK JP", sans-serif';
ctx.fillText('雑記 by swallow', 60, 550);
ctx.fillStyle = 'rgba(255,255,255,0.3)';
const siteText = 'docs.techswan.online';
ctx.fillText(siteText, W - 60 - ctx.measureText(siteText).width, 550);
return canvas.toBuffer('image/png');
}
// --- Article discovery ---
function findArticle(slug) {
for (const dir of ['docs-tech', 'docs']) {
const mdPath = path.join(ROOT, dir, slug, 'index.md');
if (fs.existsSync(mdPath)) return { mdPath, category: dir };
}
return null;
}
function getAllArticles() {
const articles = [];
for (const dir of ['docs-tech', 'docs']) {
const dirPath = path.join(ROOT, dir);
if (!fs.existsSync(dirPath)) continue;
for (const slug of fs.readdirSync(dirPath)) {
const mdPath = path.join(dirPath, slug, 'index.md');
if (fs.existsSync(mdPath)) {
articles.push({ slug, mdPath, category: dir });
}
}
}
return articles;
}
// --- Frontmatter update ---
function ensureFrontmatterImage(mdPath) {
const raw = fs.readFileSync(mdPath, 'utf-8');
const parsed = matter(raw);
if (parsed.data.image) return false; // already set
parsed.data.image = './banner.png';
const updated = matter.stringify(parsed.content, parsed.data);
fs.writeFileSync(mdPath, updated);
return true;
}
// --- Main ---
function generateOne(slug, force = false) {
const article = findArticle(slug);
if (!article) {
console.error(`❌ Article not found: ${slug}`);
return false;
}
const { mdPath, category } = article;
const bannerPath = path.join(path.dirname(mdPath), 'banner.png');
if (fs.existsSync(bannerPath) && !force) {
console.log(`⏭️ Skip (exists): ${slug}`);
return true;
}
const parsed = matter(fs.readFileSync(mdPath, 'utf-8'));
const title = parsed.data.title || slug;
const buf = drawBanner(title, category);
fs.writeFileSync(bannerPath, buf);
const updated = ensureFrontmatterImage(mdPath);
console.log(`${slug} → banner.png (${(buf.length / 1024).toFixed(0)}KB)${updated ? ' + frontmatter updated' : ''}`);
return true;
}
// --- CLI ---
const args = process.argv.slice(2);
const force = args.includes('--force');
const all = args.includes('--all');
if (all) {
const articles = getAllArticles();
console.log(`Found ${articles.length} articles`);
let generated = 0;
for (const { slug } of articles) {
if (generateOne(slug, force)) generated++;
}
console.log(`\nDone: ${generated}/${articles.length}`);
} else {
const slug = args.find(a => !a.startsWith('--'));
if (!slug) {
console.log('Usage: node scripts/generate-banner.js <slug> | --all [--force]');
process.exit(1);
}
generateOne(slug, force);
}