bun run build- Compile TypeScript to dist/bun run fmt- Format code with oxfmtbun run fmt:check- Check formatting (CI)bun run lint- Lint with oxlint (type-aware)bun run lint:fix- Auto-fix linting issuesbun test- Run all testsbun test src/version.test.ts- Run single test filebun test -t "test name pattern"- Run tests matching patternbun test --coverage- Generate coverage report
bun changeset- Run CLI from sourcebun install/bun install <pkg>/bun install --dev <pkg>- Manage dependencies
src/*.ts - Source files
src/*.test.ts - Test files (Bun test)
dist/ - Compiled output (gitignored)
.changeset/ - Changeset files
- Local imports: Use
.jsextension:import { foo } from './config.js' - Built-ins: Use
node:prefix:import { readFileSync } from 'node:fs' - Type imports:
import type { Config } from './config.js' - Order: Built-ins → External deps → Local deps
Example:
import { readFileSync } from 'node:fs';
import path from 'node:path';
import pc from 'picocolors';
import { readConfig } from './config.js';
import type { ChangesetConfig } from './config.js';- Config: ES2020, NodeNext modules, strict enabled
- Interfaces vs Types:
interfacefor objects,typefor unions/aliases - Never use
any: Useunknownthen narrow with type guards - Optional params: Use
?and defaults:function foo({ dryRun = false } = {}) - Type assertions: Use
assparingly, only when certain - Export for tests: Functions tested must be exported
- Functions/variables: camelCase -
findPackages,packageMap - Types/interfaces: PascalCase -
ChangesetConfig,ChangesetType - Constants: Descriptive camelCase -
defaultChangesetTypes - Files: kebab-case -
config.ts,version.ts - Test files:
<module>.test.ts- e.g.,version.test.ts
- oxfmt: Formats all files except
*.md(.oxfmtrc.json) - oxlint: Type-aware linting, ignores
dist/**and*.test.ts(.oxlintrc.json) - CI order: format → lint → test → build
- Programming errors:
throw new Error("descriptive message") - User errors:
console.error(pc.red("message")); process.exit(1) - Warnings:
console.warn("msg")orconsole.log(pc.yellow("msg")) - Success:
console.log(pc.green("✔"), pc.cyan("message")) - Validation: Early returns, guard clauses at function start
program
.command("version")
.option("--dry-run", "Preview changes", false)
.action(async (options) => {
await version({ dryRun: options.dryRun });
process.exit(0); // Required to prevent prompt issues
});- Import:
import { describe, test, expect, beforeEach, afterEach, spyOn, mock } from 'bun:test' - Mock modules: Must come before imports:
mock.module("./config.js", () => ({ readConfig: () => ({...}) })); import { version } from "./version.js";
- Test syntax: Use
test()notit():test("should do X", () => {...}) - Grouping:
describe("functionName", () => {...}) - Spying:
spyOn(console, "log").mockImplementation(() => {}) - Cleanup:
afterEach(() => { mock.clearAllMocks() })
- Read:
readFileSync(path, "utf-8") - Write:
writeFileSync(path, content, { encoding: "utf-8" }) - Check:
if (!existsSync(path)) {...} - Paths:
path.join(dir, file)for cross-platform - JSON:
JSON.stringify(obj, null, 2)(2-space indent) - Delete:
unlinkSync(path) - Create dir:
mkdirSync(path)
import pc from 'picocolors';
console.log(pc.green("✔"), pc.cyan("Done"), pc.dim("(info)"));
console.error(pc.red("Error message"));
console.log(pc.yellow("Warning"), pc.bold("important"));- Async/await: Always use for async ops, use
Promise.all()for parallel - Early returns: Prefer over nested conditionals
- Template literals: Use for strings:
`Hello ${name}` - Array methods: Prefer
map(),filter(),find(),some(),every() - For loops: Use
for...ofwhen needingbreak/continue - Destructuring:
const { name, version } = packageJson - Map/Set: Use for collections, convert with
Array.from(map.values()) - Glob:
globSync({ patterns: ["**/*.md"], ignore: ["**/node_modules/**"] })
- No code comments (self-documenting code)
- Small, focused functions
- Type everything explicitly
- No trailing whitespace/newlines
- Default:
feat→ minor, others → patch - Breaking:
!suffix (e.g.,feat!) → major - Explicit:
@majorsuffix → major - Configurable: Set
releaseTypeinlazyChangesets.typesarray - Multiple: Highest bump wins for same package
---
"@package/name": feat!
"@other/package": fix
---
Description of changes- Location:
.changeset/config.json - Fields:
access,baseBranch,updateInternalDependencies,ignore - Optional:
lazyChangesets.types(uses defaults if omitted)
- Create changeset:
bun changeset - Write code + tests
- Run:
bun test→bun run fmt:check→bun run lint→bun run build - Commit (include changeset file)
Order: format → lint → test → build (must all pass)
changeset version- Bump versions, update CHANGELOGs, delete changesets- Commit version changes
changeset publish- Publish to npm, create GitHub releases
- ❌ Missing
.jsextension in local imports - ❌ Missing
node:prefix for built-ins - ❌ Using
anytype - ❌ Forgetting
process.exit(0)in CLI commands - ❌ Not cleaning up mocks in
afterEach - ❌ Using
it()instead oftest() - ❌ Missing
encoding: "utf-8"in file operations - ❌ Not checking file existence before operations