Browse Source

feat(devcontainer): add devcontainer environment documentation and smoke testing workflow

Yuki Takei 1 week ago
parent
commit
92a13b3449
2 changed files with 139 additions and 0 deletions
  1. 39 0
      .claude/rules/devcontainer.md
  2. 100 0
      apps/app/.claude/skills/app-commands/SKILL.md

+ 39 - 0
.claude/rules/devcontainer.md

@@ -0,0 +1,39 @@
+# Devcontainer Environment
+
+## Service Connectivity
+
+This project runs inside a devcontainer defined in `.devcontainer/compose.yml`. The Docker Compose services are **always accessible by hostname** — do NOT run connectivity checks (`ping`, `nc`, `node net.connect`, etc.) before using them.
+
+| Service | Hostname | Port | Notes |
+|---------|----------|------|-------|
+| MongoDB | `mongo` | `27017` | Replica set `rs0`; required for transactions and change streams |
+| Elasticsearch | `elasticsearch` | `9200` | Full-text search |
+
+## MongoDB
+
+Connection string (already in `apps/app/.env.development`):
+```
+mongodb://mongo:27017/growi?replicaSet=rs0
+```
+
+`mongosh` is **not** installed in the devcontainer (`app` service). To run ad-hoc queries from the devcontainer, use the bundled MongoDB driver via Node.js:
+
+```bash
+node -e "
+const { MongoClient } = require('/workspace/growi-vault/node_modules/.pnpm/mongodb@6.8.0_@aws-sdk+credential-providers@3.600.0_@aws-sdk+client-sso-oidc@3.600.0__socks@2.8.3/node_modules/mongodb');
+async function main() {
+  const client = new MongoClient('mongodb://mongo:27017/growi?replicaSet=rs0');
+  await client.connect();
+  const db = client.db('growi');
+  // ... your query here ...
+  await client.close();
+}
+main().catch(console.error);
+"
+```
+
+## Smoke Testing the App
+
+The development server **can always be started** in the devcontainer for smoke and integration verification. Never claim the runtime environment is unavailable.
+
+See `apps/app/.claude/skills/app-commands/SKILL.md` → **Smoke Testing** section for the full workflow.

+ 100 - 0
apps/app/.claude/skills/app-commands/SKILL.md

@@ -187,6 +187,106 @@ Development uses `dotenv-flow`:
 
 See `.env.example` for available variables.
 
+## Smoke Testing
+
+The devcontainer always has MongoDB and other services running (see `.claude/rules/devcontainer.md`). The dev server **can and should** be started for smoke verification — never claim the runtime environment is unavailable.
+
+### Workflow
+
+**Step 1 — Override env vars without touching committed files**
+
+Create `apps/app/.env.development.local` (highest dotenv-flow priority; gitignored):
+
+```bash
+# Example: disable vault feature to test 404 behaviour
+cat > apps/app/.env.development.local << 'EOF'
+VAULT_ENABLED=false
+EOF
+```
+
+dotenv-flow load order (first definition wins):
+1. `.env.development.local` ← your override
+2. `.env.local`
+3. `.env.development` ← committed defaults
+4. `.env`
+
+> **Note:** nodemon watches `*.*` but does **not** reliably pick up dotfile changes (files starting with `.`). After editing `.env.development.local`, kill the ts-node process manually so nodemon restarts it with the new env:
+> ```bash
+> kill $(ss -tlnp | grep ':3000' | grep -o 'pid=[0-9]*' | cut -d= -f2)
+> ```
+
+**Step 2 — Start the dev server in background**
+
+```bash
+turbo run dev --filter @growi/app &
+```
+
+Wait for the ready message:
+```bash
+until curl -s http://localhost:3000/ > /dev/null 2>&1; do sleep 1; done
+echo "Server ready"
+```
+
+Or watch the log for `Express server is listening on port 3000`.
+
+**Step 3 — Curl the endpoints**
+
+```bash
+# Feature disabled → 404 (no Retry-After)
+curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/_vault/repo.git/info/refs?service=git-upload-pack
+
+# Push attempt → always 403
+curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:3000/_vault/repo.git/git-receive-pack
+
+# Check response body
+curl -s http://localhost:3000/_vault/repo.git/info/refs?service=git-upload-pack
+
+# Check specific headers
+curl -sI http://localhost:3000/_vault/repo.git/info/refs?service=git-upload-pack | grep -i retry-after
+```
+
+**Step 4 — Switch env and retest**
+
+Edit `.env.development.local`, then kill and wait for nodemon to restart:
+
+```bash
+echo "VAULT_ENABLED=true" > apps/app/.env.development.local
+kill $(ss -tlnp | grep ':3000' | grep -o 'pid=[0-9]*' | cut -d= -f2)
+until curl -s http://localhost:3000/ > /dev/null 2>&1; do sleep 1; done
+```
+
+**Step 5 — Manipulate MongoDB state if needed**
+
+```bash
+node -e "
+const { MongoClient } = require('/workspace/growi-vault/node_modules/.pnpm/mongodb@6.8.0_@aws-sdk+credential-providers@3.600.0_@aws-sdk+client-sso-oidc@3.600.0__socks@2.8.3/node_modules/mongodb');
+async function main() {
+  const client = new MongoClient('mongodb://mongo:27017/growi?replicaSet=rs0');
+  await client.connect();
+  // e.g. reset bootstrap state
+  await client.db('growi').collection('vault_sync_state').updateOne(
+    { _id: 'singleton' },
+    { \$set: { bootstrapState: 'pending' } },
+    { upsert: true }
+  );
+  await client.close();
+}
+main().catch(console.error);
+"
+```
+
+**Step 6 — Stop the server**
+
+```bash
+kill $(pgrep -f "nodemon|src/server/app.ts") 2>/dev/null
+```
+
+### What counts as a passing smoke test
+
+- The Express server starts without throwing on import (`Express server is listening on port 3000` in logs)
+- Feature-flag–gated endpoints return the correct status code for each flag state (404 when disabled, 503 with the right message when bootstrap incomplete, 403 for read-only enforcement)
+- No unhandled exception in server startup logs
+
 ## Troubleshooting
 
 ### Migration Issues