Guidelines for AI coding agents working on this repository.
A CLI tool for managing Docker services in local development environments. ESM-only Node.js project using docker-compose to orchestrate services like Redis, Mongo, Nginx, etc.
# Install dependencies (pnpm required, enforced by preinstall hook)
pnpm install
# Run all tests (must be sequential due to Docker conflicts)
pnpm test
# Run a single test file
node --test test/cli-install.test.js
# Run specific test by name pattern
node --test --test-name-pattern="pattern" test/cli.test.js
# Tests with coverage
pnpm run test:cov # HTML + text report
pnpm run test:ci # lcov + text (CI)
# Lint (uses @antfu/eslint-config with flat config)
pnpm exec eslint .
pnpm exec eslint . --fix
# Make shortcuts
make test # Run tests
make test.coverage # Run tests with coverage- ESM only: Use
import/export, neverrequire() - Use
node:prefix for built-in modules:import path from 'node:path' - Always include
.jsextension in relative imports:import { x } from './utils.js' - Use
import.meta.urlfor file path derivation - For JSON imports, use
createRequirefromnode:module
// 1. Node built-ins (with node: prefix)
import { execSync } from 'node:child_process'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
// 2. Third-party packages
import chalk from 'chalk'
import fs from 'fs-extra'
import YAML from 'yaml'
// 3. Local imports
import { COMPOSE_DIR, TEMPLATES_DIR } from './constants.js'
import { docker, escape } from '../src/utils.js'- Indentation: 2 spaces (tabs only in Makefiles)
- Quotes: Single quotes
- Semicolons: None (consistently omitted across the entire codebase)
- Trailing commas: None (enforced by
style/comma-dangle: ['error', 'never']) - Line endings: LF
- Final newline: Required
- Trailing whitespace: Not allowed
- Files: kebab-case (
cli-install.js,cli-start.test.js) - Functions/variables: camelCase (
readPackageJson,composePath) - Constants: SCREAMING_SNAKE_CASE (
COMPOSE_DIR,TEMPLATES_DIR) - Exports:
export const functionName =pattern
- Arrow functions only -- the codebase never uses the
functionkeyword - Top-level arrow functions are allowed (
antfu/top-level-function: off)
export const escape = name =>
name.replace(/^[^a-z0-9]*/i, '').replace(/[^a-z0-9-]/gi, '-')
export const install = async (opts) => {
// implementation
}Rule: 'curly': ['error', 'multi-line', 'consistent'] -- else goes on a new line after }:
if (service) return result
if (service) {
await compose('up', '-d', service)
}
else {
await compose('up', '-d')
}- CLI-facing errors use
error()fromsrc/utils.jswhich prints withchalk.red()and callsprocess.exit() - In
bin/files:try { await action(options) } catch (e) { error(e) } - Empty catch for non-critical:
try { ... } catch {} - Warnings:
chalk.yellow(), Info: plainconsole.log() - Always append
\nafter error/warning messages
Uses the native Node.js test runner (node:test) with node:assert/strict. Tests must run sequentially (--test-concurrency=1) because they share Docker resources.
import assert from 'node:assert/strict'
import { afterEach, describe, test } from 'node:test'
import { clearArena, cli, prepareArena } from './helpers.js'
describe('$ cli install', () => {
afterEach(async () => {
await clearArena()
})
test('Within a folder with no package.json', async () => {
const result = await cli(['install'], '/tmp')
assert.equal(result.code, 1, 'Should fail')
assert.ok(result.stderr.includes('ERROR'), 'Should output error')
})
})- Test files:
test/*.test.js - Helpers in
test/helpers.js(prepareArena,clearArena,cli,compose,loadYaml) - Arena pattern: tests use
test/_arena/as a temporary project directory - Color disabled in tests:
process.env.FORCE_COLOR = 0 - Every assertion includes a descriptive message string
- Test names describe the scenario:
'If services are defined in .compose subfolder'
├── bin/ # CLI entry points (cli.js, cli-install.js, etc.)
├── src/ # Source code
│ ├── check.js # Port availability checks
│ ├── constants.js # Path constants, version
│ ├── install.js # Service installation logic
│ └── utils.js # Shared utilities (docker, compose, escape, error)
├── templates/ # Docker-compose service templates (YAML)
├── test/ # Tests
│ ├── helpers.js # Test infrastructure (arena, cli runner, compose)
│ └── *.test.js # Test files (18 files)
└── eslint.config.js # ESLint flat config (@antfu/eslint-config)
- Node.js: >= 20 required (
.nvmrctargets 24) - Package manager: pnpm only (enforced)
- Docker: Requires
dockeranddocker-composein PATH - No TypeScript: Plain JavaScript only
- Shebang:
#!/usr/bin/env nodefor CLI executables inbin/ - CI: Tests run on Node 20, 22, 24 via GitHub Actions