開発手順
作業ディレクトリ
すべての make コマンドはプロジェクトルートから実行する。
cd /path/to/glatasks
make test # OKapp/ に移動して実行すると ${PWD} がずれてMakefile内のパス解決が誤動作するため、必ずルートから実行する。
開発環境の構築手順
本リポジトリをcloneする
uv をインストールする
bashcurl -LsSf https://astral.sh/uv/install.sh | shpre-commitフックをインストールする
bashuvx pre-commit install起動する
bashmake deploy
make コマンド
make help で一覧を確認できる。よく使うコマンド:
make format— コード編集後に実行。整形 + 自動修正付きlintmake test— コミット前に実行。format + 型チェック + ユニットテスト + バックアップテスト + e2eの全検証make deploy— ビルド → 停止 → 起動
ツールチェイン
- Prettier, prettier-plugin-svelte, prettier-plugin-tailwindcss: コード整形
- ESLint: typescript-eslint, eslint-plugin-svelte
Biome移行における主要な阻害要因は以下のとおり。
- Svelteマークアップ非対応 — Biomeは
.svelteのマークアップ部分のフォーマットに対応していない。現在はprettier-plugin-svelteが全体を統一的に処理している - Tailwind CSSクラスソート非対応 — prettier-plugin-tailwindcssに相当する機能がBiomeに存在しない。当該機能はプロジェクト全体で使用している
Docker 構成
サービス構成・環境変数は compose.yaml / .env を参照。
開発環境での動作確認
開発環境はすべてDocker Compose上で動作する。ホストから直接 localhost:3000 にアクセスできない場合がある。
nginx経由で確認:
curl -k https://localhost:38180/healthcheckappコンテナ経由で確認:
docker compose exec app curl --fail http://localhost:3000/healthcheckmake healthcheck はホスト直接→コンテナ経由の順にフォールバックして確認する。
ユニットテスト
Vitestを使ったユニットテストは make node-shell でコンテナに入り、pnpm exec vitest run で実行できる。
make node-shell
pnpm exec vitest runmake test を実行すると型チェック + ユニットテスト + e2eがまとめて実行される(lintは make format で実行済み)。
テストコードは app/src/ 配下に *.test.ts として配置する(例: app/src/lib/crypto.test.ts)。
e2e テスト
Playwrightを使ったe2eテストを make test-e2e で実行できる。
make test-e2eテストコードは app/tests/ に配置する。 nginx経由のHTTPS(port 38180)でテストするため、開発環境が起動している必要がある。 テストユーザーは app/tests/global-setup.ts で初回自動作成される。
Playwright テスト実装の注意点
- SvelteKitのhydration完了を待つ:
waitForSelectorはSSRで描画されるため即返るが、onMountのAPI呼び出しはまだ完了していない。SSE接続が常時開いているためwaitUntil: "networkidle"は使えない。Promise.all([page.goto(url), page.waitForResponse(res => res.url().includes("/api/trpc"))])パターンを使うこと。 browser.newContext()を使う場合はbaseURLを明示する:page.goto("/")が動くようbaseURLを指定すること。- セレクタの曖昧さに注意:
button:has-text("追加")はサイドバーのリスト追加ボタンにも一致する。main button:has-text("追加")のようにスコープを限定すること。 - 複数ブラウザ(多端末同期)のテスト:
browser.newContext(...)を2つ作り、終了時にfinallyでctx.close()する。 引数にはstorageState/ignoreHTTPSErrors/baseURLを指定する。 値はapp/tests/.auth/user.json/true/process.env.BASE_URL ?? "https://localhost:38180"とする。 - SSEイベントを受信しない状態を再現する:
await ctx.route("**/api/events", route => route.abort())でSSEエンドポイントへの接続だけを遮断できる。/api/trpcは通るので削除等の通常操作は引き続き実行できる。
テスト設計の思想
make format と make test の2パターンを基本とする。
make format: 軽量な整形 + lint。コード編集後に日常的に実行する用途。 pre-commit hooks(prettier, eslint --fix, markdownlint, textlint)を実行make test: 全テスト。コミット前に実行する用途。 format → 型チェック → ユニットテスト → バックアップテスト → e2eテスト。formatでeslint --fix済みのためlintチェックは省略- CI(
pnpm run test): lint + 型チェック + ユニットテスト。CIではformatが先行しないためlintを含む
開発時の注意点
- JSONボディから受け取る数値は文字列の場合があるため
Number()で明示変換すること("5" !== 5の型不一致を防ぐ) - 日時は全レイヤーでUTC統一。DB(TIMESTAMP型)→ サーバー(Dateオブジェクト)→ クライアント(ISO8601文字列)の変換は自動で行われるため、 タイムゾーンを意識するコードは不要
サプライチェーン攻撃対策
npm / PyPIレジストリへの悪意あるパッケージ公開に対する防御として、公開から一定期間が経過したパッケージのみインストールを許可する設定を導入している。
pnpm: minimumReleaseAge
pnpm-workspace.yaml に minimumReleaseAge: 1440(1日 = 1440分)を設定。 npmレジストリに公開されてから1日未満のバージョンはインストールされない。pnpm dlx(pnpx)にも適用される(pnpm 10.18以降)。
docs/ はpnpm workspacesでルートと統合管理しているため、pnpm-workspace.yaml の設定が自動的に適用される。
緊急でリリース直後のパッケージが必要な場合は pnpm-workspace.yaml に一時的に追加する:
minimumReleaseAgeExclude:
- package-name@1.2.3uv: exclude-newer
uv.toml に exclude-newer = "1 day" を設定。PyPIに公開されてから1日未満のパッケージはインストールされない。uvx(uv tool run)にも適用される。
pre-commit のバージョン固定
.pre-commit-config.yaml の additional_dependencies で指定するnpmパッケージ(textlint等)は pre-commitが直接npm経由でインストールする。そのためpnpmの minimumReleaseAge は適用されない。代わりに @latest ではなくバージョンを固定している。
pnpmにおけるlockfile尊重
サプライチェーン攻撃対策の二重防御として、CI・Docker・makeから呼ばれるpnpm installはすべて--frozen-lockfileを明示している。 これによりpnpm-lock.yamlがpackage.jsonと乖離している場合に再resolveを許さずインストールを失敗させ、 ロックファイルが意図せず書き換わるリスクを抑えている。
Dockerfileのビルドステージcompose.yaml/compose.development.yamlのアプリ起動コマンドMakefileのRUN_NODE関数およびtest-e2eターゲット.github/workflows/ci.yaml/.github/workflows/docs.yamlのCIステップ
依存を更新するときはmake updateから呼ばれるpnpm update --latestを使う。pnpm updateは--frozen-lockfileの影響を受けないため、開発フローを阻害しない。
CI/CD
CI(ci.yaml)
masterへのpush・PR時に自動実行。lint + 型チェック + ユニットテスト(pnpm run test)を実行する。 CIでは make format が先行しないため、pnpm run test にlintを含めている。 e2eテストはCIでは実行しない(Docker Compose環境が必要なため)。
リリース→デプロイの流れ
sequenceDiagram
participant Dev as 開発者
participant R as release.yaml
participant T as ci.yaml
participant D as deploy.yaml
participant S as サーバー
Dev->>R: gh workflow run release.yaml
R->>T: ci.yaml の成功を確認
T-->>R: 成功
R->>R: タグ・リリース作成
R->>D: gh workflow run deploy.yaml
D->>D: Docker イメージビルド → GHCR プッシュ
D->>S: SSH で make sync && make backup && make deployrelease.yaml(手動実行): masterのci.yamlが成功していることを確認 → バージョンタグとリリースを作成 →gh workflow run deploy.yamlでデプロイを起動deploy.yaml(release.yamlから起動): DockerイメージをビルドしてGHCRにプッシュ → SSHでサーバーに接続しmake sync && make backup && make deployを実行
GitHub Actionsのデプロイ用SSHキー作成手順
鍵ペアを作成、サーバーに登録:
ssh-keygen -t ed25519 -C "github-action@GLATasks" -f github_action
ssh-copy-id -i github_action.pub ubuntu@aws.tqzh.tkGitHubに秘密鍵を登録:
- リポジトリ → Settings → Secrets and variables → Actions → New repository secret
- Name:
SSH_PRIVATE_KEY - Value:
cat github_actionの出力
- Name:
後始末:
\rm github_action github_action.pubバックアップとリストア
バックアップ
デプロイ前にDBダンプとキーファイルのバックアップを取得する。CIデプロイ(deploy.yaml)では make deploy の前に自動実行される。
make backupバックアップ先: ${DATA_DIR}/backups/YYYYMMDD_HHMMSS/(DBダンプ + キーファイル)
デフォルトで直近5世代を保持する。BACKUP_KEEP 環境変数で変更可能:
BACKUP_KEEP=10 make backupDBコンテナが停止中の場合はエラー終了する。初回デプロイなどDBがない状態では SKIP_DB_DUMP=1 でスキップ可能:
SKIP_DB_DUMP=1 make backupリストア
# DB 復元
docker compose exec -T db mariadb -uglatasks -pglatasks glatasks < ${DATA_DIR}/backups/YYYYMMDD_HHMMSS/glatasks.sql
# キーファイル復元
cp -p ${DATA_DIR}/backups/YYYYMMDD_HHMMSS/.encrypt_key ${DATA_DIR}/
cp -p ${DATA_DIR}/backups/YYYYMMDD_HHMMSS/.secret_key ${DATA_DIR}/
# app 再起動(キーファイルを反映)
make restart-appリリース手順
事前にghコマンドをインストールしてgh auth loginでログインしておき、以下のコマンドのいずれかを実行。
gh workflow run release.yaml --field="bump=バグフィックス"
gh workflow run release.yaml --field="bump=マイナーバージョンアップ"
gh workflow run release.yaml --field="bump=メジャーバージョンアップ"https://github.com/ak110/GLATasks/actions で状況を確認できる。
リリース作成後、deploy.yamlが自動的にトリガーされ本番デプロイが実行される。詳細は CI/CD セクションを参照。
READMEとdocsの役割分担
本プロジェクトのドキュメントは以下の構成で配置している。
- README.md: 概要・特徴・ドキュメントへのリンクを網羅する「玄関」。README.mdだけを読めばプロジェクトの目的と使い始めるための入口が把握できる状態を保つ
- docs/guide/: 利用者向けの詳細情報(使い方・導入手順など)
- docs/development/: 開発者向けの情報(セットアップ・テスト・CI/CD・リリース手順など)
本プロジェクトはWebアプリのため、インストール手順はREADMEには置かずdocs/guide/getting-started.mdに集約している。
README.mdとdocs側で概要・特徴が部分的に重複する場合があるが、README.mdはGitHubトップとして、docs側は公開ドキュメントの入口としてそれぞれ自己完結する必要があるため、この重複は許容する。
変更頻度が低いため二重管理のコストより一貫性・可読性のメリットが上回ると判断した。変更時は、docs側で同じ情報を再掲している箇所があれば同じコミット内で合わせて更新する。
コミットメッセージ (Conventional Commits)
Conventional Commits形式に従う。ただし記述の方向性があまり変わらないような軽微な修正はchoreなどにしてよい。
ドキュメントサイト
VitePress を使用。 docs/ ディレクトリ直下のMarkdownファイルがページ、docs/.vitepress/config.ts でサイト設定を管理する。
ローカルプレビュー
make docshttp://localhost:5173/GLATasks/ でプレビューできる。
デプロイ
masterへのpush時に docs/ 配下の変更があれば docs.yaml ワークフローが自動実行され、GitHub Pagesにデプロイされる。
GitHub Pages の初期設定
GitHub PagesのソースをGitHub Actionsに設定する:
gh api repos/ak110/GLATasks/pages -X POST -f build_type=workflow