kalup 0.3.0 → 0.5.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 +4 -4
- package/dist/{commands-T4j_GVBD.mjs → commands-D_Q8-jc5.mjs} +7123 -2131
- package/dist/commands.d.mts +1 -1
- package/dist/commands.mjs +1 -1
- package/dist/{context-C1tH5K0x.d.mts → context-0mrva_6H.d.mts} +84 -0
- package/dist/{host-CDDeLV5T.mjs → host-BkBYlh_7.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 +681 -116
- package/dist/schemas/plan-1.schema.json +18 -0
- package/dist/schemas/state-1.schema.json +80 -18
- package/docs/apply.md +10 -5
- package/docs/config.md +64 -9
- package/docs/dictionary.md +3 -3
- package/docs/errors/E_ASSOCIATION_FIELD.md +21 -0
- package/docs/errors/E_ASSOCIATION_NAME.md +17 -0
- package/docs/errors/E_BLUEPRINT_REQUIRES.md +3 -3
- package/docs/errors/E_DUPLICATE_LABEL.md +24 -0
- 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_PLAN_DELETE.md +1 -1
- package/docs/errors/E_PLAN_INVALID.md +1 -1
- package/docs/errors/E_PLAN_RISK.md +1 -1
- package/docs/errors/E_PREVENT_DESTROY.md +1 -1
- package/docs/errors/E_TOMBSTONE_ADDRESS.md +3 -3
- package/docs/errors/E_TOMBSTONE_CONFLICT.md +1 -1
- package/docs/errors/E_UNKNOWN_OBJECT.md +1 -1
- 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_OBJECT_FIELD.md +25 -0
- package/docs/errors/W_OBJECT_PROPERTY.md +17 -0
- package/docs/plan.md +40 -8
- package/docs/pull.md +14 -7
- package/docs/rm.md +8 -5
- package/docs/snapshot.md +3 -3
- package/docs/targets.md +2 -2
- package/package.json +2 -2
|
@@ -4,7 +4,7 @@ A key under `objects` is neither a standard object nor a custom object in the po
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
A key that is not a standard object name (`contacts`, `companies`, `deals`, `line_items` and the rest, plural) is read as a custom object. None of the portal's custom objects has that name.
|
|
7
|
+
A key that is not a standard object name (`contacts`, `companies`, `deals`, `line_items` and the rest, plural) is read as a custom object. None of the portal's custom objects has that name, and config defines none either. A key a `defineCustomObject` in config backs is never this code: `pull` reports that object missing in portal and leaves its file as it is, `plan` creates it, and `compare` finds it on the config side only. A key whose object `hubspot/removed.ts` names is left out by `pull` too, whether or not HubSpot still holds it.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -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
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# W_OBJECT_FIELD
|
|
2
|
+
|
|
3
|
+
A warning from validate: a custom object in config has a name, a label or secondary display properties HubSpot refuses. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
A custom object name starts with a letter and holds only letters, digits and underscores, at most 50 characters, and its singular and plural labels hold at most 50 characters each. HubSpot refuses anything else on create (live runs, 2026-10-05). The name is the first argument of `defineCustomObject` and is permanent once HubSpot creates the object; the labels can change.
|
|
8
|
+
|
|
9
|
+
`secondaryDisplayProperties` holds at most two properties, each once: HubSpot refuses a third (live runs, 2026-10-01).
|
|
10
|
+
|
|
11
|
+
An object HubSpot holds already can break these rules, so validate only warns. `plan` blocks a create, or an update, that would send such a value, with reason `unsupported`.
|
|
12
|
+
|
|
13
|
+
## Fix
|
|
14
|
+
|
|
15
|
+
Choose a name HubSpot takes, such as `orchard_visit`, or shorten the label. For an object HubSpot holds already, the name in config is the portal's: keep it. List at most two secondary display properties, each once.
|
|
16
|
+
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
export const Visit = defineCustomObject('orchard-visit', {
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
hubspot/objects/orchard_visit.ts:3: W_OBJECT_FIELD: 'orchard-visit' is not a custom object name HubSpot takes: a letter, then letters, digits and underscores, at most 50 characters (fix: choose another name; HubSpot never changes a custom object name once it creates the object) (docs: errors/W_OBJECT_FIELD.md)
|
|
25
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# W_OBJECT_PROPERTY
|
|
2
|
+
|
|
3
|
+
A warning from validate: a custom object's `primaryDisplayProperty`, `secondaryDisplayProperties`, `requiredProperties` or `searchableProperties` names a property its object file does not list. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
HubSpot refuses a schema create or update that names a property it does not hold (live runs, 2026-10-05). A property HubSpot gives every custom object, such as `hs_object_id` or `hs_createdate`, needs no entry, and neither does any other `hs_` name, a prefix HubSpot reserves. Any other one the object file does not list may still be in the portal, outside the pull scope, so validate only warns. `plan` blocks a schema write that names a property neither the portal holds nor the plan creates, and raises this warning itself for an `hs_` name it takes as HubSpot's that is not one HubSpot is known to give every custom object.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Add the property to the object's `properties`: with its definition when Kalup should create it, or as a reference, `p.string('<name>')`, when HubSpot holds it already.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
hubspot/objects/orchard_visit.ts:3: W_OBJECT_PROPERTY: primaryDisplayProperty of object:orchard_visit names visit_title, which the object file does not list; HubSpot refuses a schema write naming a property it does not hold (fix: add visit_title to the object's properties, as a reference if HubSpot holds it already: p.string('visit_title')) (docs: errors/W_OBJECT_PROPERTY.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, stage and association 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; `W_LIMIT_UNREADABLE` for property 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.
|
|
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, and the labels of each object pair in association scope. A 403 leaves that object unread (`E_SCOPE`); on the pipelines list, only its pipelines; on a labels list, only that pair's associations.
|
|
13
|
+
5. Limits Tracking (403 without a `crm.objects.*` scope; `W_LIMIT_UNREADABLE` for property and pipeline creates; the association label counts of each pair the plan creates labels on, which only warn, `W_LIMIT_HEADROOM`, since HubSpot counts a label deleted in the last 40 seconds), 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
|
|
|
@@ -39,19 +39,51 @@ With `drift: 'overwrite'`, drift and conflicts are written labelled `reverts-ui-
|
|
|
39
39
|
|
|
40
40
|
## What blocks
|
|
41
41
|
|
|
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.
|
|
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.
|
|
43
|
+
|
|
44
|
+
## Custom objects
|
|
45
|
+
|
|
46
|
+
- A new custom object is one `create` step. Apply creates it with its name, labels and description, then its groups and properties, then sets the display, required and searchable properties, since HubSpot refuses those fields until the properties they name exist. The step's notes say what HubSpot adds to a new object (its own properties, the group `<name>_information` and associations with activities) and which fields wait for the properties. When the object file lists `<name>_information`, as a pull writes it once a property sits in it, that group's create step notes that apply gives HubSpot's group config's label instead.
|
|
47
|
+
- HubSpot's schemas list can show a custom object's values from before a write for some seconds after apply verified it, so a plan made right after such an apply can show the write as held drift. Plan again a minute later, and do not pull in that window: the pull would take the old values into config.
|
|
48
|
+
- An update writes the whole schema in one request. A create and every update are `safe`: the display fields change forms and record views, not record values. A delete is `destructive`.
|
|
49
|
+
- Blocked `unsupported`: a create whose name HubSpot holds archived, since the create would purge the archived object with its records and association labels (restore it in HubSpot and pull, purge it in HubSpot, or choose another name); a create whose name differs only in case from a custom object HubSpot holds, since HubSpot keeps names unique ignoring case; and a create or update whose display, required or searchable field names a property the portal does not hold and the plan does not create. When the plan blocks that property's create (a limit, for example), the object's step is blocked `dependency-blocked`, naming the property.
|
|
50
|
+
- `kalup rm object:<name>` writes one tombstone that covers everything on the object: the plan has one delete step, which archives the object. Its title says how many of the object's properties, groups and pipelines go with it, and that HubSpot keeps no properties on an archived custom object; apply stops before any write when its own read finds more than the plan did. When the key cannot read the object's pipelines, the step is blocked `scope`, since the plan cannot count them. HubSpot refuses the archive while the object holds records. A tombstone on something under the object that asks otherwise than the object's is blocked with the reason. Takeover never archives a custom object.
|
|
51
|
+
|
|
52
|
+
## Pipelines and stages
|
|
53
|
+
|
|
54
|
+
- 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.
|
|
55
|
+
- 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.
|
|
56
|
+
- 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.
|
|
57
|
+
- 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`.
|
|
58
|
+
- 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.
|
|
59
|
+
- 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.
|
|
60
|
+
- 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.
|
|
61
|
+
- 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.
|
|
62
|
+
- Takeover never deletes a pipeline or stage.
|
|
43
63
|
|
|
44
64
|
## Tombstones, missing and orphans
|
|
45
65
|
|
|
46
|
-
|
|
66
|
+
## Associations
|
|
67
|
+
|
|
68
|
+
- A create sends the name and both labels; a plain association's create sends an empty label. Apply creates a pair's plain association before its labels. A label created on a pair with a custom object and no plain association makes one too, under a name HubSpot picks: the step's note says so, and pull writes it once an object of the pair sets `associations: true`.
|
|
69
|
+
- An update sends both labels. Risk: a create and an update are `safe`; a delete is `destructive`, since records lose the association.
|
|
70
|
+
- A plain association delete is blocked `unsupported` while a label of its pair remains, one HubSpot does not name yet included, unless the plan deletes that label first: HubSpot refuses it.
|
|
71
|
+
- Blocked `unsupported` too: a plain association config gives a label, or a label config holds as a plain association (add an entry under another name instead); a create whose label the pair shows already from the same object, a plain create on a pair that holds a plain association, and a label create that fits HubSpot's cap only once a delete in this plan has run (deletes run last: apply the delete, then plan again).
|
|
72
|
+
- An association the read did not find, on a pair whose lists hold a type HubSpot's schema read does not name yet, is blocked: it may be that type. Plan again in a few minutes.
|
|
73
|
+
- HubSpot holds at most 50 labels per pair. Plan warns `W_LIMIT_HEADROOM` when its creates would pass the count Limits Tracking reports, and never blocks on it; HubSpot refuses the 51st with HTTP 437, which apply reports.
|
|
74
|
+
- Blocked `scope`: an association of a pair whose labels were not read or answered 403.
|
|
75
|
+
- Takeover never deletes an association. `notCovered` names association limits, which Kalup does not manage yet.
|
|
76
|
+
|
|
77
|
+
`hubspot/removed.ts` tombstones name custom objects, properties, groups, pipelines, stages and associations:
|
|
47
78
|
|
|
48
79
|
- `release`: a `release` step drops the entry, even one naming another portal name; nothing is sent.
|
|
49
80
|
- `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
81
|
- `destroy`, absent by a complete read: a release expecting `exists: false`.
|
|
82
|
+
- 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
83
|
|
|
52
84
|
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
85
|
|
|
54
|
-
Releases follow the config steps, then deletes, the tombstones' and then takeover's, properties before groups.
|
|
86
|
+
Releases follow the config steps, then deletes, the tombstones' and then takeover's, properties before groups, then stages, then pipelines, then association labels, then plain associations.
|
|
55
87
|
|
|
56
88
|
`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
89
|
|
|
@@ -61,7 +93,7 @@ Releases follow the config steps, then deletes, the tombstones' and then takeove
|
|
|
61
93
|
|
|
62
94
|
`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
95
|
|
|
64
|
-
A write's `expect` holds the live value of each field it sets, the full options when any option changes,
|
|
96
|
+
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
97
|
|
|
66
98
|
## Output
|
|
67
99
|
|
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`, `hubspot/associations.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. Then the labels of each object pair in association scope, both ways, and the schema read of each object of such a pair, for the internal names; a 403 on a labels list skips only that pair.
|
|
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,8 @@ 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.
|
|
25
|
+
- `associations` (default `false`): every association label and plain association between the object and the other objects under `objects`. Without it on either object of a pair, pull refreshes only the associations `hubspot/associations.ts` defines, and `--discover` lists the rest. `kalup init` sets it on every object it writes. HubSpot's own labels, and the plain association it defines between two standard objects, are never written.
|
|
24
26
|
|
|
25
27
|
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
28
|
|
|
@@ -28,7 +30,7 @@ A portal property in scope is written unless `hubspot/removed.ts` names it or it
|
|
|
28
30
|
|
|
29
31
|
## Merge rules
|
|
30
32
|
|
|
31
|
-
**Custom object schema**: its fields (config.md) take the portal value.
|
|
33
|
+
**Custom object schema**: its fields (config.md) take the portal value. A custom object config defines and the portal lacks is kept as the file has it, printed `missing in portal`; plan creates it. One `hubspot/removed.ts` names is never written back, with its pipelines, printed `in hubspot/removed.ts, not written back`, whether or not HubSpot still holds it. A key under `objects` that is neither a standard object, nor defined in config, nor in the portal is `E_UNKNOWN_OBJECT`.
|
|
32
34
|
|
|
33
35
|
**Properties already in the file**:
|
|
34
36
|
|
|
@@ -45,6 +47,10 @@ A portal property in scope is written unless `hubspot/removed.ts` names it or it
|
|
|
45
47
|
|
|
46
48
|
**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
49
|
|
|
50
|
+
**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.
|
|
51
|
+
|
|
52
|
+
**Associations** merge by name. An entry in both takes the portal's `label` and `inverseLabel`, and keeps its key and comments; `inverseLabel` is left out when it equals the label. A file entry the portal lacks is kept, printed `missing in portal`. With `associations: true` on an object of the pair, an association only the portal holds is added to `hubspot/associations.ts`, the file created when needed, as `Associations`: from the object whose key sorts first, keyed camelCase of its name. The barrel re-exports `Associations`. A label HubSpot's schema read does not name yet, a few minutes after a create in the HubSpot UI, is not written until it does; one Kalup created is named by the type IDs state records.
|
|
53
|
+
|
|
48
54
|
## With state
|
|
49
55
|
|
|
50
56
|
Where state holds agreed values for a resource (its base), each unit is compared with it, as `plan` does:
|
|
@@ -53,6 +59,7 @@ Where state holds agreed values for a resource (its base), each unit is compared
|
|
|
53
59
|
- Only config changed it: the file's value, printed `config change kept`.
|
|
54
60
|
- Both changed it: the file's value, printed `conflict, config kept`, a difference.
|
|
55
61
|
- 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`.
|
|
62
|
+
- 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
63
|
- No base: the rules above.
|
|
57
64
|
|
|
58
65
|
`--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 +68,16 @@ After the files are written, and only after a complete read, pull records in sta
|
|
|
61
68
|
|
|
62
69
|
## Target overrides
|
|
63
70
|
|
|
64
|
-
- `skip: true`: not read (a group with its file properties), printed `skipped on this target, kept as written`, never a difference.
|
|
71
|
+
- `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
72
|
- `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
73
|
- `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
74
|
|
|
68
75
|
## Flags
|
|
69
76
|
|
|
70
|
-
- `--only <glob>`: merge only matching addresses. `*` matches any characters, `/` included: `property:companies/*`.
|
|
71
|
-
- `--discover`: list what is outside the scope, write nothing.
|
|
77
|
+
- `--only <glob>`: merge only matching addresses. `*` matches any characters, `/` included: `property:companies/*`, `stage:deals/renewals/*`.
|
|
78
|
+
- `--discover`: list what is outside the scope, write nothing: custom objects config does not name, properties, the pipelines of each object without `pipelines: true` that the files do not define, and the associations between objects under `objects` that pull does not write.
|
|
72
79
|
- `--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.
|
|
80
|
+
- `--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`, `pipelines` (pipeline IDs by object) and `associations` (addresses).
|
|
74
81
|
|
|
75
82
|
## Exit codes
|
|
76
83
|
|
package/docs/rm.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Remove
|
|
2
2
|
|
|
3
|
-
`kalup rm <address> [--release] [--json]` takes a
|
|
3
|
+
`kalup rm <address> [--release] [--json]` takes a custom object, property, property group, pipeline, stage or association 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 `object:<name>`, `property:<object>/<name>`, `group:<object>/<name>`, `pipeline:<object>/<id>`, `stage:<object>/<pipelineId>/<stageId>` or `association:<from>/<to>/<name>` (`E_TOMBSTONE_ADDRESS`, exit 3).
|
|
10
|
+
- A custom object leaves config with everything on it: its export, every pipeline export on the object and every association entry with the object on either side, and each file left with no export or entry goes. Its one tombstone covers its groups, properties, pipelines, stages and associations. Its entry under `objects` in `kalup.config.ts` stays, so the plan reads the object.
|
|
10
11
|
- 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.
|
|
12
|
+
- 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`. An association's entry leaves `hubspot/associations.ts`, and the file goes when it held no other.
|
|
11
13
|
- `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
14
|
- An address config does not define (an orphan the plan lists) gets the tombstone alone.
|
|
13
15
|
- `hubspot/index.ts` is written again.
|
|
@@ -16,12 +18,13 @@ Before writing, rm validates the project as it would leave it; any issue is exit
|
|
|
16
18
|
|
|
17
19
|
## What it refuses
|
|
18
20
|
|
|
19
|
-
- `destroy` for a resource with `lifecycle: { preventDestroy: true }` (`E_PREVENT_DESTROY`, exit 3). Remove preventDestroy first, or use `--release`.
|
|
21
|
+
- `destroy` for a resource with `lifecycle: { preventDestroy: true }` (`E_PREVENT_DESTROY`, exit 3), or for a custom object while anything on it sets it, since the archive takes them along. Remove preventDestroy first, or use `--release`.
|
|
20
22
|
- 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.
|
|
23
|
+
- 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
24
|
|
|
22
25
|
## Destroy and release
|
|
23
26
|
|
|
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.
|
|
27
|
+
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. HubSpot archives a deleted custom object with what is on it, and refuses while the object holds records; Kalup never purges it. A deleted pipeline or stage is gone for good: HubSpot keeps no archive, and refuses the delete while a record sits in the stage. A deleted association label is gone for good too, and records lose it; HubSpot refuses to delete a plain association while a label of its pair remains, so remove the labels first.
|
|
25
28
|
|
|
26
29
|
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
30
|
|
|
@@ -41,4 +44,4 @@ The output names what was removed and the plan command. `--json` data: `address`
|
|
|
41
44
|
|---|---|
|
|
42
45
|
| 0 | Written, or already so |
|
|
43
46
|
| 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` |
|
|
47
|
+
| 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, its pipelines when they are in scope, and the associations of each pair in association scope, `skip` and `name` overrides applied. A 403 leaves that object unread; on the pipelines list, only its pipelines; on a labels list, only that pair's associations.
|
|
11
11
|
3. Write the file, then print a summary.
|
|
12
12
|
|
|
13
13
|
## The file
|
|
@@ -16,7 +16,7 @@ By default the file is `.kalup/snapshots/<target>/<stamp>.json` under the projec
|
|
|
16
16
|
|
|
17
17
|
The file is an `ir/1` document with `generator.frontend: 'portal'`, keys sorted, and U+007F to U+009F, U+2028 and U+2029 escaped:
|
|
18
18
|
|
|
19
|
-
- `resources`: what the read captured, under local addresses. Groups carry their label. A custom property carries its definition; a HubSpot-defined or calculated one at most its options. A custom object carries its labels, display property and property lists. Where one names a portal resource a `name` override shadows (a property's group, a schema's property), it reads `shadowed:<name>`, which never equals a config name.
|
|
19
|
+
- `resources`: what the read captured, under local addresses. Groups carry their label. A custom property carries its definition; a HubSpot-defined or calculated one at most its options. A custom object carries its labels, description, display property and property lists. Where one names a portal resource a `name` override shadows (a property's group, a schema's property), it reads `shadowed:<name>`, which never equals a config name.
|
|
20
20
|
- `observation`: the target's name and `portalId`, `observedAt`, the one timestamp an IR document may hold, and `coverage`.
|
|
21
21
|
|
|
22
22
|
`coverage` has `complete`, `otherObjects` (custom objects config does not name, or `unknown` when the schemas list was not read), `notCaptured` (documented fields Kalup drops) and one entry per object key: `status` (`read`, `unreadable`, `absent` or `excluded`), `missingScope` when unreadable, `objectTypeId`, the names out of scope, `unaddressable` (config names them), shadowed by a `name` override or unsupported (Kalup does not write it; `externalOptions` and `referencedObjectType` are kept), `unsupportedSchema` for a schema without a label, and the addresses `skip` and `name` overrides exclude or rename.
|
|
@@ -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, stages and associations held). The text names pipelines, stages and associations only when the snapshot holds any.
|
|
44
44
|
|
|
45
45
|
## Exit codes
|
|
46
46
|
|
package/docs/targets.md
CHANGED
|
@@ -44,8 +44,8 @@ The key goes out as `Authorization: Bearer`. `init` prints the read scopes the p
|
|
|
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, an association alone.
|
|
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.5.0",
|
|
4
4
|
"description": "Configuration as code for HubSpot: pull, compare, plan and apply properties and property groups",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hubspot",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@oclif/core": "^5.1.2",
|
|
66
|
-
"@kalup/core": "0.
|
|
66
|
+
"@kalup/core": "0.5.0"
|
|
67
67
|
},
|
|
68
68
|
"engines": {
|
|
69
69
|
"node": ">=22.13.1"
|