This file provides guidance for AI coding assistants working with the Craft codebase.
- Always use
pnpmfor package management. Never usenpmoryarn. - Node.js version is managed by Volta (currently v22.12.0).
- Install dependencies with
pnpm install --frozen-lockfile.
| Command | Description |
|---|---|
pnpm build |
Build the project (outputs to dist/craft) |
pnpm test |
Run tests |
pnpm lint |
Run ESLint |
pnpm fix |
Auto-fix lint issues |
To manually test changes:
pnpm build && ./dist/craft- TypeScript is used throughout the codebase.
- Prettier 3.x with single quotes and no arrow parens (configured in
.prettierrc.yml). - ESLint 9.x with flat config (
eslint.config.mjs) usingtypescript-eslint. - Unused variables prefixed with
_are allowed (e.g.,_unusedParam).
src/
├── __mocks__/ # Test mocks
├── __tests__/ # Test files (*.test.ts)
├── artifact_providers/ # Artifact provider implementations
├── commands/ # CLI command implementations
├── schemas/ # Valibot schemas and TypeScript types for config
├── status_providers/ # Status provider implementations
├── targets/ # Release target implementations
├── types/ # Shared TypeScript types
├── utils/ # Utility functions
├── config.ts # Configuration loading
├── index.ts # CLI entry point
└── logger.ts # Logging utilities
dist/
└── craft # Single bundled executable (esbuild output)
- Tests use Vitest.
- Test files are located in
src/__tests__/and follow the*.test.tsnaming pattern. - Run tests with
pnpm test. - Use
vi.fn(),vi.mock(),vi.spyOn()for mocking (Vitest's mock API).
- Main branch is
master. - CI runs tests on Node.js 20 and 22.
- Craft releases itself using its own tooling (dogfooding).
- Project configuration lives in
.craft.ymlat the repository root. - The configuration schema is defined in
src/schemas/.
Craft supports a --dry-run flag that prevents destructive operations. This is implemented via a centralized abstraction layer.
Instead of checking isDryRun() manually in every function, destructive operations are wrapped with dry-run-aware proxies:
- Git operations: Use
getGitClient()fromsrc/utils/git.tsorcreateGitClient(directory)for working with specific directories - GitHub API: Use
getGitHubClient()fromsrc/utils/githubApi.ts - File writes: Use
safeFsfromsrc/utils/dryRun.ts - Other actions: Use
safeExec()orsafeExecSync()fromsrc/utils/dryRun.ts
ESLint rules prevent direct usage of raw APIs:
no-restricted-imports: Blocks directsimple-gitimportsno-restricted-syntax: Blocksnew Octokit()instantiation
If you're writing a wrapper module that needs raw access, use:
// eslint-disable-next-line no-restricted-imports -- This is the wrapper module
import simpleGit from 'simple-git';When adding new code that performs destructive operations:
- Git: Get the git client via
getGitClient()orcreateGitClient()- mutating methods are automatically blocked - GitHub API: Get the client via
getGitHubClient()-create*,update*,delete*,upload*methods are automatically blocked - File writes: Use
safeFs.writeFile(),safeFs.unlink(), etc. instead of rawfsmethods - Other: Wrap with
safeExec(action, description)for custom operations
Some operations need explicit isDryRun() checks:
- Commands with their own
--dry-runflag (e.g.,dart pub publish --dry-runin pubDev target) - Operations that need to return mock data in dry-run mode
- User experience optimizations (e.g., skipping sleep timers)
For long-term knowledge entries managed by lore (gotchas, patterns, decisions, architecture), see .lore.md in the project root.