Claude-code-plugins-plus-skills hubspot-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/hubspot-pack/skills/hubspot-local-dev-loop" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-hubspot-local-dev-loop && rm -rf "$T"
manifest: plugins/saas-packs/hubspot-pack/skills/hubspot-local-dev-loop/SKILL.md
source content

HubSpot Local Dev Loop

Overview

Set up a fast local development workflow for HubSpot integrations with sandbox accounts, mocking, and test utilities.

Prerequisites

  • Completed
    hubspot-install-auth
    setup
  • Node.js 18+ with npm/pnpm
  • HubSpot developer test account (free at developers.hubspot.com)

Instructions

Step 1: Create Project Structure

my-hubspot-project/
├── src/
│   ├── hubspot/
│   │   ├── client.ts       # Singleton @hubspot/api-client wrapper
│   │   ├── contacts.ts     # Contact operations
│   │   ├── deals.ts        # Deal operations
│   │   └── types.ts        # HubSpot type definitions
│   └── index.ts
├── tests/
│   ├── mocks/
│   │   └── hubspot.ts      # Shared mock factory
│   ├── contacts.test.ts
│   └── deals.test.ts
├── .env.local               # Local secrets (git-ignored)
├── .env.example             # Template for team
├── tsconfig.json
└── package.json

Step 2: Create Client Singleton

// src/hubspot/client.ts
import * as hubspot from '@hubspot/api-client';

let instance: hubspot.Client | null = null;

export function getHubSpotClient(): hubspot.Client {
  if (!instance) {
    if (!process.env.HUBSPOT_ACCESS_TOKEN) {
      throw new Error('HUBSPOT_ACCESS_TOKEN environment variable is required');
    }
    instance = new hubspot.Client({
      accessToken: process.env.HUBSPOT_ACCESS_TOKEN,
      numberOfApiCallRetries: 3,
    });
  }
  return instance;
}

// Reset client (useful for tests)
export function resetHubSpotClient(): void {
  instance = null;
}

Step 3: Configure Testing with Vitest

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "test": "vitest",
    "test:watch": "vitest --watch",
    "test:integration": "HUBSPOT_TEST=true vitest --config vitest.integration.config.ts"
  },
  "devDependencies": {
    "@hubspot/api-client": "^13.0.0",
    "vitest": "^2.0.0",
    "tsx": "^4.0.0"
  }
}

Step 4: Mock HubSpot API for Unit Tests

// tests/mocks/hubspot.ts
import { vi } from 'vitest';

export function createMockHubSpotClient() {
  return {
    crm: {
      contacts: {
        basicApi: {
          create: vi.fn().mockResolvedValue({
            id: '101',
            properties: { firstname: 'Jane', lastname: 'Doe', email: 'jane@test.com' },
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
            archived: false,
          }),
          getById: vi.fn().mockResolvedValue({
            id: '101',
            properties: { firstname: 'Jane', lastname: 'Doe', email: 'jane@test.com' },
          }),
          getPage: vi.fn().mockResolvedValue({
            results: [],
            paging: undefined,
          }),
          update: vi.fn().mockResolvedValue({ id: '101', properties: {} }),
          archive: vi.fn().mockResolvedValue(undefined),
        },
        searchApi: {
          doSearch: vi.fn().mockResolvedValue({ total: 0, results: [] }),
        },
        batchApi: {
          create: vi.fn().mockResolvedValue({ status: 'COMPLETE', results: [] }),
          read: vi.fn().mockResolvedValue({ status: 'COMPLETE', results: [] }),
          update: vi.fn().mockResolvedValue({ status: 'COMPLETE', results: [] }),
        },
      },
      deals: {
        basicApi: {
          create: vi.fn().mockResolvedValue({
            id: '201',
            properties: { dealname: 'Test Deal', amount: '1000' },
          }),
          getById: vi.fn(),
          update: vi.fn(),
        },
      },
      companies: {
        basicApi: {
          create: vi.fn().mockResolvedValue({
            id: '301',
            properties: { name: 'Test Co', domain: 'test.com' },
          }),
        },
      },
    },
  };
}

// tests/contacts.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createMockHubSpotClient } from './mocks/hubspot';

vi.mock('../src/hubspot/client', () => ({
  getHubSpotClient: vi.fn(),
}));

describe('Contact operations', () => {
  const mockClient = createMockHubSpotClient();

  beforeEach(() => {
    vi.mocked(getHubSpotClient).mockReturnValue(mockClient as any);
  });

  it('should create a contact with required properties', async () => {
    const result = await mockClient.crm.contacts.basicApi.create({
      properties: { firstname: 'Jane', lastname: 'Doe', email: 'jane@test.com' },
      associations: [],
    });
    expect(result.id).toBe('101');
    expect(result.properties.email).toBe('jane@test.com');
  });
});

Step 5: Developer Test Account

# Create a free developer test account at:
# https://developers.hubspot.com/get-started

# Use test account token for integration tests
# .env.local
HUBSPOT_ACCESS_TOKEN=pat-na1-test-xxxx  # test account token
HUBSPOT_PORTAL_ID=12345678              # test portal ID

Output

  • Client singleton with reset capability for tests
  • Mock factory covering contacts, deals, companies
  • Unit tests running without API calls
  • Integration tests using developer test account
  • Hot-reload dev loop with
    tsx watch

Error Handling

ErrorCauseSolution
HUBSPOT_ACCESS_TOKEN is required
Missing env varCopy
.env.example
to
.env.local
Mock type mismatchSDK version changeUpdate mock factory to match SDK types
Test timeoutReal API call leakedVerify mocks are wired correctly
429
in integration tests
Rate limited on test accountAdd delay between test runs

Examples

Integration Test with Real API

// tests/integration/contacts.integration.test.ts
import { describe, it, expect } from 'vitest';
import * as hubspot from '@hubspot/api-client';

const shouldRun = process.env.HUBSPOT_TEST === 'true';

describe.skipIf(!shouldRun)('HubSpot Integration', () => {
  const client = new hubspot.Client({
    accessToken: process.env.HUBSPOT_ACCESS_TOKEN!,
  });

  it('should create and archive a test contact', async () => {
    const contact = await client.crm.contacts.basicApi.create({
      properties: {
        firstname: 'Integration',
        lastname: `Test-${Date.now()}`,
        email: `test-${Date.now()}@example.com`,
      },
      associations: [],
    });
    expect(contact.id).toBeDefined();

    // Clean up
    await client.crm.contacts.basicApi.archive(contact.id);
  });
});

Resources

Next Steps

See

hubspot-sdk-patterns
for production-ready code patterns.