kalup 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/{commands-Bk1w1oup.mjs → commands-CHucC_PV.mjs} +3458 -1065
- package/dist/commands.d.mts +1 -1
- package/dist/commands.mjs +1 -1
- package/dist/{context-8pARDYRR.d.mts → context-B7bI9BjL.d.mts} +52 -0
- package/dist/{host-BoqS00po.mjs → host-D1HtgrVd.mjs} +1 -1
- package/dist/host.d.mts +1 -1
- package/dist/host.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/schemas/ir-1.schema.json +41 -1
- package/dist/schemas/plan-1.schema.json +18 -0
- package/docs/apply.md +6 -4
- package/docs/config.md +38 -9
- package/docs/dictionary.md +3 -3
- package/docs/errors/E_BLUEPRINT_SCHEMA.md +2 -2
- package/docs/errors/E_DEFINITION_FIELD.md +1 -1
- package/docs/errors/E_DIR_AMBIGUOUS.md +4 -4
- package/docs/errors/E_DUPLICATE_LABEL.md +24 -0
- package/docs/errors/E_HS_PREFIX.md +3 -3
- package/docs/errors/E_HTTP.md +1 -1
- package/docs/errors/E_MISSING_EXPORT.md +2 -2
- package/docs/errors/E_PIPELINE_FIELD.md +23 -0
- package/docs/errors/E_PIPELINE_ID.md +17 -0
- package/docs/errors/E_PIPELINE_STAGES.md +17 -0
- package/docs/errors/E_PULL_INVALID.md +2 -2
- package/docs/errors/E_TOMBSTONE_ADDRESS.md +3 -3
- package/docs/errors/E_UNSUPPORTED_FILE.md +3 -3
- package/docs/errors/E_WRITE_IN_READ_MODE.md +1 -1
- package/docs/errors/E_WRITE_NOT_ALLOWED.md +1 -1
- package/docs/errors/W_LEGACY_DIR.md +2 -2
- package/docs/errors/W_LIMIT_UNREADABLE.md +2 -2
- package/docs/errors/W_RATE_HEADERS.md +1 -1
- package/docs/errors/W_RATE_LIMIT.md +1 -1
- package/docs/errors/W_WRITE_SCOPE.md +17 -0
- package/docs/plan.md +22 -9
- package/docs/pull.md +10 -6
- package/docs/rm.md +6 -4
- package/docs/snapshot.md +2 -2
- package/docs/targets.md +3 -3
- package/package.json +3 -3
|
@@ -4,14 +4,14 @@ A file under `hubspot/` that this version does not read. Exit 3.
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A `definePipeline` file outside `hubspot/pipelines/`, a `defineConfig` file under `hubspot/`, and a `defineRemoved` file anywhere under `hubspot/` except `hubspot/removed.ts`. With `dir` set in `kalup.config.ts`, the same paths under that folder. Kalup reports them instead of skipping them silently.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
11
|
-
Move
|
|
11
|
+
Move pipelines to `hubspot/pipelines/<object>.ts`, such as `hubspot/pipelines/deals.ts`. A `defineConfig` file belongs at the project root as `kalup.config.ts`, and tombstones belong in `hubspot/removed.ts`.
|
|
12
12
|
|
|
13
13
|
## Example
|
|
14
14
|
|
|
15
15
|
```
|
|
16
|
-
hubspot/
|
|
16
|
+
hubspot/deals.ts:1: E_UNSUPPORTED_FILE: a definePipeline file belongs under hubspot/pipelines/ (fix: move it to hubspot/pipelines/) (docs: errors/E_UNSUPPORTED_FILE.md)
|
|
17
17
|
```
|
|
@@ -4,7 +4,7 @@ Kalup refused to send a request to a write path through a read client. Exit 1. N
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
Every command that only reads (`pull`, `plan`, `status`, `compare`, `snapshot`) goes through a client that allows only paths tagged `read`, so none of them can reach a write path. Only `kalup apply` opens a write client, and it may send only the
|
|
7
|
+
Every command that only reads (`pull`, `plan`, `status`, `compare`, `snapshot`) goes through a client that allows only paths tagged `read`, so none of them can reach a write path. Only `kalup apply` opens a write client, and it may send only the writes on its own list (see `E_WRITE_NOT_ALLOWED`).
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ Kalup refused to send a write that this run may not send. Exit 1. Nothing was se
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
A run that writes gets an explicit list of the writes it may send. This version allows creating, updating and archiving properties and property groups, and nothing else: no custom object schema writes. A request to any other write path, or to a read path through the write channel, is refused before it leaves Kalup.
|
|
7
|
+
A run that writes gets an explicit list of the writes it may send. This version allows creating, updating and archiving properties and property groups, and creating, updating and deleting pipelines and stages, and nothing else: no custom object schema writes and no pipeline replace (PUT). A request to any other write path, or to a read path through the write channel, is refused before it leaves Kalup.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ A warning from every command that reads the project: the object files are in `ka
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
Kalup
|
|
7
|
+
Kalup keeps the object files in the folder `dir` in `kalup.config.ts` names, `hubspot/` by default. When `dir` is not set, `kalup/` holds .ts files and `hubspot/` holds none, Kalup keeps reading and writing `kalup/` and warns once per command. When both hold .ts files it stops with `E_DIR_AMBIGUOUS` instead.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -13,5 +13,5 @@ Add `dir: 'kalup'` to `kalup.config.ts` to keep the folder, or move `kalup/` to
|
|
|
13
13
|
## Example
|
|
14
14
|
|
|
15
15
|
```
|
|
16
|
-
kalup.config.ts: W_LEGACY_DIR: the object files are in kalup/, the
|
|
16
|
+
kalup.config.ts: W_LEGACY_DIR: the object files are in kalup/, the old default folder; the default is now hubspot/ (fix: add dir: 'kalup' to kalup.config.ts, or move kalup/ to hubspot/) (docs: errors/W_LEGACY_DIR.md)
|
|
17
17
|
```
|
|
@@ -4,11 +4,11 @@ A warning from `plan`, and from `apply` without a plan file: the plan creates pr
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
Before it plans a property create, `plan` reads HubSpot's Limits Tracking API for the custom property limit (W_LIMIT_HEADROOM).
|
|
7
|
+
Before it plans a property create, `plan` reads HubSpot's Limits Tracking API for the custom property limit (W_LIMIT_HEADROOM). That read answers 403 to a key with `crm.schemas.*` scopes only, and 200 once the key holds one `crm.objects.<object>.read` scope of any object (live runs, 2026-09-29 and 2026-10-01). The message gives HubSpot's status, or the issue code for another error or a 200 without a limit and a usage (`E_HTTP`). A create past the limit then fails in `apply` instead of being blocked in the plan.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
11
|
-
Add a `crm.objects.<object>.read` scope to the key, such as `crm.objects.companies.read` (Development > Keys > Service keys); it also lets the key read that object's records, which Kalup never requests.
|
|
11
|
+
Add a `crm.objects.<object>.read` scope to the key, such as `crm.objects.companies.read` (Development > Keys > Service keys); it also lets the key read that object's records, which Kalup never requests. One such scope, of any object, is enough for every Limits Tracking reading.
|
|
12
12
|
|
|
13
13
|
## Example
|
|
14
14
|
|
|
@@ -8,7 +8,7 @@ From `status`: HubSpot sent no rate-limit headers, so Kalup sends at most 8 requ
|
|
|
8
8
|
|
|
9
9
|
From `plan`: HubSpot sent no daily figure, or one that is not a whole number of requests (empty, fractional, negative), so `budget.dailyRemaining` is `null` and the plan cannot weigh its calls against the daily limit. From `apply`: the same, so it cannot refuse a run that would use more than half of what is left (`E_BUDGET`).
|
|
10
10
|
|
|
11
|
-
A service key's answers
|
|
11
|
+
A service key's answers carry the daily headers (live runs, 2026-09-29 and 2026-10-01), so this warning is not expected with one; the fallback stays for an answer without them.
|
|
12
12
|
|
|
13
13
|
## Fix
|
|
14
14
|
|
|
@@ -4,7 +4,7 @@ A warning from `pull`, `plan`, `snapshot` or `compare`: HubSpot sent no rate-lim
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
Kalup paces requests from HubSpot's rate-limit headers. Without them it sends at most 8 requests per second. A service key's answers
|
|
7
|
+
Kalup paces requests from HubSpot's rate-limit headers. Without them it sends at most 8 requests per second. A service key's answers carry them (live runs, 2026-09-29 and 2026-10-01), so this warning is not expected with one. `status` reports the same thing as W_RATE_HEADERS.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# W_WRITE_SCOPE
|
|
2
|
+
|
|
3
|
+
A warning from `status`: HubSpot's token introspection lists the read key's scopes, and a write scope apply needs is not among them. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
`status` reads the scopes a service key holds through HubSpot's token introspection (the key goes in the request body, as HubSpot requires) and checks the write scopes `init` lists against them by name, when apply writes with the same key. A separate write key is never resolved or sent, so its scopes stay unchecked and the line says so. When introspection answers nothing, status checks no write scope.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Add the scope the message names to the key (Development > Keys > Service keys), then run `kalup status` again.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
W_WRITE_SCOPE: the key in HUBSPOT_SANDBOX_KEY does not hold crm.schemas.companies.write, which apply needs for companies (fix: add the scope crm.schemas.companies.write to the key) (docs: errors/W_WRITE_SCOPE.md)
|
|
17
|
+
```
|
package/docs/plan.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Plan
|
|
2
2
|
|
|
3
|
-
`kalup plan [--target <name>] [--take config <address[#unit]>] [--out [<file>]] [--exit-code]` shows what apply would do to one target: a step per object, group and
|
|
3
|
+
`kalup plan [--target <name>] [--take config <address[#unit]>] [--out [<file>]] [--exit-code]` shows what apply would do to one target: a step per object, group, property, pipeline and stage config manages, `definition` overrides applied (config.md), then the releases and deletes tombstones ask for. It writes neither portal nor state. `--out <file>` saves the plan/1 document; `--out` alone saves it as `.kalup/plans/<target>-<planId>.json` and prints the path. `kalup apply <file>` applies either.
|
|
4
4
|
|
|
5
5
|
This page is the reference. For the walk-through with examples, see [kalup plan](https://kalup.dev/docs/commands/plan) and [Drift](https://kalup.dev/docs/concepts/drift) on the website.
|
|
6
6
|
|
|
@@ -9,8 +9,8 @@ This page is the reference. For the walk-through with examples, see [kalup plan]
|
|
|
9
9
|
1. Validate (`E_NO_CONFIG` exit 1, other issues exit 3), then pick the target (targets.md).
|
|
10
10
|
2. The read key, then the portal guard (`E_TARGET_PORTAL_MISMATCH`, exit 4).
|
|
11
11
|
3. State for that portal, `.kalup/state/portal-<portalId>.json`; an unusable file is `E_STATE_INVALID`.
|
|
12
|
-
4. Pull's read and scope, plus tombstoned properties. A 403 leaves that object unread (`E_SCOPE`).
|
|
13
|
-
5. Limits Tracking (403 without a `crm.objects.*` scope
|
|
12
|
+
4. Pull's read and scope, plus tombstoned properties, and the pipelines of each object whose files define one, with `pipelines: true`, or with a tombstoned pipeline or stage. A 403 leaves that object unread (`E_SCOPE`); on the pipelines list, only its pipelines.
|
|
13
|
+
5. Limits Tracking (403 without a `crm.objects.*` scope; `W_LIMIT_UNREADABLE` for property and pipeline creates), then the three `archived=true` lists of each object with a property create or an owned property HubSpot no longer holds. A group delete reads no archived list: only active properties block it. A 403 there is exit 1.
|
|
14
14
|
6. The plan, checked against `plan-1.schema.json` (`E_PLAN_SCHEMA` is a bug).
|
|
15
15
|
|
|
16
16
|
## State and the base
|
|
@@ -25,7 +25,7 @@ A state entry owns an address when it was created or adopted and its `id` is the
|
|
|
25
25
|
| `pulled` | present | `adopt` against the base pull recorded: a file edit since is a `config-change` |
|
|
26
26
|
| owned | absent | no step; listed in `missing` |
|
|
27
27
|
|
|
28
|
-
Each unit (a field, an option, an option's `label`, `hidden` and `description`, `options.order`) is classified against the base: `config-change` is written; `drift` (only HubSpot moved), `conflict` (both moved) and `diverged` (no base) are held. An option in config and the base that HubSpot dropped is drift; one config dropped is kept with a note. `removedOptions` and `options: 'exact'` remove, risk `risky`. Under takeover, `options` defaults to `exact`: such a removal is `destructive`, labelled `takeover`, and blocked without `allowDestroy` (`policy`) or after an incomplete read (`scope`).
|
|
28
|
+
Each unit (a field, an option, an option's `label`, `hidden` and `description`, `options.order`, a pipeline's `stages` order) is classified against the base: `config-change` is written; `drift` (only HubSpot moved), `conflict` (both moved) and `diverged` (no base) are held. An option in config and the base that HubSpot dropped is drift; one config dropped is kept with a note. `removedOptions` and `options: 'exact'` remove, risk `risky`. Under takeover, `options` defaults to `exact`: such a removal is `destructive`, labelled `takeover`, and blocked without `allowDestroy` (`policy`) or after an incomplete read (`scope`).
|
|
29
29
|
|
|
30
30
|
Converged units with a missing or outdated base go in `baseUnits`: apply records them without a write. An update that only holds or notes units is never applied.
|
|
31
31
|
|
|
@@ -41,17 +41,30 @@ With `drift: 'overwrite'`, drift and conflicts are written labelled `reverts-ui-
|
|
|
41
41
|
|
|
42
42
|
The first rule that matches: a `skip` override (no step, `coverage.excluded`); a `lookup` override; an unread object (`scope`, action `unknown`); a blocked parent or missing group (`dependency-blocked`); a property Kalup does not write (pull.md), whose fix makes it a `p.string` reference; for a create, a missing `name` override target, an archived property name (a create restores it; an archived group's name is created anew), or no limit room; HubSpot-defined or calculated, a `type` or `hasUniqueValue` difference, or read-only definition or options. A custom object schema is compared and never written: a missing one is blocked, and its differences are held or noted.
|
|
43
43
|
|
|
44
|
+
## Pipelines and stages
|
|
45
|
+
|
|
46
|
+
- Kalup writes the pipelines of deals, tickets and custom objects. Those of contacts, companies, appointments, services, listings, courses, orders and leads are compared, never written: a create is blocked `unsupported`, and a unit a step would write becomes a note.
|
|
47
|
+
- A pipeline create carries every config stage of the pipeline in one request, since HubSpot refuses a pipeline with no stage. The step lists them under `stages`; they get no steps of their own.
|
|
48
|
+
- A stage create in an existing pipeline goes after the last stage. When config places it before a stage HubSpot holds, the pipeline's step also sets the `stages` order; apply moves the stages one request at a time after the stage steps, so the budget counts two calls per stage of that order.
|
|
49
|
+
- Risk: a create is `safe`, unless its pipeline or stage ID is all digits, an ID HubSpot assigned in another portal: `risky`, with a note naming the `name` override and the portal's pipeline with the same label. A label, `displayOrder` or order change is `safe`. A change of `probability`, `ticketState` or `state` is `risky`: it changes how existing records count in forecasts and in open and closed reports. A delete is `destructive`.
|
|
50
|
+
- Blocked `unsupported`: a create whose pipeline ID another object's pipeline holds, or whose stage ID another pipeline of the object holds, naming the holder; a stage delete that would leave its pipeline with no stage, or a ticket pipeline with no closed stage. Blocked `scope`: a pipeline or stage of an object whose pipelines were not read or answered 403.
|
|
51
|
+
- The first pipeline created on a custom object carries a note: HubSpot adds its own properties `hs_pipeline` and `hs_pipeline_stage` to the object, for good. A deal or ticket pipeline create notes when the plan did not read the other object's pipelines: HubSpot keeps pipeline IDs unique across the two.
|
|
52
|
+
- A pipeline create is blocked `override` when the target skips every one of its stages. A stage tombstone that asks otherwise than its pipeline's is blocked with the reason. A destroy on a pipeline or stage Kalup does not write is blocked, with the release fix.
|
|
53
|
+
- A stage order shows by label in the plan text (`stage order: "Tasting", "Signed" -> ...`). The plan document keeps the IDs, and `stageLabels` on the step maps each to its label; it is display only and not approved.
|
|
54
|
+
- Takeover never deletes a pipeline or stage.
|
|
55
|
+
|
|
44
56
|
## Tombstones, missing and orphans
|
|
45
57
|
|
|
46
|
-
`hubspot/removed.ts` tombstones name properties and
|
|
58
|
+
`hubspot/removed.ts` tombstones name properties, groups, pipelines and stages:
|
|
47
59
|
|
|
48
60
|
- `release`: a `release` step drops the entry, even one naming another portal name; nothing is sent.
|
|
49
|
-
- `destroy`, present: a `delete`, risk `destructive`, labelled `existed-before-kalup` for an adopted resource, expecting every base unit's live value. Blocked with `policy` without `allowDestroy: true`, `unsupported` when it is not archivable or a group still holds
|
|
61
|
+
- `destroy`, present: a `delete`, risk `destructive`, labelled `existed-before-kalup` for an adopted resource, expecting every base unit's live value. Blocked with `policy` without `allowDestroy: true`, `unsupported` when it is not archivable or a group still holds active properties the plan does not delete (archived ones do not block: HubSpot archives a group once every property in it is archived), `not-owned` without an owning entry.
|
|
50
62
|
- `destroy`, absent by a complete read: a release expecting `exists: false`.
|
|
63
|
+
- A pipeline's tombstone covers its stages: they get no steps and no orphan notes. A pipeline or stage delete is permanent: HubSpot keeps no archive. HubSpot refuses it while a record sits in the stage, and apply names the stages and records.
|
|
51
64
|
|
|
52
|
-
Under takeover (config.md), a `delete` labelled `takeover` archives each custom property and group in the pull scope that config lacks, with a `mode` note naming the statement that asked for it; an option removal takeover asks for carries the note too. One `Takeover on <objects>` heading precedes the first such step and says whether each is confirmed at a terminal or all are blocked. Blocked with `policy` without `allowDestroy`, `scope` after an incomplete read, `unsupported` when not archivable or a group keeps
|
|
65
|
+
Under takeover (config.md), a `delete` labelled `takeover` archives each custom property and group in the pull scope that config lacks, with a `mode` note naming the statement that asked for it; an option removal takeover asks for carries the note too. One `Takeover on <objects>` heading precedes the first such step and says whether each is confirmed at a terminal or all are blocked. Blocked with `policy` without `allowDestroy`, `scope` after an incomplete read, `unsupported` when not archivable or a group keeps an active property. The `policy` fix leads with `kalup pull --target <t> --only <address>`, which keeps it in config, then `exclude` or `lifecycle: { options: 'additive' }` to leave it unmanaged, then `allowDestroy`. A delete expects every captured field's live value. Apply checks the same rules against its own read (a skipped group, a schema's properties, an empty group).
|
|
53
66
|
|
|
54
|
-
Releases follow the config steps, then deletes, the tombstones' and then takeover's, properties before groups.
|
|
67
|
+
Releases follow the config steps, then deletes, the tombstones' and then takeover's, properties before groups, then stages, then pipelines.
|
|
55
68
|
|
|
56
69
|
`missing` lists owned resources a complete read did not find, with `archived` (`null` for a group) and the exits; `orphans`, owned entries config no longer names, with both `kalup rm` commands; one naming another portal name, only `--release`.
|
|
57
70
|
|
|
@@ -61,7 +74,7 @@ Releases follow the config steps, then deletes, the tombstones' and then takeove
|
|
|
61
74
|
|
|
62
75
|
`writesHash` digests the target, portal, policy, state lineage and serial, `normVersions`, bindings, and each unblocked step with an effect: `address`, `action`, `transport`, `api`, `labels`, `baseUnits`, `desired`, `ignoreChanges`, `changes` (`unit`, `op`, `after`), `expect`. Titles, held values and notes stay out. `planId` is `pl_` plus its first 12 hex digits.
|
|
63
76
|
|
|
64
|
-
A write's `expect` holds the live value of each field it sets, the full options when any option changes,
|
|
77
|
+
A write's `expect` holds the live value of each field it sets, the full options when any option changes, a property's `type` and `fieldType`, and a pipeline's live stage order when the step sets it. A pipeline delete expects the full live stage list, so a stage added in HubSpot since the review stops it.
|
|
65
78
|
|
|
66
79
|
## Output
|
|
67
80
|
|
package/docs/pull.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Pull
|
|
2
2
|
|
|
3
|
-
`kalup pull [--target <name>]` reads one target and merges it into `hubspot/objects/*.ts` and the target's `definition` overrides. It never writes to the portal (`E_WRITE_IN_READ_MODE`), records in state what the files and the portal agree on (below), and sanitizes portal strings it prints.
|
|
3
|
+
`kalup pull [--target <name>]` reads one target and merges it into `hubspot/objects/*.ts`, `hubspot/pipelines/*.ts` and the target's `definition` overrides. It never writes to the portal (`E_WRITE_IN_READ_MODE`), records in state what the files and the portal agree on (below), and sanitizes portal strings it prints.
|
|
4
4
|
|
|
5
5
|
This page is the reference. For the walk-through with examples, see [kalup pull](https://kalup.dev/docs/commands/pull) on the website.
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ This page is the reference. For the walk-through with examples, see [kalup pull]
|
|
|
8
8
|
|
|
9
9
|
1. Validate, then pick the target (targets.md).
|
|
10
10
|
2. The read key, then the portal guard (targets.md).
|
|
11
|
-
3. The read: custom object schemas when `objects` names one (or with `--discover`), then each object's properties (sensitive ones too) and groups. A 403 on a list is `E_SCOPE`: that object is skipped and pull ends with `E_INCOMPLETE`.
|
|
11
|
+
3. The read: custom object schemas when `objects` names one (or with `--discover`), then each object's properties (sensitive ones too) and groups, and its pipelines when they are in scope (below). A 403 on a list is `E_SCOPE`: that object is skipped and pull ends with `E_INCOMPLETE`. A 403 on the pipelines list skips only the object's pipelines.
|
|
12
12
|
4. Normalize. A `hubspotDefined` property, or one HubSpot calculates with a field type other than `calculation_equation`, becomes a reference; a custom `calculation_equation` property is managed with its `calculationFormula`. An owner property (select or radio) is `p.owner`, a `phone_number` one `p.phoneNumber`, rich text a `p.string` with `fieldType: 'html'`. A property Kalup does not write becomes a `p.string` reference, with `W_UNSUPPORTED_TYPE`: a `type` or custom `fieldType` no builder carries (`object_coordinates`, a rollup), or a custom `externalOptions` property that is no owner select or radio. A display field is kept only on the types that show it, and a field holding HubSpot's default is left out. Options are ordered by `displayOrder`, missing or negative last.
|
|
13
13
|
5. Merge, with state where it owns a resource (below), then validate: an issue is `E_PULL_INVALID`, even with `--check`, and nothing is written.
|
|
14
14
|
6. Write the changed files and `hubspot/index.ts` as one, each first copied to `.kalup/history/<timestamp>/`; a failure puts all back (`E_PROJECT_WRITE`).
|
|
@@ -21,6 +21,7 @@ This page is the reference. For the walk-through with examples, see [kalup pull]
|
|
|
21
21
|
- `include: [...]`: properties by internal name, on top of `custom`; the only way in for HubSpot-defined ones the files do not define.
|
|
22
22
|
- `exclude: [...]`: internal names left out, `*` matching any run: never pulled, never archived by takeover. `include` wins over a pattern; a name in both is `E_SETTING_VALUE`, so `--discover` and the plan's notes say to take a listed name out of `exclude` rather than add it to `include`.
|
|
23
23
|
- `as`: the export name for the first pull, by default PascalCase singular (`line_items` to `LineItem`).
|
|
24
|
+
- `pipelines` (default `false`): every pipeline of the object. Without it, pull refreshes only the pipelines `hubspot/pipelines/<object>.ts` defines, and `--discover` lists the rest. `kalup init` sets it for deals and tickets.
|
|
24
25
|
|
|
25
26
|
Under takeover (config.md), pull ends with a line naming what a plan for the target would archive once the files are as pull leaves them, such as what `--only` left out.
|
|
26
27
|
|
|
@@ -45,6 +46,8 @@ A portal property in scope is written unless `hubspot/removed.ts` names it or it
|
|
|
45
46
|
|
|
46
47
|
**Groups**: a file group missing in the portal is kept, printed `missing in portal`; otherwise it takes the portal label. Every group a managed property uses is written, whatever `--only` says.
|
|
47
48
|
|
|
49
|
+
**Pipelines** merge by ID. A pipeline in both takes the portal's `label` and `displayOrder`; its stages take the portal's `label` and metadata field and follow the portal's order. A portal-only stage is added; a file-only stage is kept where it stood, printed `missing in portal`. A file pipeline the portal lacks is kept, printed `missing in portal`. With `pipelines: true`, a pipeline only the portal holds is appended to `hubspot/pipelines/<object>.ts`, the file created when needed, in `displayOrder` then ID order. Its export name is PascalCase of its label followed by `Pipeline` unless the label ends with it (`Sales Pipeline` to `SalesPipeline`); a stage's key is camelCase of its ID when the ID is a lowercase slug, else of its label, with `stage` in front of one starting with a digit. Names are made unique. The barrel re-exports every pipeline.
|
|
50
|
+
|
|
48
51
|
## With state
|
|
49
52
|
|
|
50
53
|
Where state holds agreed values for a resource (its base), each unit is compared with it, as `plan` does:
|
|
@@ -53,6 +56,7 @@ Where state holds agreed values for a resource (its base), each unit is compared
|
|
|
53
56
|
- Only config changed it: the file's value, printed `config change kept`.
|
|
54
57
|
- Both changed it: the file's value, printed `conflict, config kept`, a difference.
|
|
55
58
|
- An option config added stays, printed `config change kept`; one config dropped stays dropped. An option HubSpot removed stays, printed `removed in HubSpot, kept in config`.
|
|
59
|
+
- A stage order config changed keeps the file's order of the stages both hold, printed `config change kept` on the pipeline's `stages`.
|
|
56
60
|
- No base: the rules above.
|
|
57
61
|
|
|
58
62
|
`--accept <address[#unit]>` (repeatable, `*` as in `--only`) takes the portal side of those units, as each kept line prints; one matching nothing is `E_ACCEPT_UNMATCHED`.
|
|
@@ -61,16 +65,16 @@ After the files are written, and only after a complete read, pull records in sta
|
|
|
61
65
|
|
|
62
66
|
## Target overrides
|
|
63
67
|
|
|
64
|
-
- `skip: true`: not read (a group with its file properties), printed `skipped on this target, kept as written`, never a difference.
|
|
68
|
+
- `skip: true`: not read (a group with its file properties, a pipeline with its stages), printed `skipped on this target, kept as written`, never a difference.
|
|
65
69
|
- `name: '<portal name>'`: read under that name, written under the address. When the portal lacks it, the address is `missing in portal`, and a resource referring to its own name is printed `refers to a shadowed portal name, not written`. A portal holding both names is `E_OVERRIDE_AMBIGUOUS`.
|
|
66
70
|
- `definition`: a field it states merges into the override, not the object file. A field only its `ignoreChanges` names keeps the file's value, printed `ignored on this target, kept as written`. A move into a group config lacks keeps the override's group, printed `its portal group is not in config, override kept`, a difference: add the group to take it.
|
|
67
71
|
|
|
68
72
|
## Flags
|
|
69
73
|
|
|
70
|
-
- `--only <glob>`: merge only matching addresses. `*` matches any characters, `/` included: `property:companies/*`.
|
|
71
|
-
- `--discover`: list what is outside the scope, write nothing.
|
|
74
|
+
- `--only <glob>`: merge only matching addresses. `*` matches any characters, `/` included: `property:companies/*`, `stage:deals/renewals/*`.
|
|
75
|
+
- `--discover`: list what is outside the scope, write nothing: custom objects config does not name, properties, and the pipelines of each object without `pipelines: true` that the files do not define.
|
|
72
76
|
- `--check`: print the changes and `would write <file>`, write nothing. With `--exit-code`, exit 2 on any difference: a change line but `skipped`, `in hubspot/removed.ts`, a new property in a removed group, `config change kept` or `ignored on this target`, a `W_CODEC_MISMATCH`, or a file to rewrite.
|
|
73
|
-
- `--json`: `data` holds `target`, `portalId`, `objects` (counts and `changes[]` per object: `kind`, `address`, and `field`, `before` and `after` when one field differs; a kept value has the file's side in `before`, the portal's in `after`; `kind` is `added`, `changed`, `missing`, `local-only`, `excluded`, `shadowed`, `removed`, `removed-group`, `kept`, `conflict`, `removed-in-hubspot`, `ignored` or `override-group`), `files` and `state` (`path`, `recorded`, the resources whose base changed, and `serial`; absent with `--check` or after an incomplete read); with `--discover`, what is outside the scope.
|
|
77
|
+
- `--json`: `data` holds `target`, `portalId`, `objects` (counts and `changes[]` per object: `kind`, `address`, and `field`, `before` and `after` when one field differs; a kept value has the file's side in `before`, the portal's in `after`; `kind` is `added`, `changed`, `missing`, `local-only`, `excluded`, `shadowed`, `removed`, `removed-group`, `kept`, `conflict`, `removed-in-hubspot`, `ignored` or `override-group`), `files` and `state` (`path`, `recorded`, the resources whose base changed, and `serial`; absent with `--check` or after an incomplete read); with `--discover`, what is outside the scope: `objects`, `properties` and `pipelines` (pipeline IDs by object).
|
|
74
78
|
|
|
75
79
|
## Exit codes
|
|
76
80
|
|
package/docs/rm.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# Remove
|
|
2
2
|
|
|
3
|
-
`kalup rm <address> [--release] [--json]` takes a property
|
|
3
|
+
`kalup rm <address> [--release] [--json]` takes a property, property group, pipeline or stage out of config and writes its tombstone in `hubspot/removed.ts`. Absence never deletes: a resource dropped from an object file by hand stays in the portal and comes back on the next pull. `rm` is the only way to ask for a delete, and it works offline: it reads no key, sends no request and never touches state.
|
|
4
4
|
|
|
5
5
|
This page is the reference. For the walk-through with examples, see [kalup rm](https://kalup.dev/docs/commands/rm) on the website.
|
|
6
6
|
|
|
7
7
|
## What it writes
|
|
8
8
|
|
|
9
|
-
- The address must be `property:<object>/<name>` or `
|
|
9
|
+
- The address must be `property:<object>/<name>`, `group:<object>/<name>`, `pipeline:<object>/<id>` or `stage:<object>/<pipelineId>/<stageId>` (`E_TOMBSTONE_ADDRESS`, exit 3). Custom objects are not removed in this release.
|
|
10
10
|
- The property, or the group entry, leaves the export that defines it, in whichever file holds it. An export left with no properties keeps its groups; `defineObject` accepts it.
|
|
11
|
+
- A pipeline's export leaves `hubspot/pipelines/<object>.ts`, and the file goes when it held no other. Its one tombstone covers its stages. A stage leaves its pipeline's `stages`.
|
|
11
12
|
- `hubspot/removed.ts` gets `'<address>': { action: 'destroy' }`, or `'release'` with `--release`. An address already there gets its action changed; the same action writes nothing.
|
|
12
13
|
- An address config does not define (an orphan the plan lists) gets the tombstone alone.
|
|
13
14
|
- `hubspot/index.ts` is written again.
|
|
@@ -18,10 +19,11 @@ Before writing, rm validates the project as it would leave it; any issue is exit
|
|
|
18
19
|
|
|
19
20
|
- `destroy` for a resource with `lifecycle: { preventDestroy: true }` (`E_PREVENT_DESTROY`, exit 3). Remove preventDestroy first, or use `--release`.
|
|
20
21
|
- A group that config properties use, or a property a custom object schema in config names as a display, required or searchable property (`E_RM_DEPENDENTS`, exit 3). This applies to `--release` too.
|
|
22
|
+
- A pipeline's last stage, or a ticket pipeline's last closed stage (`E_PIPELINE_STAGES`, exit 3): HubSpot refuses both. Remove the pipeline instead, or mark another stage closed first.
|
|
21
23
|
|
|
22
24
|
## Destroy and release
|
|
23
25
|
|
|
24
|
-
A `destroy` tombstone becomes a delete in the next plan only when all of these hold: the portal's state owns the resource (Kalup created or adopted it there), the target sets `allowDestroy: true`, and a person at a terminal types the target name and the number of destructive steps when applying (apply.md). HubSpot archives a deleted property; it can be restored in HubSpot for 90 days.
|
|
26
|
+
A `destroy` tombstone becomes a delete in the next plan only when all of these hold: the portal's state owns the resource (Kalup created or adopted it there), the target sets `allowDestroy: true`, and a person at a terminal types the target name and the number of destructive steps when applying (apply.md). HubSpot archives a deleted property; it can be restored in HubSpot for 90 days. A deleted pipeline or stage is gone for good: HubSpot keeps no archive, and refuses the delete while a record sits in the stage.
|
|
25
27
|
|
|
26
28
|
A `release` tombstone stops Kalup managing the resource. The portal keeps it, the next apply drops its state entry without a request, and pull never writes it back into config (pull.md).
|
|
27
29
|
|
|
@@ -41,4 +43,4 @@ The output names what was removed and the plan command. `--json` data: `address`
|
|
|
41
43
|
|---|---|
|
|
42
44
|
| 0 | Written, or already so |
|
|
43
45
|
| 1 | `E_USAGE`, `E_NO_CONFIG`, `E_PROJECT_WRITE` |
|
|
44
|
-
| 3 | Config invalid, before or after the removal; `E_TOMBSTONE_ADDRESS`, `E_PREVENT_DESTROY`, `E_RM_DEPENDENTS` |
|
|
46
|
+
| 3 | Config invalid, before or after the removal; `E_TOMBSTONE_ADDRESS`, `E_PREVENT_DESTROY`, `E_RM_DEPENDENTS`, `E_PIPELINE_STAGES` |
|
package/docs/snapshot.md
CHANGED
|
@@ -7,7 +7,7 @@ This page is the reference. For the walk-through with examples, see [kalup snaps
|
|
|
7
7
|
## Order of work
|
|
8
8
|
|
|
9
9
|
1. Validate (exit 3), pick the target (targets.md), the read key, then the portal guard (exit 4).
|
|
10
|
-
2. Pull's read and scope: three properties lists per object, `skip` and `name` overrides applied. A 403 leaves that object unread.
|
|
10
|
+
2. Pull's read and scope: three properties lists per object, and its pipelines when they are in scope, `skip` and `name` overrides applied. A 403 leaves that object unread; on the pipelines list, only its pipelines.
|
|
11
11
|
3. Write the file, then print a summary.
|
|
12
12
|
|
|
13
13
|
## The file
|
|
@@ -40,7 +40,7 @@ Snapshot of target sandbox, portal 1111111, observed at 2026-09-23T10:15:30.123Z
|
|
|
40
40
|
Wrote .kalup/snapshots/sandbox/20260923T101530123Z.json
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
`--json` gives `file`, `target`, `portalId`, `observedAt`, `complete` and `counts` (objects read, groups and
|
|
43
|
+
`--json` gives `file`, `target`, `portalId`, `observedAt`, `complete` and `counts` (objects read, groups, properties, pipelines and stages held). The text names pipelines and stages only when the snapshot holds any.
|
|
44
44
|
|
|
45
45
|
## Exit codes
|
|
46
46
|
|
package/docs/targets.md
CHANGED
|
@@ -38,14 +38,14 @@ The first of several targets is never chosen, and no choice is remembered. Text
|
|
|
38
38
|
|
|
39
39
|
`credentials.read.env` names the variable that holds the read key. Without `credentials` it is `HUBSPOT_SERVICE_KEY`, the variable the target `init` writes names. `init` itself needs no key. The key comes from the environment, else from the project's `.env`. A missing key is `E_MISSING_KEY`. No output carries a key.
|
|
40
40
|
|
|
41
|
-
The key goes out as `Authorization: Bearer`. `init` prints the read scopes the pull scope needs; `status` checks each. Both recommend one `crm.objects.<object>.read` scope too: Limits Tracking
|
|
41
|
+
The key goes out as `Authorization: Bearer`. `init` prints the read scopes the pull scope needs; `status` checks each. Both recommend one `crm.objects.<object>.read` scope too: Limits Tracking answers 403 to `crm.schemas.*` scopes alone and 200 once one `crm.objects.<object>.read` scope of any object is added; without it `plan` cannot check the property limit (`W_LIMIT_UNREADABLE`). `credentials.write` names the key apply, `state rebuild --write` and `target rebind` use (apply.md); without it they use the read key. That key needs the read scopes and `crm.schemas.<object>.write` per object (`crm.schemas.custom.write` for custom objects), which `init` and `status` list. `status` reads the scopes a key holds through HubSpot's token introspection and checks the recommended scope and, when the write key is the read key, each write scope by name; a separate write key is never resolved by a read command, so its scopes stay unchecked.
|
|
42
42
|
|
|
43
43
|
## Overrides
|
|
44
44
|
|
|
45
45
|
`overrides` is keyed by address: `property:<object>/<name>`, `group:<object>/<name>` or `object:<name>`. A key that is not an address in config is `E_UNKNOWN_OVERRIDE`. Any field other than these four is `E_NOT_DATA`:
|
|
46
46
|
|
|
47
|
-
- `name: '<portal name>'`: the resource's internal name in this portal. Every read uses it; `plan` blocks it when the portal lacks it. `E_OVERRIDE_AMBIGUOUS` when the portal holds both names; `E_OVERRIDE_NAME` when two addresses would read one portal resource.
|
|
48
|
-
- `skip: true`: left out on this target by every read, a group with its config properties.
|
|
47
|
+
- `name: '<portal name>'`: the resource's internal name in this portal, or for a pipeline or stage its ID there. Every read uses it; `plan` blocks it when the portal lacks it. `E_OVERRIDE_AMBIGUOUS` when the portal holds both names; `E_OVERRIDE_NAME` when two addresses would read one portal resource.
|
|
48
|
+
- `skip: true`: left out on this target by every read, a group with its config properties, a pipeline with its stages.
|
|
49
49
|
- `definition: {...}`: the fields that differ on this target (config.md).
|
|
50
50
|
- `lookup`: no lookup resource is managed yet; `plan` blocks the resource, `compare` reports it unknown.
|
|
51
51
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kalup",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Configuration as code for HubSpot: pull, compare, plan and apply properties and property groups",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hubspot",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@oclif/core": "^5.
|
|
66
|
-
"@kalup/core": "0.
|
|
65
|
+
"@oclif/core": "^5.1.2",
|
|
66
|
+
"@kalup/core": "0.4.0"
|
|
67
67
|
},
|
|
68
68
|
"engines": {
|
|
69
69
|
"node": ">=22.13.1"
|