|
1 | 1 | # Agent Guidelines for @uscreen.de/dev-service |
2 | 2 |
|
3 | | -This document provides guidelines for AI coding agents working on this repository. |
| 3 | +Guidelines for AI coding agents working on this repository. |
4 | 4 |
|
5 | 5 | ## Project Overview |
6 | 6 |
|
7 | | -A CLI tool for managing Docker services in local development environments. This is an ESM (ECMAScript modules) Node.js project that uses docker-compose to orchestrate development services. |
| 7 | +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. |
8 | 8 |
|
9 | 9 | ## Build, Lint & Test Commands |
10 | 10 |
|
11 | | -### Testing |
12 | 11 | ```bash |
13 | | -# Run all tests (sequential execution required) |
14 | | -pnpm test |
15 | | - |
16 | | -# Run tests with coverage (HTML + text report) |
17 | | -pnpm run test:cov |
| 12 | +# Install dependencies (pnpm required, enforced by preinstall hook) |
| 13 | +pnpm install |
18 | 14 |
|
19 | | -# Run tests with coverage for CI (lcov + text report) |
20 | | -pnpm run test:ci |
| 15 | +# Run all tests (must be sequential due to Docker conflicts) |
| 16 | +pnpm test |
21 | 17 |
|
22 | 18 | # Run a single test file |
23 | 19 | node --test test/cli-install.test.js |
24 | 20 |
|
25 | | -# Run specific test with pattern |
| 21 | +# Run specific test by name pattern |
26 | 22 | node --test --test-name-pattern="pattern" test/cli.test.js |
27 | | -``` |
28 | 23 |
|
29 | | -### Linting |
30 | | -```bash |
31 | | -# Lint check (uses @uscreen.de/eslint-config-prettystandard-node) |
32 | | -pnpm exec eslint . |
| 24 | +# Tests with coverage |
| 25 | +pnpm run test:cov # HTML + text report |
| 26 | +pnpm run test:ci # lcov + text (CI) |
33 | 27 |
|
34 | | -# Lint and auto-fix |
| 28 | +# Lint (uses @antfu/eslint-config with flat config) |
| 29 | +pnpm exec eslint . |
35 | 30 | pnpm exec eslint . --fix |
36 | | -``` |
37 | 31 |
|
38 | | -### Installation |
39 | | -```bash |
40 | | -# Install dependencies (pnpm is required) |
41 | | -pnpm install |
42 | | -``` |
43 | | - |
44 | | -### Make commands |
45 | | -```bash |
46 | | -make test # Run tests |
47 | | -make test.coverage # Run tests with coverage |
| 32 | +# Make shortcuts |
| 33 | +make test # Run tests |
| 34 | +make test.coverage # Run tests with coverage |
48 | 35 | ``` |
49 | 36 |
|
50 | 37 | ## Code Style Guidelines |
51 | 38 |
|
52 | 39 | ### Module System |
53 | | -- **ESM only**: Use `import`/`export`, not `require()` |
54 | | -- Use `'use strict'` at the top of files for consistency |
55 | | -- Use `import.meta.url` for current file path |
56 | | -- To require JSON files, use `createRequire` from `module` |
| 40 | +- **ESM only**: Use `import`/`export`, never `require()` |
| 41 | +- Use `node:` prefix for built-in modules: `import path from 'node:path'` |
| 42 | +- Always include `.js` extension in relative imports: `import { x } from './utils.js'` |
| 43 | +- Use `import.meta.url` for file path derivation |
| 44 | +- For JSON imports, use `createRequire` from `node:module` |
57 | 45 |
|
58 | | -### Import Style |
| 46 | +### Import Order |
59 | 47 | ```javascript |
60 | | -// Standard library imports first |
61 | | -import path from 'path' |
| 48 | +// 1. Node built-ins (with node: prefix) |
| 49 | +import { execSync } from 'node:child_process' |
| 50 | +import path from 'node:path' |
| 51 | +import { fileURLToPath } from 'node:url' |
| 52 | +// 2. Third-party packages |
| 53 | +import chalk from 'chalk' |
62 | 54 | import fs from 'fs-extra' |
63 | | -import { fileURLToPath } from 'url' |
64 | | - |
65 | | -// Third-party imports |
66 | 55 | import YAML from 'yaml' |
67 | | -import chalk from 'chalk' |
68 | | -import { Command } from 'commander' |
69 | | - |
70 | | -// Local imports last |
| 56 | +// 3. Local imports |
| 57 | +import { COMPOSE_DIR, TEMPLATES_DIR } from './constants.js' |
71 | 58 | import { docker, escape } from '../src/utils.js' |
72 | | -import { COMPOSE_DIR } from './constants.js' |
73 | 59 | ``` |
74 | 60 |
|
75 | | -### File Extensions |
76 | | -- Always include `.js` extension in relative imports: `import { x } from './utils.js'` |
77 | | -- Never omit extensions in ESM imports |
78 | | - |
79 | 61 | ### Formatting |
80 | | -- **Indentation**: 2 spaces (no tabs, except in Makefiles) |
81 | | -- **Quotes**: Single quotes for strings |
82 | | -- **Semicolons**: Not required but used inconsistently - follow existing pattern in file |
83 | | -- **Line endings**: LF (Unix style) |
| 62 | +- **Indentation**: 2 spaces (tabs only in Makefiles) |
| 63 | +- **Quotes**: Single quotes |
| 64 | +- **Semicolons**: None (consistently omitted across the entire codebase) |
| 65 | +- **Trailing commas**: None (enforced by `style/comma-dangle: ['error', 'never']`) |
| 66 | +- **Line endings**: LF |
84 | 67 | - **Final newline**: Required |
85 | 68 | - **Trailing whitespace**: Not allowed |
86 | | -- **Max line length**: No strict limit, but be reasonable |
87 | 69 |
|
88 | 70 | ### Naming Conventions |
89 | | -- **Files**: kebab-case (e.g., `cli-install.js`, `cli-start.test.js`) |
90 | | -- **Functions**: camelCase (e.g., `readPackageJson`, `checkComposeDir`) |
91 | | -- **Constants**: SCREAMING_SNAKE_CASE (e.g., `COMPOSE_DIR`, `TEMPLATES_DIR`) |
92 | | -- **Variables**: camelCase (e.g., `projectname`, `composePath`) |
93 | | -- **Exported functions**: Use `export const functionName =` or `export const functionName = async` |
| 71 | +- **Files**: kebab-case (`cli-install.js`, `cli-start.test.js`) |
| 72 | +- **Functions/variables**: camelCase (`readPackageJson`, `composePath`) |
| 73 | +- **Constants**: SCREAMING_SNAKE_CASE (`COMPOSE_DIR`, `TEMPLATES_DIR`) |
| 74 | +- **Exports**: `export const functionName =` pattern |
94 | 75 |
|
95 | 76 | ### Function Patterns |
96 | | -```javascript |
97 | | -// Arrow functions for simple utilities |
98 | | -export const escape = (name) => |
99 | | - name.replace(/^[^a-zA-Z0-9]*/, '').replace(/[^a-zA-Z0-9-]/g, '-') |
| 77 | +- **Arrow functions only** -- the codebase never uses the `function` keyword |
| 78 | +- Top-level arrow functions are allowed (`antfu/top-level-function: off`) |
100 | 79 |
|
101 | | -// Named functions for complex logic |
102 | | -const fillTemplate = (template, data, removeSections, keepSections) => { |
103 | | - // implementation |
104 | | -} |
| 80 | +```javascript |
| 81 | +export const escape = name => |
| 82 | + name.replace(/^[^a-z0-9]*/i, '').replace(/[^a-z0-9-]/gi, '-') |
105 | 83 |
|
106 | | -// Async arrow functions |
107 | 84 | export const install = async (opts) => { |
108 | 85 | // implementation |
109 | 86 | } |
110 | 87 | ``` |
111 | 88 |
|
112 | | -### Error Handling |
113 | | -```javascript |
114 | | -// Throw errors with descriptive messages |
115 | | -if (invalid.length > 0) { |
116 | | - throw Error('Invalid custom services:\n...') |
117 | | -} |
| 89 | +### Curly Braces |
| 90 | +Rule: `'curly': ['error', 'multi-line', 'consistent']` -- `else` goes on a new line after `}`: |
118 | 91 |
|
119 | | -// Catch and handle gracefully when appropriate |
120 | | -try { |
121 | | - packageJson = readPackageSync({ cwd: root }) |
122 | | -} catch (e) {} |
| 92 | +```javascript |
| 93 | +if (service) return result |
123 | 94 |
|
124 | | -// Use error utility for CLI output |
125 | | -export const error = (e) => { |
126 | | - console.error(chalk.red(`ERROR: ${e.message}\n`)) |
127 | | - process.exit(e.code || 1) |
| 95 | +if (service) { |
| 96 | + await compose('up', '-d', service) |
| 97 | +} |
| 98 | +else { |
| 99 | + await compose('up', '-d') |
128 | 100 | } |
129 | 101 | ``` |
130 | 102 |
|
131 | | -### Console Output |
132 | | -- Use `chalk` for colored output in CLI |
133 | | -- Error messages: `chalk.red('ERROR: ...')` |
134 | | -- Warning messages: `chalk.yellow('WARNING: ...')` |
135 | | -- Info messages: plain `console.log()` |
136 | | -- Always include newline after messages |
| 103 | +### Error Handling |
| 104 | +- CLI-facing errors use `error()` from `src/utils.js` which prints with `chalk.red()` and calls `process.exit()` |
| 105 | +- In `bin/` files: `try { await action(options) } catch (e) { error(e) }` |
| 106 | +- Empty catch for non-critical: `try { ... } catch {}` |
| 107 | +- Warnings: `chalk.yellow()`, Info: plain `console.log()` |
| 108 | +- Always append `\n` after error/warning messages |
137 | 109 |
|
138 | | -### Async/Await |
139 | | -- Prefer async/await over promise chains |
140 | | -- Use `Promise.all()` for parallel operations |
141 | | -- Handle rejections appropriately |
| 110 | +## Testing |
142 | 111 |
|
143 | | -### Testing with Node Test Runner |
| 112 | +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. |
144 | 113 | ```javascript |
145 | | -import { test } from 'node:test' |
146 | 114 | import assert from 'node:assert/strict' |
147 | | -import { cli } from './helpers.js' |
148 | | - |
149 | | -test('$ cli', async (t) => { |
150 | | - const result = await cli([]) |
151 | | - assert.equal(result.code, 1, 'Should fail') |
152 | | - assert.equal(result.stdout, '', 'Should output nothing to stdout') |
| 115 | +import { afterEach, describe, test } from 'node:test' |
| 116 | +import { clearArena, cli, prepareArena } from './helpers.js' |
| 117 | + |
| 118 | +describe('$ cli install', () => { |
| 119 | + afterEach(async () => { |
| 120 | + await clearArena() |
| 121 | + }) |
| 122 | + |
| 123 | + test('Within a folder with no package.json', async () => { |
| 124 | + const result = await cli(['install'], '/tmp') |
| 125 | + assert.equal(result.code, 1, 'Should fail') |
| 126 | + assert.ok(result.stderr.includes('ERROR'), 'Should output error') |
| 127 | + }) |
153 | 128 | }) |
154 | 129 | ``` |
155 | 130 |
|
156 | | -### Test Patterns |
157 | | -- Use native Node.js test runner (node:test) |
158 | | -- Tests must run sequentially (`--test-concurrency=1`) |
159 | | -- Use strict assertions (`node:assert/strict`) |
160 | | -- Test files: `*.test.js` in `test/` directory |
161 | | -- Helper functions in `test/helpers.js` |
162 | | -- Setup/teardown using `prepareArena()` and `clearArena()` |
163 | | -- Disable color output in tests: `process.env.FORCE_COLOR = 0` |
| 131 | +- Test files: `test/*.test.js` |
| 132 | +- Helpers in `test/helpers.js` (`prepareArena`, `clearArena`, `cli`, `compose`, `loadYaml`) |
| 133 | +- Arena pattern: tests use `test/_arena/` as a temporary project directory |
| 134 | +- Color disabled in tests: `process.env.FORCE_COLOR = 0` |
| 135 | +- Every assertion includes a descriptive message string |
| 136 | +- Test names describe the scenario: `'If services are defined in .compose subfolder'` |
164 | 137 |
|
165 | 138 | ## Project Structure |
166 | 139 |
|
167 | 140 | ``` |
168 | | -. |
169 | 141 | ├── bin/ # CLI entry points (cli.js, cli-install.js, etc.) |
170 | 142 | ├── src/ # Source code |
171 | | -│ ├── install.js # Service installation logic |
172 | | -│ ├── utils.js # Shared utilities |
173 | 143 | │ ├── check.js # Port availability checks |
174 | | -│ └── constants.js # Constants |
175 | | -├── templates/ # Service templates (YAML configs) |
| 144 | +│ ├── constants.js # Path constants, version |
| 145 | +│ ├── install.js # Service installation logic |
| 146 | +│ └── utils.js # Shared utilities (docker, compose, escape, error) |
| 147 | +├── templates/ # Docker-compose service templates (YAML) |
176 | 148 | ├── test/ # Tests |
177 | | -│ ├── helpers.js # Test utilities |
178 | | -│ └── *.test.js # Test files |
179 | | -└── package.json # Package manifest |
| 149 | +│ ├── helpers.js # Test infrastructure (arena, cli runner, compose) |
| 150 | +│ └── *.test.js # Test files (18 files) |
| 151 | +└── eslint.config.js # ESLint flat config (@antfu/eslint-config) |
180 | 152 | ``` |
181 | 153 |
|
182 | 154 | ## Important Notes |
183 | 155 |
|
184 | | -- **Node version**: Requires Node.js >= 20 |
185 | | -- **Package manager**: pnpm only (enforced by preinstall hook) |
186 | | -- **Docker dependencies**: Requires `docker` and `docker-compose` in PATH |
187 | | -- **Tests**: Must run sequentially due to Docker resource conflicts |
188 | | -- **No TypeScript**: Plain JavaScript with JSDoc if needed |
189 | | -- **Shebang**: Use `#!/usr/bin/env node` for CLI executables |
| 156 | +- **Node.js**: >= 20 required (`.nvmrc` targets 24) |
| 157 | +- **Package manager**: pnpm only (enforced) |
| 158 | +- **Docker**: Requires `docker` and `docker-compose` in PATH |
| 159 | +- **No TypeScript**: Plain JavaScript only |
| 160 | +- **Shebang**: `#!/usr/bin/env node` for CLI executables in `bin/` |
| 161 | +- **CI**: Tests run on Node 20, 22, 24 via GitHub Actions |
0 commit comments