Claude-code-plugins-plus-skills bamboohr-local-dev-loop
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/bamboohr-pack/skills/bamboohr-local-dev-loop" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-bamboohr-local-dev-loop && rm -rf "$T"
manifest:
plugins/saas-packs/bamboohr-pack/skills/bamboohr-local-dev-loop/SKILL.mdsource content
BambooHR Local Dev Loop
Overview
Set up a fast, reproducible local development workflow for BambooHR integrations with request mocking, hot reload, and integration testing against the real API.
Prerequisites
- Completed
setupbamboohr-install-auth - Node.js 18+ with npm or pnpm
andBAMBOOHR_API_KEY
set inBAMBOOHR_COMPANY_DOMAIN.env
Instructions
Step 1: Project Structure
my-bamboohr-project/ ├── src/ │ ├── bamboohr/ │ │ ├── client.ts # Reusable API client │ │ ├── types.ts # BambooHR response types │ │ └── employees.ts # Employee operations │ └── index.ts ├── tests/ │ ├── mocks/ │ │ └── bamboohr.ts # API response fixtures │ ├── unit/ │ │ └── employees.test.ts │ └── integration/ │ └── bamboohr.test.ts ├── .env.local # Real API key (git-ignored) ├── .env.example # Template for team ├── .env.test # Test config (sandbox key) └── package.json
Step 2: Create Reusable API Client
// src/bamboohr/client.ts import 'dotenv/config'; export class BambooHRClient { private baseUrl: string; private authHeader: string; constructor(companyDomain?: string, apiKey?: string) { const domain = companyDomain || process.env.BAMBOOHR_COMPANY_DOMAIN!; const key = apiKey || process.env.BAMBOOHR_API_KEY!; this.baseUrl = `https://api.bamboohr.com/api/gateway.php/${domain}/v1`; this.authHeader = `Basic ${Buffer.from(`${key}:x`).toString('base64')}`; } async request<T>(path: string, options: RequestInit = {}): Promise<T> { const res = await fetch(`${this.baseUrl}${path}`, { ...options, headers: { Authorization: this.authHeader, Accept: 'application/json', 'Content-Type': 'application/json', ...options.headers, }, }); if (!res.ok) { const errMsg = res.headers.get('X-BambooHR-Error-Message') || res.statusText; throw new BambooHRError(res.status, errMsg, path); } return res.json() as Promise<T>; } async getEmployee(id: number | string, fields: string[]): Promise<Record<string, string>> { return this.request(`/employees/${id}/?fields=${fields.join(',')}`); } async getDirectory(): Promise<{ employees: BambooEmployee[] }> { return this.request('/employees/directory'); } } export class BambooHRError extends Error { constructor(public status: number, message: string, public path: string) { super(`BambooHR ${status}: ${message} [${path}]`); this.name = 'BambooHRError'; } }
Step 3: Setup Hot Reload and Scripts
{ "scripts": { "dev": "tsx watch src/index.ts", "test": "vitest run", "test:watch": "vitest --watch", "test:integration": "DOTENV_CONFIG_PATH=.env.test vitest run tests/integration/", "typecheck": "tsc --noEmit" }, "devDependencies": { "tsx": "^4.0.0", "vitest": "^2.0.0", "typescript": "^5.5.0", "msw": "^2.0.0" } }
Step 4: Mock BambooHR API with MSW
// tests/mocks/bamboohr.ts import { http, HttpResponse } from 'msw'; const MOCK_COMPANY = 'testcompany'; const BASE = `https://api.bamboohr.com/api/gateway.php/${MOCK_COMPANY}/v1`; export const bamboohrHandlers = [ // Employee directory http.get(`${BASE}/employees/directory`, () => { return HttpResponse.json({ fields: [{ id: 'displayName', type: 'text', name: 'Display Name' }], employees: [ { id: '1', displayName: 'Jane Smith', firstName: 'Jane', lastName: 'Smith', jobTitle: 'Engineer', department: 'Engineering', workEmail: 'jane@test.com', location: 'Remote', }, { id: '2', displayName: 'Bob Jones', firstName: 'Bob', lastName: 'Jones', jobTitle: 'Designer', department: 'Design', workEmail: 'bob@test.com', location: 'NYC', }, ], }); }), // Single employee http.get(`${BASE}/employees/:id/`, ({ params }) => { return HttpResponse.json({ id: params.id, firstName: 'Jane', lastName: 'Smith', jobTitle: 'Engineer', department: 'Engineering', hireDate: '2023-01-15', workEmail: 'jane@test.com', status: 'Active', }); }), // Custom report http.post(`${BASE}/reports/custom`, () => { return HttpResponse.json({ title: 'Test Report', employees: [ { firstName: 'Jane', lastName: 'Smith', department: 'Engineering' }, ], }); }), ];
Step 5: Write Unit Tests
// tests/unit/employees.test.ts import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'; import { setupServer } from 'msw/node'; import { bamboohrHandlers } from '../mocks/bamboohr'; import { BambooHRClient } from '../../src/bamboohr/client'; const server = setupServer(...bamboohrHandlers); beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); describe('BambooHRClient', () => { const client = new BambooHRClient('testcompany', 'fake-key'); it('fetches the employee directory', async () => { const dir = await client.getDirectory(); expect(dir.employees).toHaveLength(2); expect(dir.employees[0].displayName).toBe('Jane Smith'); }); it('fetches a single employee', async () => { const emp = await client.getEmployee(1, ['firstName', 'lastName', 'jobTitle']); expect(emp.firstName).toBe('Jane'); expect(emp.jobTitle).toBe('Engineer'); }); });
Step 6: Integration Test Against Real API
// tests/integration/bamboohr.test.ts import { describe, it, expect } from 'vitest'; import { BambooHRClient } from '../../src/bamboohr/client'; const HAS_KEY = !!process.env.BAMBOOHR_API_KEY; describe.skipIf(!HAS_KEY)('BambooHR Integration', () => { const client = new BambooHRClient(); it('should fetch the real employee directory', async () => { const dir = await client.getDirectory(); expect(dir.employees.length).toBeGreaterThan(0); expect(dir.employees[0]).toHaveProperty('displayName'); }, 15_000); });
Output
- Reusable
with typed methodsBambooHRClient - MSW mocks for offline development
- Unit tests with mocked API
- Integration tests gated on
presenceBAMBOOHR_API_KEY - Hot-reload dev server via
tsx watch
Error Handling
| Error | Cause | Solution |
|---|---|---|
| Wrong key in | Re-copy from BambooHR dashboard |
MSW | Unmocked endpoint hit | Add handler to |
in tests | MSW server not started | Ensure |
| Slow integration tests | Real API latency | Increase vitest timeout to 15s |
Resources
Next Steps
See
bamboohr-sdk-patterns for production-ready code patterns.