Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Project Overview

This is `@hubspot/local-dev-lib` — a shared TypeScript library that provides core functionality for HubSpot local development tooling. It replaces the deprecated `@hubspot/cli-lib`.

**IMPORTANT** - Always make a plan and confirm the plan with the user before implementation.

# What This Library Does

- **Config utils** (`config/`) — manage HubSpot account configuration files. `getConfig()` resolves config using: env vars → global config (`~/.hscli/config.yml`) → local config (findup). Key functions: `getConfig()`, `updateConfigAccount()`, `getConfigAccountById()`, `getConfigAccountByName()`, `getLinkedOrAllConfigAccounts()`.
- **Per-directory linking** (`config/hsSettings.ts`) — `.hs/settings.json` scopes which accounts are active for a directory. Created by `hs account link`. Contains `accounts` (array of linked account IDs) and `localDefaultAccount`.
- **API utils** (`api/`) — HTTP calls to HubSpot public API endpoints. Requires a parsed config in memory. The HTTP wrapper handles auth headers and token refreshes automatically.
- **Lib utils** (`lib/`) — exported functions and modules: filesystem navigation, HubSpot account connections (sandboxes), file parsing, and GitHub integration. Anything exported from the repo should live here (excluding special cases like `config/`).
- **Internal utils** (`utils/`) — internal helper functions that are NOT exported from the repo.
- **Error utils** (`errors/`) — standardized error handling. This library throws errors rather than logging them — consumers catch and handle. Custom errors: `HubSpotHttpError` (HTTP failures with status/method/payload metadata), `HubSpotConfigError` (config issues), `FileSystemError` (FS operations with read/write context). Type predicates help identify timeouts, auth errors, and missing scopes.

# Key Consumers

- **HubSpot CLI** (`hubspot-cli`) — the primary consumer
- **VS Code extension** — uses config and API functions

Changes here affect all consumers. Be careful with breaking changes to exported function signatures.

# Testing Changes Against the CLI

## Option 1: Local linking (for active development)

1. In LDL: `yarn local-dev` — builds, runs `yarn link`, and watches for changes
2. In CLI: `yarn local-link` — interactive prompt to symlink local packages
3. Changes in LDL are reflected in the CLI after `yarn build`

To stop: run `yarn unlink` in LDL, then `yarn install --force` in CLI.

## Option 2: Experimental NPM release (for CI testing or sharing)

1. In LDL: `yarn release -v=prerelease -t=experimental`
2. In CLI: update `package.json` to the experimental version (e.g., `"@hubspot/local-dev-lib": "0.7.5-experimental.0"`) and run `yarn install --force`

Experimental releases are tagged on NPM so they won't be installed by default. See [PUBLISHING.md](../../PUBLISHING.md) for full details.
111 changes: 111 additions & 0 deletions .claude/rules/GENERAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# General Rules for @hubspot/local-dev-lib

These rules apply to ALL work in this repository. They override the managed org-wide general rules, which target HubSpot's Java/frontend stack and do not apply here.

## Stack

This is a **shared Node.js library** consumed by the HubSpot CLI and VS Code extension:

- **Language**: TypeScript (strict mode, ESM)
- **Testing**: Vitest (NOT Jest, NOT jasmine, NOT bend)
- **Package manager**: yarn (NOT npm, NOT Maven)
- **Build**: `yarn build` (NOT `mcp__devex-mcp-server__build_java`, NOT `bend`)
- **Linting**: `yarn lint` (eslint + prettier, zero warnings)
- **Formatting**: `yarn prettier:write`

Do NOT use Java, Maven, CHIRP, bend, Trellis, or other HubSpot backend/frontend platform tools in this repo.

## This is a Library, Not a CLI

This repo is consumed via `@hubspot/local-dev-lib` by the CLI and other tools. Key implications:

- No `process.exit()` — throw errors instead and let consumers handle them
- No direct user prompts — return data and let the CLI prompt
- No default exports — named exports only (enforced by ESLint `import/no-default-export`)
- All exports must be declared in `package.json` `exports` field to be consumable

## Look First, Then Build

Before creating or modifying anything, study how the codebase already does it.

### Before adding a new function

1. Check if it already exists in `lib/`, `config/`, `api/`, or `utils/`
2. Read comparable functions to understand the pattern
3. Follow the same structure exactly

### Before adding a new type

1. Check `types/` for existing type definitions
2. Follow the discriminated union pattern used for account types
3. Ensure the type is exported from the repo via `package.json` `exports`

### Before adding user-facing strings

1. All strings go in `lang/en.json` using nested keys
2. Use the `i18n()` function with `{{ variable }}` interpolation
3. Check existing keys for similar patterns

### When you find discrepancies

Stop and alert the user. Do NOT silently pick one pattern over another.

## Code Organization

- `api/` — HTTP calls to HubSpot services. Return `HubSpotPromise<T>`.
- `config/` — Config file read/write (YAML). Core account resolution logic.
- `constants/` — Shared constants
- `errors/` — Custom error classes (`HubSpotHttpError`, `HubSpotConfigError`, `FileSystemError`)
- `http/` — Axios wrapper with HubSpot auth
- `lang/` — i18n strings (`en.json`)
- `lib/` — Exported functions and modules (path, fileManager, logger, oauth, etc.). Anything exported from the repo should live here (excluding special cases like `config/`).
- `utils/` — Internal helper functions that are NOT exported from the repo
- `models/` — Business logic classes
- `types/` — TypeScript type definitions

## Error Handling

- Throw custom error classes from `errors/`, never return error objects
- Use `HubSpotHttpError` for API failures, `HubSpotConfigError` for config issues, `FileSystemError` for FS operations
- Never call `process.exit()` — that's the consumer's responsibility

## Build and Test Commands

- Build: `yarn build`
- All tests: `yarn test`
- Specific test: `yarn test <path>`
- Lint: `yarn lint`
- Format: `yarn prettier:write`
- Circular deps: `yarn circular-deps`
- Local dev (symlink): `yarn local-dev`

## After Making Code Changes

Always run these steps after modifying code:

1. `yarn prettier:write` — format all changed files
2. `yarn build` — verify TypeScript compiles
3. `yarn test <paths>` — run tests for changed files

## Code Style

- No `any` types
- No default exports
- No comments unless explicitly asked
- No classes where functions work
- Early returns for readability
- Single quotes, 2-space indent, trailing commas, 80-char lines
- NEVER use the word "comprehensive"

## What NOT to Use From Managed Rules
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, good idea 👍


The following tools and patterns from the org-wide managed rules do NOT apply here:

- `mcp__devex-mcp-server__build_java` — this is a yarn/node project
- `mcp__devex-mcp-server__get_onepager` — not required before edits in this repo
- `mcp__devex-mcp-server__search_docs` — not required before edits in this repo
- `bend` tools — not applicable
- `TrellisTools` — not applicable
- CHIRP discovery tools — not applicable
- `local_build_log_analyzer` agent — use `yarn test` / `yarn build` output directly
- `build_status_reporter` agent — use `yarn test` / `yarn build` output directly
42 changes: 42 additions & 0 deletions .claude/rules/TESTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
paths:
- '**/__tests__/**/*.ts'
- '!node_modules/**'
- '!dist/**'
---

# Test Guidelines

**Framework**: Vitest with globals enabled (all tests in `__tests__/` directories, co-located with source)

## Rules

- No try/catch blocks — use `expect().toThrow()` for error testing
- All cleanup in `afterEach()` using `vi.restoreAllMocks()`
- Never skip tests — fix or remove them
- Follow the naming convention of `[file-being-tested].test.ts`

## Mocking

- Use `vi.mock()` at module level for dependency mocking
- Use `vi.mocked()` for type-safe access to mocked functions
- When mock return values depend on input, use `mockImplementation()` over static `mockReturnValue()`
- Shared test helpers live in `__tests__/__utils__/` (e.g., `mockAxiosResponse()`)

## Structure

```typescript
import { vi, describe, it, expect } from 'vitest';

vi.mock('../dependency.js');

describe('moduleName', () => {
afterEach(() => {
vi.restoreAllMocks();
});

it('should do the thing', () => {
// arrange, act, assert
});
});
```
27 changes: 20 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,38 @@ yarn install
#### Generate a new build

```bash
yarn run build
yarn build
```

#### Testing with the HubSpot CLI

When contributing to local-dev-lib, you may also need to test the changes in the HubSpot CLI. To use a local version of local-dev-lib as a dependancy, use [yarn link](https://classic.yarnpkg.com/lang/en/docs/cli/link/).
When contributing to local-dev-lib, you may also need to test the changes in the HubSpot CLI.

1. Run `yarn run local-dev` in the local-dev-lib root to set up a symlink.
2. Run `yarn link @hubspot/local-dev-lib` in the hubspot-cli root and in `packages/cli` to use the symlinked local package.
##### Option 1: Local linking (for active development)

To stop using your local local-dev-lib, you can follow a similar process with [yarn unlink](https://classic.yarnpkg.com/en/docs/cli/unlink).
Use [yarn link](https://classic.yarnpkg.com/lang/en/docs/cli/link/) to symlink your local copy:

1. Run `yarn local-dev` in the local-dev-lib root to build and set up a symlink.
2. Run `yarn local-link` in the hubspot-cli-private root to use the symlinked local package.

To stop using your local local-dev-lib:

1. Run `yarn unlink` in the local-dev-lib root.
2. Run `yarn unlink` in the hubspot-cli root and in `packages/cli`.
2. Run `yarn install --force` in the hubspot-cli root to restore the published version.

##### Option 2: Experimental NPM release (for testing in CI or sharing with others)

Publish an experimental version to NPM from your branch:

1. Run `yarn release -v=prerelease --t=experimental`
2. In the CLI repo, update `package.json` to point to the experimental version (e.g., `"@hubspot/local-dev-lib": "0.7.4-experimental.0"`).
3. Run `yarn install --force` in the CLI repo to pull the experimental release.


## Merging

To merge, either create, or have a maintainer create a blank branch, and set your PRs base branch to the blank branch. Merge your PR into the blank branch, and ensure that it passes the build. Then merge the new branch into main.

## Documentation

- [Publishing Releases](./docs/PublishingReleases.md)
- [Publishing Releases](./PUBLISHING.md)
56 changes: 56 additions & 0 deletions PUBLISHING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Publishing Releases
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed docs/PublishingReleases.md since it was the only doc there and added this MD file at the root


Only contributors who are members of the HubSpot NPM organization can publish releases.

## Release Command

```bash
yarn release -v=<increment> -t=<tag>
```

### Parameters

- `-v, --versionIncrement` (required): `patch`, `minor`, `major`, or `prerelease`
- `-t, --tag` (required): `latest`, `next`, or `experimental`
- `-d, --dryRun`: Run through the process without actually publishing

### Examples

```bash
# Patch release to latest
yarn release -v=patch -t=latest

# Minor release to latest
yarn release -v=minor -t=latest

# Major release with breaking changes
yarn release -v=major -t=latest

# Experimental release from a feature branch
yarn release -v=prerelease -t=experimental

# Dry run (test without publishing)
yarn release -v=patch -t=latest -d
```

The script handles version bumping, git tagging, and publishing to NPM. It also validates the branch, checks the version against published versions, and opens the GitHub PR/release pages.

## Experimental Releases

Use experimental releases to test changes before merging, or to share in-progress work with the CLI team for CI validation.

1. From your feature branch: `yarn release -v=prerelease -t=experimental`
2. This publishes a version like `0.7.5-experimental.0` tagged as `experimental` on NPM
3. It won't be installed by default — consumers must explicitly reference the version

To use in the CLI:

```json
"@hubspot/local-dev-lib": "0.7.5-experimental.0"
```

Then run `yarn install --force` in the CLI repo.

## Local Testing (without publishing)

For active development, use local linking instead of publishing. See [CONTRIBUTING.md](./CONTRIBUTING.md) for details.
14 changes: 3 additions & 11 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@

These API utils simplify making requests to some of HubSpot's public API endpoints.

To use these API utils, you need to:

- Have a HubSpot configuration file, populated with valid account information
- Parse and store the configuration file in memory using the available config file utils

Once those steps are complete, you can make requests with these utils. The HTTP wrapper will handle all of the authentication requirements, such as formatting the header and token refreshes.
To use these API utils, you need to have a HubSpot configuration file populated with valid account information. The HTTP wrapper will automatically load the config and handle all of the authentication requirements, such as formatting the header and token refreshes, so there is no need to call `getConfig()` before making requests.

## Error handling

Expand All @@ -19,12 +14,9 @@ This library also includes utils that handle request errors. Check out the [Erro

Here's how to use the `addSecret` API util:

```js
const { loadConfig } = require('@hubspot/local-dev-lib/config');
const { addSecret } = require('@hubspot/local-dev-lib/api/secrets');
```typescript
import { addSecret } from '@hubspot/local-dev-lib/api/secrets';

// Parse and store the config file information
loadConfig();
const accountId = 12345;
await addSecret(accountId, 'my-secret-name', 'my-secret-value');
```
30 changes: 24 additions & 6 deletions config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,35 @@

## Config utils

The hubspot local development tooling uses a configuration file to store information about the HubSpot accounts that it has been granted access to.
The HubSpot local development tooling uses configuration files to store information about authenticated HubSpot accounts.

The config file is named `huspot.config.yml`.
### Global vs Local Config

There are two types of config files:

- **Global config** (`~/.hscli/config.yml`) — stores all authenticated accounts in one place. This is the recommended approach and is used by `hs account auth`.
- **Local config** (`hubspot.config.yml`) — a legacy per-project config file found via findup. Deprecated in favor of global config.

### Per-directory linking (`.hs/settings.json`)

In addition to the config files above, directories can have a `.hs/settings.json` file that scopes which accounts are active for that directory. This is created by `hs account link` and contains:

- `accounts` — array of account IDs linked to this directory
- `localDefaultAccount` — which linked account is the default for this directory

This lets developers work across multiple HubSpot accounts in different project directories without changing the global default.

## Using the config utils

There are a handful of standard config utils that anyone working in this library should be familiar with.

#### getConfig()

Locates and parses the hubspot config file. This function will automatically find the correct config file using the following criteria:
Locates and parses the HubSpot config file. This function will automatically find the correct config file using the following criteria:

1. Checks to see if a config was specified via environment variables (see below)
2. If no environment variables are present, looks for a global config file (located at `~/.hscli/config.yml`)
3. If no global config file is found, looks up the file tree for a local config file.
3. If no global config file is found, looks up the file tree for a local config file
4. If no local config file is found, throws an error

##### Custom config location environment variables
Expand All @@ -36,9 +50,13 @@ Safely writes updated values to the HubSpot config file.

Returns config data for a specific account, given the account's ID or name. Errors if an account is not found.

#### getAccountIfExist
#### getConfigAccountIfExists()

Returns config data for a specific account, given either a name or an ID. Returns null without erroring if an account is not found.

#### getLinkedOrAllConfigAccounts()

Returns config data for a specific account, given either a name or an ID. Returns null without erroring if an account is not found
Returns only accounts listed in `.hs/settings.json` when present, or all accounts when no settings file exists.

## Example config

Expand Down
11 changes: 0 additions & 11 deletions docs/PublishingReleases.md

This file was deleted.

Loading
Loading