This document provides development workflow guidelines and coding standards for agentic coding assistants working in this hzn-cli repository.
Build the project:
npm run build- Compiles TypeScript to JavaScript in
./builddirectory - Sets executable permissions on build artifacts
- Copies configuration files to build directory
Watch mode for development:
npm run build:watch- Automatically rebuilds when TypeScript files change
Run all tests:
npm test- Runs Jest test suite with coverage reporting
- Coverage threshold: 80% for branches, functions, lines, and statements
Run tests in watch mode:
npm run test:watch- Runs tests and watches for changes
Run a single test file:
npx jest tests/unit-tests/hzn.test.ts- Replace with the specific test file path
View test coverage:
npm run view:coverage- Opens coverage report in browser
Start development server:
npm start- Runs nodemon with TypeScript watching
Install dependencies:
npm installClean build artifacts:
npm run clean- Removes coverage reports and compiled JavaScript files
- Language: TypeScript 4.5.5+
- Target: ES2020
- Module System: CommonJS (compiled output)
- Type Checking: Non-strict mode (
"strict": false)
ES6 imports for external dependencies:
import chalk from 'chalk';
import { existsSync } from 'fs';
import { Hzn, utils } from '../common/src/hzn';Mixed usage allowed for legacy compatibility:
const dotenv = require('dotenv');
const cp = require('child_process');Export classes and functions:
export class Utils {
// implementation
}
export const handler = (argv: Arguments<Options>): void => {
// implementation
};Use interfaces for complex parameter objects:
interface IHznParam {
org: string;
configPath: string;
name: string;
// ... other properties
}
type Options = {
action: string;
org: string | undefined;
// ... other options
};Generic type annotations:
export const builder: CommandBuilder<Options, Options> = (yargs) => {
// implementation
};Variables and functions: camelCase
const configPath = config_path || utils.getHznConfig();
const skipInitialize = ['dockerImageExists'];Classes and interfaces: PascalCase
export class Utils {
// implementation
}
interface IHznParam {
// properties
}Constants: UPPER_SNAKE_CASE
const isBoolean = ['TOP_LEVEL_SERVICE'];
const mustHave = ["SERVICE_NAME", "SERVICE_CONTAINER_CREDS"];File names: kebab-case for commands, camelCase for utilities
// Commands: deploy.ts, setup.ts
// Utilities: utils.ts, hzn.ts, env.tsIndentation: 2 spaces (follow existing code patterns)
Line length: No strict limit, break lines for readability
Object/array formatting:
const param: IHznParam = {
org: 'myorg',
configPath: 'configPath',
name: 'name'
};
let availableActions = 'Available actions:'
runDirectly.concat(justRun).concat(promptForUpdate).concat(customRun).sort().forEach((action) => {
availableActions += ` ${action}`
});Use try/catch for synchronous operations:
try {
// risky operation
} catch (error) {
console.error('Error:', error);
}Observable error handling:
return new Observable((observer) => {
try {
// async operation
observer.next(result);
observer.complete();
} catch (error) {
observer.error(error);
}
});Validation and early returns:
if(env.length == 0) {
let value = utils.getPropValueFromFile(`${utils.getHznConfig()}/.env-local`, 'DEFAULT_ORG')
env = value.length > 0 ? value : 'biz'
}Test file naming: *.test.ts
// tests/unit-tests/hzn.test.ts
// tests/unit-tests/deploy.test.tsTest structure:
describe('ClassName', () => {
let instance: ClassName;
beforeEach(() => {
instance = new ClassName(param);
});
it('should have instance of ClassName', () => {
expect(instance).toBeInstanceOf(ClassName);
});
});Mocking with ts-jest:
import { mocked } from 'ts-jest/utils';
// Use mocked() for mocking dependenciesYargs command structure:
export const command: string = 'deploy <action>';
export const desc: string = 'Deploy <action> to Org <org>';
export const builder: CommandBuilder<Options, Options> = (yargs) =>
yargs
.options({
org: {type: 'string', desc: 'Organization to be deployed to'},
// ... other options
});
export const handler = (argv: Arguments<Options>): void => {
// command implementation
};Source structure:
src/
├── commands/ # CLI commands
├── common/src/ # Shared utilities
│ ├── utils.ts # Utility functions
│ ├── hzn.ts # Core HZN logic
│ ├── env.ts # Environment handling
│ └── index.ts # Exports
└── index.ts # CLI entry point
Test structure:
tests/
├── unit-tests/ # Unit test files
└── helpers/ # Test utilities
- Type Safety: Use TypeScript types consistently, avoid
anywhen possible - Error Handling: Always handle potential errors in async operations
- Code Reuse: Extract common functionality to utility functions
- Testing: Maintain 80%+ test coverage, write tests for new functionality
- Documentation: Add JSDoc comments for complex functions
- Imports: Group imports by type (external libraries, then internal modules)
- Performance: Use efficient algorithms, avoid unnecessary operations
- Security: Never log or expose sensitive credentials or tokens
Pre-commit validation:
npm run precommit- Runs build before commits to ensure code compiles
Required environment variables:
HZN_ORG_ID: Organization identifierHZN_EXCHANGE_USER_AUTH: Authentication credentialsHZN_EXCHANGE_URL: Exchange service URLHZN_FSS_CSSURL: CSS service URL
Configuration files:
.env-hzn.json: Horizon-specific configuration.env-local: Local environment variables.env-support: Platform support configuration AGENTS.md