AP

app-commands

Provides direct access to GROWI-specific build, lint, and migration commands.

Install

mkdir -p .claude/skills/app-commands && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/3360" && unzip -o skill.zip -d .claude/skills/app-commands && rm skill.zip

Installs to .claude/skills/app-commands

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

GROWI main application (apps/app) specific commands and scripts. Auto-invoked when working in apps/app.
103 chars✓ has a “when” trigger
Beginner

Key capabilities

  • Executes package-specific linting and typechecking for the main app
  • Triggers database migration status checks and rollbacks
  • Runs visual regression tests in isolated environments
  • Invokes local console REPLs for administrative tasks

How it works

Routes commands to either Turborepo for cached build tasks or local scripts for app-specific operations.

Inputs & outputs

You give it
Maintenance task command or file path
You get back
Execution of specific maintenance or build scripts

When to use app-commands

  • Running database migrations
  • Executing typechecks and linting
  • Triggering build and test cycles

About this skill

App Commands (apps/app)

Commands specific to the main GROWI application. For global commands (turbo, pnpm), see the global tech-stack skill.

Quality Check Commands

IMPORTANT: Distinguish between Turborepo tasks and package-specific scripts.

Turbo Tasks vs Package Scripts

TaskTurborepo (turbo.json)Package Script (package.json)
lint✅ Yes✅ Yes (runs all lint:*)
test✅ Yes✅ Yes
build✅ Yes✅ Yes
lint:typecheck❌ No✅ Yes
lint:biome❌ No✅ Yes
lint:styles❌ No✅ Yes

Recommended Commands

# Run ALL quality checks (uses Turborepo caching)
turbo run lint --filter @growi/app
turbo run test --filter @growi/app
turbo run build --filter @growi/app

# Run INDIVIDUAL lint checks (package-specific scripts, from apps/app directory)
pnpm run lint:typecheck   # TypeScript only
pnpm run lint:biome       # Biome only
pnpm run lint:styles      # Stylelint only

Running individual test files: See the testing rule (.claude/rules/testing.md).

Quick Reference

TaskCommand
Migrationpnpm run dev:migrate
OpenAPI generatepnpm run openapi:generate-spec:apiv3
REPL consolepnpm run console
Visual regressionpnpm run reg:run
Version bumppnpm run version:patch

Database Migration

# Run pending migrations
pnpm run dev:migrate

# Check migration status
pnpm run dev:migrate:status

# Apply migrations
pnpm run dev:migrate:up

# Rollback last migration
pnpm run dev:migrate:down

# Production migration
pnpm run migrate

Note: Migrations use migrate-mongo. Files are in config/migrate-mongo/.

Creating a New Migration

# Create migration file manually in config/migrate-mongo/
# Format: YYYYMMDDHHMMSS-migration-name.js

# Test migration cycle
pnpm run dev:migrate:up
pnpm run dev:migrate:down
pnpm run dev:migrate:up

OpenAPI Commands

# Generate OpenAPI spec for API v3
pnpm run openapi:generate-spec:apiv3

# Validate API v3 spec
pnpm run lint:openapi:apiv3

# Generate operation IDs
pnpm run openapi:build:generate-operation-ids

Generated specs output to tmp/openapi-spec-apiv3.json.

Style Pre-build (Vite)

# Development mode
pnpm run dev:pre:styles-commons
pnpm run dev:pre:styles-components

# Production mode
pnpm run pre:styles-commons
pnpm run pre:styles-commons-components

Pre-builds SCSS styles into CSS bundles using Vite.

Debug & Utility

REPL Console

pnpm run console
# or
pnpm run repl

Interactive Node.js REPL with Mongoose models loaded. Useful for debugging database queries.

Visual Regression Testing

pnpm run reg:run

Version Commands

# Bump patch version (e.g., 7.4.3 → 7.4.4)
pnpm run version:patch

# Create prerelease (e.g., 7.4.4 → 7.4.5-RC.0)
pnpm run version:prerelease

# Create preminor (e.g., 7.4.4 → 7.5.0-RC.0)
pnpm run version:preminor

Build Measurement

# Measure module count KPI (cleans .next, starts next dev, triggers compilation)
./bin/measure-chunk-stats.sh           # default port 3099
./bin/measure-chunk-stats.sh 3001      # custom port

Output: [ChunkModuleStats] initial: N, async-only: N, total: N

For details on module optimization and baselines, see the build-optimization skill.

Production

# Start server (after build)
pnpm run server

# Start for CI environments
pnpm run server:ci

Note: preserver hook automatically runs migrations before starting.

CI/CD

# Launch dev server for CI
pnpm run launch-dev:ci

# Start production server for CI
pnpm run server:ci

Environment Variables

Development uses dotenv-flow:

  • .env - Default values
  • .env.local - Local overrides (not committed)
  • .env.development - Development-specific
  • .env.production - Production-specific

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):

# 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 server process manually so nodemon restarts it with the new env:

kill $(ss -tlnp | grep ':3000' | grep -o 'pid=[0-9]*' | cut -d= -f2)

Step 2 — Start the dev server in background

turbo run dev --filter @growi/app &

Wait for the ready message:

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

# 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:

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

node -e "
const { MongoClient } = require('/workspace/growi-vault/node_modules/.pnpm/[email protected]_@[email protected]_@[email protected][email protected]/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

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

Authorization Regression Check

Three capture tools freeze the apiv3 authorization surface so a refactor can be proven not to have moved it. Run them when a change touches middleware order, route registration, the auth chain, or after a large merge — a dropped guard is invisible to build, lint and unit tests. Baselines are committed under tools/authz-matrix/baselines/.

cd apps/app                        # requires MongoDB (devcontainer) and a free port 3000
pnpm run authz:capture-routes      # structural: (method, path, middlewareNames[]) per apiv3 leaf
pnpm run authz:capture-matrix      # black-box: HTTP status per endpoint × 4 personas
pnpm run authz:capture-ws          # WebSocket: /yjs + socket.io, 3 session cases each

Each writes to its default baseline path under tools/authz-matrix/baselines/; pass -- --out=<path> to write elsewhere (authz:capture-matrix also takes -- --in=<path> for the structural snapshot it derives its endpoint list from).

How to use it: capture on the base commit, apply your change, re-capture, and git diff the baseline files. Any difference inside the entries / matrix arrays is a potential authorization change and must be explained; the envelope metadata (capturedAt, git, node) changes on every run and is not signal. Adding -- --verify-determinism re-runs a capture twice and asserts the output is stable — do that before trusting a diff.

Properties worth knowing:

  • The structural walker fails if any middleware layer is anonymous, because an unnamed handler makes every slot look identical and destroys the diff. Fix the source (name the function the middleware factory returns); do not weaken the tool. The terminal route-body slot is exempt (~260 inline arrow handlers are pinned to their (path, method) slot), so "no anonymous" means no anonymous chain middleware slot.
  • The black-box matrix records the observed status code, not business-logic validity — a 400 from a missing request body after the auth gate passed is fine and deterministic.
  • Persona injection is mounted where passport.session() sits, so the matrix exercises the route-level chain (accessTokenParserloginRequiredadminRequired → handler) but not passport's own cookie parsing. Cover that with E2E.
  • WebSocket endpoints never appear in app._router.stack, which is why the third tool exists: the structural snapshot structurally cannot see /yjs/<pageId> or the socket.io namespace middleware.

External Plugin Install Smoke

GROWI installs third-party plugins as prebuilt assets (download


Content truncated.

When not to use it

  • Global tasks outside the scope of apps/app
  • Modifying packages outside the GROWI repository

Prerequisites

pnpmturborepo

Limitations

  • Dependent on correct configuration of package.json scripts
  • Only relevant to the GROWI codebase

How it compares

It provides a context-aware interface that differentiates between repo-wide workflows and application-specific maintenance.

Compared to similar skills

app-commands side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
app-commands (this skill)12moCautionBeginner
orchardcore-tester16moReviewIntermediate
replit-load-scale126dCautionAdvanced
agent-sandbox16moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

Search skills

Search the agent skills registry