kalup 0.1.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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +68 -0
- package/bin/kalup.mjs +13 -0
- package/dist/commands-C--Xsk1p.mjs +16145 -0
- package/dist/commands.d.mts +191 -0
- package/dist/commands.mjs +2 -0
- package/dist/context-D10Syqfd.d.mts +1265 -0
- package/dist/host-BJWvo3sX.mjs +390 -0
- package/dist/host.d.mts +44 -0
- package/dist/host.mjs +2 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +14 -0
- package/dist/schemas/blueprint-1.schema.json +151 -0
- package/dist/schemas/blueprints-lock-1.schema.json +76 -0
- package/dist/schemas/ir-1.schema.json +379 -0
- package/dist/schemas/plan-1.schema.json +868 -0
- package/dist/schemas/state-1.schema.json +98 -0
- package/docs/apply.md +64 -0
- package/docs/blueprints.md +86 -0
- package/docs/compare.md +73 -0
- package/docs/config.md +88 -0
- package/docs/dictionary.md +43 -0
- package/docs/errors/E_ACCEPT_UNMATCHED.md +17 -0
- package/docs/errors/E_APPROVAL_REQUIRED.md +17 -0
- package/docs/errors/E_APPROVE_CREDENTIAL.md +17 -0
- package/docs/errors/E_APPROVE_MISMATCH.md +17 -0
- package/docs/errors/E_AUTH.md +17 -0
- package/docs/errors/E_BAD_CHAIN.md +21 -0
- package/docs/errors/E_BINDING_CHANGED.md +17 -0
- package/docs/errors/E_BIOME_CONFIG.md +17 -0
- package/docs/errors/E_BLUEPRINT_ADDED.md +17 -0
- package/docs/errors/E_BLUEPRINT_COLLISION.md +17 -0
- package/docs/errors/E_BLUEPRINT_INTEGRITY.md +17 -0
- package/docs/errors/E_BLUEPRINT_LOCK.md +17 -0
- package/docs/errors/E_BLUEPRINT_ORIGINAL.md +17 -0
- package/docs/errors/E_BLUEPRINT_REF.md +17 -0
- package/docs/errors/E_BLUEPRINT_REQUIRES.md +17 -0
- package/docs/errors/E_BLUEPRINT_SCHEMA.md +17 -0
- package/docs/errors/E_BLUEPRINT_SOURCE.md +17 -0
- package/docs/errors/E_BLUEPRINT_UNKNOWN.md +17 -0
- package/docs/errors/E_BUDGET.md +17 -0
- package/docs/errors/E_CANCELLED.md +21 -0
- package/docs/errors/E_CONFIG_EXISTS.md +17 -0
- package/docs/errors/E_DAILY_LIMIT.md +17 -0
- package/docs/errors/E_DEFAULT_TARGET.md +17 -0
- package/docs/errors/E_DUPLICATE_ADDRESS.md +17 -0
- package/docs/errors/E_DUPLICATE_ALIAS.md +24 -0
- package/docs/errors/E_DUPLICATE_KEY.md +24 -0
- package/docs/errors/E_DUPLICATE_OPTION.md +25 -0
- package/docs/errors/E_DUPLICATE_PORTAL.md +24 -0
- package/docs/errors/E_FIRST_PULL.md +18 -0
- package/docs/errors/E_HS_PREFIX.md +21 -0
- package/docs/errors/E_HTTP.md +17 -0
- package/docs/errors/E_INCOMPLETE.md +23 -0
- package/docs/errors/E_IR_SCHEMA.md +18 -0
- package/docs/errors/E_JOURNAL_WRITE.md +17 -0
- package/docs/errors/E_KEY_COLLISION.md +22 -0
- package/docs/errors/E_KEY_INVALID.md +19 -0
- package/docs/errors/E_LIFECYCLE.md +23 -0
- package/docs/errors/E_LOCKED.md +17 -0
- package/docs/errors/E_LOCK_DIR.md +17 -0
- package/docs/errors/E_MISSING_EXPORT.md +17 -0
- package/docs/errors/E_MISSING_KEY.md +17 -0
- package/docs/errors/E_NOT_DATA.md +21 -0
- package/docs/errors/E_NO_CONFIG.md +17 -0
- package/docs/errors/E_NO_TARGETS.md +17 -0
- package/docs/errors/E_OVERRIDE_AMBIGUOUS.md +21 -0
- package/docs/errors/E_OVERRIDE_DEFINITION.md +26 -0
- package/docs/errors/E_OVERRIDE_NAME.md +23 -0
- package/docs/errors/E_PLAN_DELETE.md +17 -0
- package/docs/errors/E_PLAN_DESTINATION.md +17 -0
- package/docs/errors/E_PLAN_DIGEST.md +17 -0
- package/docs/errors/E_PLAN_INVALID.md +17 -0
- package/docs/errors/E_PLAN_RISK.md +17 -0
- package/docs/errors/E_PLAN_SCHEMA.md +17 -0
- package/docs/errors/E_PLAN_STALE.md +17 -0
- package/docs/errors/E_PLAN_VERSION.md +18 -0
- package/docs/errors/E_POLICY_CHANGED.md +17 -0
- package/docs/errors/E_PORTAL_ID.md +17 -0
- package/docs/errors/E_PREVENT_DESTROY.md +17 -0
- package/docs/errors/E_PROJECT_WRITE.md +17 -0
- package/docs/errors/E_PROTECTED_SAVED_PLAN.md +17 -0
- package/docs/errors/E_PULL_INVALID.md +20 -0
- package/docs/errors/E_RATE_LIMIT.md +17 -0
- package/docs/errors/E_REBIND_STANDARD.md +17 -0
- package/docs/errors/E_REFERENCE_DEFINITION.md +21 -0
- package/docs/errors/E_RM_DEPENDENTS.md +17 -0
- package/docs/errors/E_SCOPE.md +17 -0
- package/docs/errors/E_SETTING_LEVEL.md +21 -0
- package/docs/errors/E_SETTING_VALUE.md +23 -0
- package/docs/errors/E_SNAPSHOT.md +17 -0
- package/docs/errors/E_STANDARD_OBJECT.md +21 -0
- package/docs/errors/E_STATE_CHANGED.md +19 -0
- package/docs/errors/E_STATE_CONFLICT.md +17 -0
- package/docs/errors/E_STATE_INVALID.md +17 -0
- package/docs/errors/E_STATE_SCHEMA.md +17 -0
- package/docs/errors/E_STATE_WRITE.md +19 -0
- package/docs/errors/E_STRICT_WITHOUT_OPTIONS.md +21 -0
- package/docs/errors/E_TAKE_UNMATCHED.md +19 -0
- package/docs/errors/E_TARGET_NAME.md +17 -0
- package/docs/errors/E_TARGET_PORTAL_MISMATCH.md +19 -0
- package/docs/errors/E_TARGET_REQUIRED.md +17 -0
- package/docs/errors/E_TOMBSTONE_ADDRESS.md +23 -0
- package/docs/errors/E_TOMBSTONE_CONFLICT.md +17 -0
- package/docs/errors/E_TYPE_FIELDTYPE.md +21 -0
- package/docs/errors/E_UNCERTAIN_WRITE.md +17 -0
- package/docs/errors/E_UNEXPECTED.md +17 -0
- package/docs/errors/E_UNKNOWN_BUILDER.md +21 -0
- package/docs/errors/E_UNKNOWN_GROUP.md +21 -0
- package/docs/errors/E_UNKNOWN_INCLUDE.md +17 -0
- package/docs/errors/E_UNKNOWN_OBJECT.md +17 -0
- package/docs/errors/E_UNKNOWN_OVERRIDE.md +17 -0
- package/docs/errors/E_UNKNOWN_TARGET.md +17 -0
- package/docs/errors/E_UNREACHABLE.md +17 -0
- package/docs/errors/E_UNSUPPORTED_FILE.md +17 -0
- package/docs/errors/E_USAGE.md +17 -0
- package/docs/errors/E_WRITE_IN_READ_MODE.md +17 -0
- package/docs/errors/E_WRITE_NOT_ALLOWED.md +17 -0
- package/docs/errors/W_BLUEPRINT_DOWNGRADE.md +17 -0
- package/docs/errors/W_CODEC_MISMATCH.md +18 -0
- package/docs/errors/W_INCOMPLETE.md +19 -0
- package/docs/errors/W_JSON_FIELDTYPE.md +17 -0
- package/docs/errors/W_KEY_COLLISION.md +17 -0
- package/docs/errors/W_LARGE_SCOPE.md +23 -0
- package/docs/errors/W_LIMIT_HEADROOM.md +19 -0
- package/docs/errors/W_LIMIT_UNREADABLE.md +17 -0
- package/docs/errors/W_MODE_SHADOWED.md +17 -0
- package/docs/errors/W_OVERRIDE_OPTION.md +17 -0
- package/docs/errors/W_PIN_EXPIRES.md +17 -0
- package/docs/errors/W_PREFIX.md +17 -0
- package/docs/errors/W_RATE_HEADERS.md +21 -0
- package/docs/errors/W_RATE_LIMIT.md +17 -0
- package/docs/errors/W_UNADDRESSABLE_NAME.md +21 -0
- package/docs/errors/W_UNFINISHED_APPLY.md +19 -0
- package/docs/errors/W_UNRESOLVED.md +17 -0
- package/docs/errors/W_UNSUPPORTED_TYPE.md +19 -0
- package/docs/errors/W_UNVERIFIED.md +17 -0
- package/docs/plan.md +85 -0
- package/docs/pull.md +83 -0
- package/docs/rm.md +44 -0
- package/docs/snapshot.md +52 -0
- package/docs/state.md +37 -0
- package/docs/targets.md +58 -0
- package/package.json +86 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# W_LIMIT_HEADROOM
|
|
2
|
+
|
|
3
|
+
A warning from `plan`: HubSpot reports room for fewer custom properties than the plan creates. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
Before it plans, `plan` reads HubSpot's Limits Tracking API for the custom property limit when the plan creates a property. It does not read the custom object limit, since custom object creates are unsupported. Properties count against the portal's limit and against their object's own limit, standard or custom, when HubSpot lists one. When the limit minus the usage is above 0 but below the number of creates, every create stays in the plan and the ones past the limit would fail. When nothing is left, each create is blocked with reason `limit` instead.
|
|
8
|
+
|
|
9
|
+
A reading HubSpot refuses, or answers without a limit and usage, is unreadable and blocks nothing (`W_LIMIT_UNREADABLE`).
|
|
10
|
+
|
|
11
|
+
## Fix
|
|
12
|
+
|
|
13
|
+
Leave some of the creates out on this target: add `{ '<address>': { skip: true } }` under `targets.<target>.overrides` for each one.
|
|
14
|
+
|
|
15
|
+
## Example
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
W_LIMIT_HEADROOM: the plan creates 3 custom properties and HubSpot reports room for 2 more (limit 1000, 998 in use) (fix: leave some of them out on this target with skip overrides under targets.sandbox.overrides) (docs: errors/W_LIMIT_HEADROOM.md)
|
|
19
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# W_LIMIT_UNREADABLE
|
|
2
|
+
|
|
3
|
+
A warning from `plan`, and from `apply` without a plan file: the plan creates properties, and HubSpot's property limit reading could not be read, so the plan did not check them against the limit. Exit stays 0. Nothing is blocked.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
Before it plans a property create, `plan` reads HubSpot's Limits Tracking API for the custom property limit (W_LIMIT_HEADROOM). On a developer test account (2026-09-29) that read answered 403 to a key with `crm.schemas.*` scopes only. 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
|
+
|
|
9
|
+
## Fix
|
|
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. Whether one such scope is enough is not yet confirmed live.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
W_LIMIT_UNREADABLE: HubSpot's property limit reading answered 403, so the plan could not check the property limit for 2 creates (fix: add a crm.objects.<object>.read scope, such as crm.objects.companies.read, to the key) (docs: errors/W_LIMIT_UNREADABLE.md)
|
|
17
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# W_MODE_SHADOWED
|
|
2
|
+
|
|
3
|
+
A warning from validate: a target's `mode` overrides the mode an object states. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
`targets.<target>.mode` wins over `objects.<object>.mode` on that target. When the two differ and the target states nothing for that object under `targets.<target>.objects`, the object's statement has no effect there, which is easy to miss when you read `objects` alone.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
If that is intended, state it for the object on the target, under `targets.<target>.objects.<object>.mode`, and the warning goes. Otherwise remove one of the two statements.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
kalup.config.ts:14: W_MODE_SHADOWED: targets.sandbox.mode 'addon' overrides objects.companies.mode 'takeover' on target sandbox (fix: state it under targets.sandbox.objects.companies.mode, or remove one of the two) (docs: errors/W_MODE_SHADOWED.md)
|
|
17
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# W_OVERRIDE_OPTION
|
|
2
|
+
|
|
3
|
+
A warning from validate: a target's definition override lists an option value the shared options of a `.strict()` enum lack. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
The app types and decodes an enum from the shared file alone. On that target HubSpot can store the new value, and a `.strict()` codec's `get` throws on it. A lenient enum reads it as `Unlisted`, so it gets no warning. A shared option the override leaves out is fine.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
If the app reads the property from that target, add the option to the shared options. Other targets then get it too, unless they override `options` as well. Or drop `.strict()`, and handle `Unlisted` in the app.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
kalup.config.ts:8: W_OVERRIDE_OPTION: property:deals/payment_terms on target sandbox: option 'net90' is not in the shared options, so the app's codec for paymentTerms throws on this value (fix: add it to the shared options if the app reads paymentTerms from target sandbox) (docs: errors/W_OVERRIDE_OPTION.md)
|
|
17
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# W_PIN_EXPIRES
|
|
2
|
+
|
|
3
|
+
A warning from `status` or `plan`: a HubSpot API version Kalup pins expires within 90 days. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
HubSpot supports each dated API version for 18 months. Kalup pins one version per API family and warns once per family as the end nears: `status` for every family it pins, `plan` for the families its steps use.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Upgrade `kalup` to a release that pins a newer version.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
W_PIN_EXPIRES: the crm.properties API pin 2026-09 expires 2028-03 (fix: upgrade kalup to a release that pins a newer version) (docs: errors/W_PIN_EXPIRES.md)
|
|
17
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# W_PREFIX
|
|
2
|
+
|
|
3
|
+
A warning from validate: a managed property's internal name lacks the project `prefix`. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
`prefix` in `kalup.config.ts` is set, and a property Kalup would own does not start with it. References are not checked.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Rename the property to carry the prefix, or clear `prefix`. A property already in HubSpot keeps its internal name; renaming means a new property.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
kalup/objects/companies.ts:14: W_PREFIX: 'soil_type' does not carry the project prefix 'orch_' (fix: rename it to orch_soil_type, or clear prefix in kalup.config.ts) (docs: errors/W_PREFIX.md)
|
|
17
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# W_RATE_HEADERS
|
|
2
|
+
|
|
3
|
+
A warning from `status`, `plan` or `apply` about HubSpot's rate-limit headers. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
From `status`: HubSpot sent no rate-limit headers, so Kalup sends at most 8 requests per second. The other commands report that as W_RATE_LIMIT.
|
|
8
|
+
|
|
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
|
+
|
|
11
|
+
A service key's answers carried the daily headers on a developer test account (2026-09-29). Other account types are not confirmed, so this warning may still appear.
|
|
12
|
+
|
|
13
|
+
## Fix
|
|
14
|
+
|
|
15
|
+
Nothing to fix.
|
|
16
|
+
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
W_RATE_HEADERS: HubSpot sent no daily rate-limit header, so the plan cannot weigh its calls against the daily limit (docs: errors/W_RATE_HEADERS.md)
|
|
21
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# W_RATE_LIMIT
|
|
2
|
+
|
|
3
|
+
A warning from `pull`, `plan`, `snapshot` or `compare`: HubSpot sent no rate-limit headers. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
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 carried them on a developer test account (2026-09-29); other account types are not confirmed. `status` reports the same thing as W_RATE_HEADERS.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Nothing to fix. A large read takes a little longer.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
W_RATE_LIMIT: HubSpot sent no rate-limit headers. Sending at most 8 requests per second. (docs: errors/W_RATE_LIMIT.md)
|
|
17
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# W_UNADDRESSABLE_NAME
|
|
2
|
+
|
|
3
|
+
A warning from `compare`, `plan` and `snapshot`: a portal group or property has a name no address can hold, so the read left it out. Exit stays 0, except as below.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
An address is `<type>:<path>` with no whitespace. HubSpot names its groups and properties without spaces, but its API does not promise it. A group whose name holds whitespace is not captured, nor is a property whose own name or group name holds it; the property is listed as out of scope.
|
|
8
|
+
|
|
9
|
+
A property config names in such a group is `unaddressable` in coverage instead: unknown, never absent, so `plan` never creates it. `compare` reports it `unknown` (`E_INCOMPLETE`, exit 1), `plan` blocks it, and the read is incomplete (`W_INCOMPLETE` in `snapshot`).
|
|
10
|
+
|
|
11
|
+
`pull` still writes such a name into config, and `validate` accepts it. `compare` and `plan` then stop with `E_UNEXPECTED`.
|
|
12
|
+
|
|
13
|
+
## Fix
|
|
14
|
+
|
|
15
|
+
Rename it in HubSpot to a name without spaces if you want Kalup to compare it. Otherwise nothing to fix.
|
|
16
|
+
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
W_UNADDRESSABLE_NAME: property 'a b' on companies has a name no address can hold, so it is not captured (fix: rename it in HubSpot to a name without spaces) (docs: errors/W_UNADDRESSABLE_NAME.md)
|
|
21
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# W_UNFINISHED_APPLY
|
|
2
|
+
|
|
3
|
+
A warning from `plan`, and from `apply` without a plan file, about the last apply to this portal. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
State records that the last apply did not finish (the process ended while it ran, so `lastApply.outcome` is still `running`), or that it left a write whose outcome is unknown (`uncertain`). What that apply wrote may already be in HubSpot. A property or group it created appears in this plan as an adopt step, because state has no entry for it yet, and a value it wrote may appear as a held value.
|
|
8
|
+
|
|
9
|
+
Recovery is this plan: nothing is repeated blindly, and nothing is adopted without your review.
|
|
10
|
+
|
|
11
|
+
## Fix
|
|
12
|
+
|
|
13
|
+
Review the adopt steps and held values before you apply this plan. `kalup status` shows the last apply and its plan ID, and the journal under `.kalup/journal/` lists each request it sent.
|
|
14
|
+
|
|
15
|
+
## Example
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
W_UNFINISHED_APPLY: the last apply (pl_3f9a1c07b2e4, at 2026-09-24T10:15:30.000Z) did not finish; resources it may have written appear below as adopt steps or held values (fix: review those steps before you apply this plan; kalup status shows the last apply) (docs: errors/W_UNFINISHED_APPLY.md)
|
|
19
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# W_UNRESOLVED
|
|
2
|
+
|
|
3
|
+
A warning from validate: a resource carries an `$unresolved` marker. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
The marker stands for a portal ID that could not be mapped to an address. In this version no command writes it and the config grammar has no place for it, so it should not appear.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
If you see it, report it with the command you ran. The `kalup bind` command its fix names does not exist yet.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
W_UNRESOLVED: workflow:renewal_reminder carries team ID 8841 from target production, which no address maps to (fix: run kalup bind workflow:renewal_reminder 8841 --target <target> to map it, or replace it with a $ref) (docs: errors/W_UNRESOLVED.md)
|
|
17
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# W_UNSUPPORTED_TYPE
|
|
2
|
+
|
|
3
|
+
A warning from any command that reads a portal: a property Kalup does not write. It reads as a `p.string` reference. Exit stays 0.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
Its HubSpot `type` has no builder (`phone_number`, `object_coordinates`, `json`, or a type Kalup does not know), it is a custom property whose `fieldType` its builder does not allow (a `string` with fieldType `html`, rich text), or it is a custom owner or `externalOptions` property, whose options HubSpot fills. Pull writes it as a `p.string` reference, with `.readonly()` when HubSpot marks its value read-only. A file entry that is already a reference keeps its builder; a managed one becomes a `p.string` reference. `plan` never creates, changes or archives it, and blocks a managed entry; `compare` compares the fields it has.
|
|
8
|
+
|
|
9
|
+
A HubSpot-defined or calculated owner or `externalOptions` property raises no warning: it is a `p.string` reference like any HubSpot-defined property.
|
|
10
|
+
|
|
11
|
+
## Fix
|
|
12
|
+
|
|
13
|
+
Nothing to fix in config. Read the value through the `p.string` reference, or through another builder the app chooses for a reference. To change the property, change it in HubSpot.
|
|
14
|
+
|
|
15
|
+
## Example
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
W_UNSUPPORTED_TYPE: property:companies/plot_shape has type object_coordinates and fieldType text, which Kalup does not write; read as a p.string reference (docs: errors/W_UNSUPPORTED_TYPE.md)
|
|
19
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# W_UNVERIFIED
|
|
2
|
+
|
|
3
|
+
HubSpot accepted a write, but reading it back did not show the value sent. Exit stays 0, except that the apply run exits 5: not every effect verified.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
After each write, apply reads the resource back and compares every unit it approved. A unit HubSpot stores differently, for example a label it rewrote, is unverified: its base does not move, and state records both values in the entry's `rewrites`. The next plan then notes "HubSpot stores X; change config to match" instead of writing the same value again. A write HubSpot acknowledged whose result no read showed within 60 seconds is unverified too.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Change config to the value HubSpot stores, then run `kalup plan --target <name>`. When nothing read back in time, run `kalup plan` to compare the portal with state again.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
W_UNVERIFIED: s1 Update property "Soil acidity" (soil_ph) on companies, set label: HubSpot stores label as "SOIL ACIDITY", not "Soil acidity" as sent (fix: change config to the value HubSpot stores, then run kalup plan --target sandbox) (docs: errors/W_UNVERIFIED.md)
|
|
17
|
+
```
|
package/docs/plan.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Plan
|
|
2
|
+
|
|
3
|
+
`kalup plan [--target <name>] [--take config <address[#unit]>] [--exit-code]` shows what apply would do to one target: a step per object, group and property config manages, `definition` overrides applied (config.md), then the releases and deletes tombstones ask for. It writes neither portal nor state.
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
## Order of work
|
|
8
|
+
|
|
9
|
+
1. Validate (`E_NO_CONFIG` exit 1, other issues exit 3), then pick the target (targets.md).
|
|
10
|
+
2. The read key, then the portal guard (`E_TARGET_PORTAL_MISMATCH`, exit 4).
|
|
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, developer test account 2026-09-29; `W_LIMIT_UNREADABLE` for property creates), then the three `archived=true` lists of each object with a property create, an owned property HubSpot no longer holds, or an owned group delete. A 403 there is exit 1.
|
|
14
|
+
6. The plan, checked against `plan-1.schema.json` (`E_PLAN_SCHEMA` is a bug).
|
|
15
|
+
|
|
16
|
+
## State and the base
|
|
17
|
+
|
|
18
|
+
A state entry owns an address when it was created or adopted and its `id` is the portal name the address resolves to (its `name` override, else its own). An entry naming another name owns nothing; the step notes it, and applying it replaces the entry.
|
|
19
|
+
|
|
20
|
+
| State | Portal | Step |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| none | absent | `create` |
|
|
23
|
+
| none | present | `adopt`: option adds written, differences held as `diverged` |
|
|
24
|
+
| owned | present | `update` against the base |
|
|
25
|
+
| `pulled` | present | `adopt` against the base pull recorded: a file edit since is a `config-change` |
|
|
26
|
+
| owned | absent | no step; listed in `missing` |
|
|
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`).
|
|
29
|
+
|
|
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
|
+
|
|
32
|
+
A held line names both exits: the portal side, `kalup pull --only <address>`, or `kalup pull --accept <address>#<unit>` for a conflict or an option HubSpot dropped; and config's side, `--take config <address>#<unit>`.
|
|
33
|
+
|
|
34
|
+
## Taking config's side
|
|
35
|
+
|
|
36
|
+
`--take config <selector>`, repeatable; several selectors may follow one `config`. A selector is an address, with the `*` of `--only`, and an optional unit; `#options` takes every option unit. It writes matching held units at risk `risky`, labelled `reverts-ui-edit` for drift and conflicts and `overwrites-portal` for `diverged` units; `--take config 'property:companies/*'` takes every held unit of the object's properties; an address alone also recreates a missing group, or a missing property HubSpot does not hold archived. No take recreates an archived property or writes a custom object (`unsupported`). A selector matching nothing is `E_TAKE_UNMATCHED`.
|
|
37
|
+
|
|
38
|
+
With `drift: 'overwrite'`, drift and conflicts are written labelled `reverts-ui-edit` at their change's risk. With `adopt: 'overwrite'`, `diverged` units are written, `risky`, labelled `overwrites-portal`. No policy recreates a missing resource.
|
|
39
|
+
|
|
40
|
+
## What blocks
|
|
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. A custom object schema is compared and never written: a missing one is blocked, and its differences are held or noted.
|
|
43
|
+
|
|
44
|
+
## Tombstones, missing and orphans
|
|
45
|
+
|
|
46
|
+
`kalup/removed.ts` tombstones name properties and groups:
|
|
47
|
+
|
|
48
|
+
- `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 properties (active or archived) the plan does not delete, `not-owned` without an owning entry.
|
|
50
|
+
- `destroy`, absent by a complete read: a release expecting `exists: false`.
|
|
51
|
+
|
|
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 a 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
|
+
|
|
54
|
+
Releases follow the config steps, then deletes, the tombstones' and then takeover's, properties before groups.
|
|
55
|
+
|
|
56
|
+
`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
|
+
|
|
58
|
+
## Header
|
|
59
|
+
|
|
60
|
+
`stateLineage` and `stateSerial` come from state, `null` without. `protected` defaults by account type (targets.md), `drift` and `adopt` to `hold`, `allowDestroy` to false, `yesLimit` to 25; `takeover` lists the objects in takeover mode. `budget.estimatedCalls` counts three calls per write plus apply's reads; `0` without effects.
|
|
61
|
+
|
|
62
|
+
`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
|
+
|
|
64
|
+
A write's `expect` holds the live value of each field it sets, the full options when any option changes, and a property's `type` and `fieldType`.
|
|
65
|
+
|
|
66
|
+
## Output
|
|
67
|
+
|
|
68
|
+
The header, then `Settings:` with what decides the steps on this target, from `plan.target`: the mode per object, `adopt`, `drift`, `allowDestroy`, `yesLimit`. Under each step, from plan/1 data only: a set unit as `unit: portal -> config`, `+ option` and `- option`, a create's label, group, fieldType and options, and each held unit with its class and config, portal and base (`held[].base`). With several targets, a step holding a `diverged` unit adds a `shared:` line: a pull writes the file every target shares, and a `definition` override keeps the portal's values on one target. Any `diverged` unit adds a line naming `adopt: 'overwrite'` and `--take config` with a glob. `Coverage` counts `skipped` resources (`skip` overrides, `coverage.excluded`), not what `exclude` leaves unread.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Settings: mode addon; adopt hold; drift hold; allowDestroy false; yesLimit 25
|
|
72
|
+
s5 risky [reverts-ui-edit] Update property "Billing status" (billing_status) on companies, set label
|
|
73
|
+
label: "Billing state" -> "Billing status"
|
|
74
|
+
held options[paused] drift: config {"value":"paused","label":"Paused"}, portal null, base {"label":"Paused"}. Take the portal side: kalup pull --target sandbox --accept 'property:companies/billing_status#options[paused]'; take config: kalup plan --target sandbox --take config 'property:companies/billing_status#options[paused]'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Exit codes
|
|
78
|
+
|
|
79
|
+
| Exit | When |
|
|
80
|
+
|---|---|
|
|
81
|
+
| 0 | Planned, blocked steps, `E_SCOPE` and warnings included |
|
|
82
|
+
| 2 | `--exit-code` and anything pending: a step to apply, a blocked or manual step (they count as pending, and the last line says so), a held unit, a resource missing in HubSpot, an incomplete read |
|
|
83
|
+
| 1 | `E_USAGE`, `E_NO_CONFIG`, `E_TARGET_REQUIRED`, `E_CANCELLED`, `E_MISSING_KEY`, `E_STATE_INVALID`, `E_TAKE_UNMATCHED`, `E_OVERRIDE_AMBIGUOUS`, a failed request, `E_PLAN_SCHEMA` |
|
|
84
|
+
| 3 | Config invalid, `E_NO_TARGETS`, `E_UNKNOWN_OBJECT` |
|
|
85
|
+
| 4 | `E_TARGET_PORTAL_MISMATCH` |
|
package/docs/pull.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Pull
|
|
2
|
+
|
|
3
|
+
`kalup pull [--target <name>]` reads one target and merges it into `kalup/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.
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
## Order of work
|
|
8
|
+
|
|
9
|
+
1. Validate, then pick the target (targets.md).
|
|
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`.
|
|
12
|
+
4. Normalize. A `hubspotDefined` or `calculated` property becomes a reference. A property Kalup does not write becomes a `p.string` reference, with `W_UNSUPPORTED_TYPE`: a `type` or custom `fieldType` no builder carries (`phone_number`, rich text `html`), or a custom owner or `externalOptions` property. A HubSpot-defined owner property is a `p.string` reference with no warning. Options are ordered by `displayOrder`, missing or negative last.
|
|
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
|
+
6. Write the changed files and `kalup/index.ts` as one, each first copied to `.kalup/history/<timestamp>/`; a failure puts all back (`E_PROJECT_WRITE`).
|
|
15
|
+
|
|
16
|
+
## Scope
|
|
17
|
+
|
|
18
|
+
`objects.<key>` in `kalup.config.ts` decides what pull writes:
|
|
19
|
+
|
|
20
|
+
- `custom` (default `true`): every property that is not HubSpot-defined.
|
|
21
|
+
- `include: [...]`: properties by internal name, on top of `custom`; the only way in for HubSpot-defined ones.
|
|
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
|
+
- `as`: the export name for the first pull, by default PascalCase singular (`line_items` to `LineItem`).
|
|
24
|
+
|
|
25
|
+
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
|
+
A portal property in scope is written unless `kalup/removed.ts` names it or its group, printed `in kalup/removed.ts, not written back` or `its group is in kalup/removed.ts, not written`, never a difference. A file property HubSpot moved into such a group keeps its group, a difference. Pull removes nothing. A file property outside the scope is kept, printed `out of scope, not refreshed`.
|
|
28
|
+
|
|
29
|
+
## Merge rules
|
|
30
|
+
|
|
31
|
+
**Custom object schema**: its fields (config.md) take the portal value.
|
|
32
|
+
|
|
33
|
+
**Properties already in the file**:
|
|
34
|
+
|
|
35
|
+
- Not in the portal, or archived there: kept, printed `missing in portal`.
|
|
36
|
+
- Kalup does not write it (step 4): a reference stays as written, whatever its builder; a full definition becomes a `p.string` reference, a `definition` change.
|
|
37
|
+
- Builder conflicts with the portal `type` or `fieldType` (`checkbox` on `p.enum`): kept, `W_CODEC_MISMATCH`.
|
|
38
|
+
- Portal says reference: the definition becomes options-only (`value`, `label`, the file's `as`), or none; `.managed(false)` stays as written.
|
|
39
|
+
- Portal says managed: `label`, `group`, `fieldType`, `description`, `hasUniqueValue` and `formField` take the portal value. A field the file states stays, even `description: ''`.
|
|
40
|
+
- Key, builder kind, chain, comments, the `p.json` validator and `lifecycle` come from the file. Pull adds `.readonly()` where HubSpot marks the value read-only (`modificationMetadata.readOnlyValue`), and never removes one.
|
|
41
|
+
|
|
42
|
+
**Options** merge by `value`. A member in both takes the portal's `label`, `hidden` and `description` and keeps the file's `as`. Members follow portal order. A portal-only member is added; a file-only one is kept, printed `only in config`.
|
|
43
|
+
|
|
44
|
+
**New properties** get camelCase of the internal name as key (the internal name, with `W_KEY_COLLISION`, when taken) and `.readonly()` when calculated or when HubSpot marks the value read-only.
|
|
45
|
+
|
|
46
|
+
**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
|
+
## With state
|
|
49
|
+
|
|
50
|
+
Where state holds agreed values for a resource (its base), each unit is compared with it, as `plan` does:
|
|
51
|
+
|
|
52
|
+
- Only the portal changed it: the portal's value.
|
|
53
|
+
- Only config changed it: the file's value, printed `config change kept`.
|
|
54
|
+
- Both changed it: the file's value, printed `conflict, config kept`, a difference.
|
|
55
|
+
- 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`.
|
|
56
|
+
- No base: the rules above.
|
|
57
|
+
|
|
58
|
+
`--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`.
|
|
59
|
+
|
|
60
|
+
After the files are written, and only after a complete read, pull records in state the base of every unit the files and the portal agree on, for the addresses `--only` selects: under the portal lock (`E_LOCKED`) with the serial check, as apply saves. An owned entry keeps its origin; an address no entry owns gets origin `pulled`, which owns nothing: the next plan still adopts it, but compares against that base, so a later file edit is a `config-change`, not `diverged`. A field the files leave out because HubSpot holds its default (an empty description, `formField` off, an option with no description) is recorded at that default, so adding it to the file later is a `config-change` too. A unit that still differs keeps its base. `--check` and `--discover` record nothing and take no lock. A plan saved before the pull no longer applies (`E_STATE_CHANGED`).
|
|
61
|
+
|
|
62
|
+
## Target overrides
|
|
63
|
+
|
|
64
|
+
- `skip: true`: not read (a group with its file properties), printed `skipped on this target, kept as written`, never a difference.
|
|
65
|
+
- `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
|
+
- `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
|
+
|
|
68
|
+
## Flags
|
|
69
|
+
|
|
70
|
+
- `--only <glob>`: merge only matching addresses. `*` matches any characters, `/` included: `property:companies/*`.
|
|
71
|
+
- `--discover`: list what is outside the scope, write nothing.
|
|
72
|
+
- `--check`: print the changes and `would write <file>`, write nothing. With `--exit-code`, exit 2 on any difference: a change line but `out of scope`, `skipped`, `in kalup/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`, `out-of-scope`, `excluded`, `shadowed`, `removed`, `removed-group`, `kept`, `conflict`, `removed-in-hubspot`, `ignored` or `override-group`), `files` and `state` (`recorded`, the resources whose base changed, and `serial`; absent with `--check` or after an incomplete read); with `--discover`, what is outside the scope.
|
|
74
|
+
|
|
75
|
+
## Exit codes
|
|
76
|
+
|
|
77
|
+
| Exit | When |
|
|
78
|
+
|---|---|
|
|
79
|
+
| 0 | Done, warnings included |
|
|
80
|
+
| 1 | `E_USAGE`, `E_NO_CONFIG`, `E_TARGET_REQUIRED`, `E_MISSING_KEY`, `E_STATE_INVALID`, `E_ACCEPT_UNMATCHED`, a failed request, `E_INCOMPLETE`, `E_PROJECT_WRITE`, `E_LOCKED`, `E_STATE_CONFLICT`, `E_STATE_WRITE` |
|
|
81
|
+
| 2 | `--check --exit-code` found a difference |
|
|
82
|
+
| 3 | Any validate issue, `E_NO_TARGETS`, `E_UNKNOWN_OBJECT`, `E_UNKNOWN_INCLUDE`, `E_PULL_INVALID` |
|
|
83
|
+
| 4 | `E_TARGET_PORTAL_MISMATCH` |
|
package/docs/rm.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Remove
|
|
2
|
+
|
|
3
|
+
`kalup rm <address> [--release] [--json]` takes a property or property group out of config and writes its tombstone in `kalup/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
|
+
|
|
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
|
+
|
|
7
|
+
## What it writes
|
|
8
|
+
|
|
9
|
+
- The address must be `property:<object>/<name>` or `group:<object>/<name>` (`E_TOMBSTONE_ADDRESS`, exit 3). Custom objects are not removed in this release.
|
|
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
|
+
- `kalup/removed.ts` gets `'<address>': { action: 'destroy' }`, or `'release'` with `--release`. An address already there gets its action changed; the same action writes nothing.
|
|
12
|
+
- An address config does not define (an orphan the plan lists) gets the tombstone alone.
|
|
13
|
+
- `kalup/index.ts` is written again.
|
|
14
|
+
|
|
15
|
+
Before writing, rm validates the project as it would leave it; any issue is exit 3 and nothing is written. Each file is copied to `.kalup/history/<timestamp>/`, then all of them are written through one staged write: temporary files first, then a rename each, and on a failure every file is put back (`E_PROJECT_WRITE`).
|
|
16
|
+
|
|
17
|
+
## What it refuses
|
|
18
|
+
|
|
19
|
+
- `destroy` for a resource with `lifecycle: { preventDestroy: true }` (`E_PREVENT_DESTROY`, exit 3). Remove preventDestroy first, or use `--release`.
|
|
20
|
+
- 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.
|
|
21
|
+
|
|
22
|
+
## Destroy and release
|
|
23
|
+
|
|
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.
|
|
25
|
+
|
|
26
|
+
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
|
+
|
|
28
|
+
## Next
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
kalup rm property:companies/legacy_score
|
|
32
|
+
kalup plan --target sandbox
|
|
33
|
+
kalup apply --target sandbox
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The output names what was removed and the plan command. `--json` data: `address`, `action`, `files` written, `from` (the object file) and `previous` (the action before, when there was one).
|
|
37
|
+
|
|
38
|
+
## Exit codes
|
|
39
|
+
|
|
40
|
+
| Exit | When |
|
|
41
|
+
|---|---|
|
|
42
|
+
| 0 | Written, or already so |
|
|
43
|
+
| 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` |
|
package/docs/snapshot.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Snapshot
|
|
2
|
+
|
|
3
|
+
`kalup snapshot [--target <name>]` reads one target and saves what it read, with a record of what the read covered. It never writes to the portal or to a config file.
|
|
4
|
+
|
|
5
|
+
This page is the reference. For the walk-through with examples, see [kalup snapshot](https://kalup.dev/docs/commands/snapshot) on the website.
|
|
6
|
+
|
|
7
|
+
## Order of work
|
|
8
|
+
|
|
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.
|
|
11
|
+
3. Write the file, then print a summary.
|
|
12
|
+
|
|
13
|
+
## The file
|
|
14
|
+
|
|
15
|
+
By default the file is `.kalup/snapshots/<target>/<stamp>.json` under the project root, stamped when the read finished, as in `20260923T101530123Z`. A target name that is not a safe directory name on every system becomes a slug plus the first 8 hex digits of the name's SHA-256. `--out <file>` writes exactly there, relative to the current directory. A snapshot never replaces a file: one that exists is `E_SNAPSHOT`, exit 1.
|
|
16
|
+
|
|
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
|
+
|
|
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.
|
|
20
|
+
- `observation`: the target's name and `portalId`, `observedAt`, the one timestamp an IR document may hold, and `coverage`.
|
|
21
|
+
|
|
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.
|
|
23
|
+
|
|
24
|
+
A snapshot holds the scope and the captured fields, and is no backup of the portal.
|
|
25
|
+
|
|
26
|
+
## Using it
|
|
27
|
+
|
|
28
|
+
- `kalup compare <file> <target>`: drift since the snapshot.
|
|
29
|
+
- `kalup compare <older file> <newer file>`: two reads, no request or project needed.
|
|
30
|
+
- `kalup docs <file>`: the data dictionary of the read (dictionary.md).
|
|
31
|
+
|
|
32
|
+
## Incomplete reads
|
|
33
|
+
|
|
34
|
+
The file is still written, with `complete: false`, the objects not read marked `unreadable` and `unaddressable` properties listed, and `W_INCOMPLETE` names the fix. The exit stays 0.
|
|
35
|
+
|
|
36
|
+
## Output
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Snapshot of target sandbox, portal 1111111, observed at 2026-09-23T10:15:30.123Z: 2 objects, 3 groups, 10 properties
|
|
40
|
+
Wrote .kalup/snapshots/sandbox/20260923T101530123Z.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`--json` gives `file`, `target`, `portalId`, `observedAt`, `complete` and `counts` (objects read, groups and properties held).
|
|
44
|
+
|
|
45
|
+
## Exit codes
|
|
46
|
+
|
|
47
|
+
| Exit | When |
|
|
48
|
+
|---|---|
|
|
49
|
+
| 0 | Written, an incomplete read included |
|
|
50
|
+
| 1 | `E_USAGE`, `E_NO_CONFIG`, `E_TARGET_REQUIRED`, `E_CANCELLED`, `E_MISSING_KEY`, `E_OVERRIDE_AMBIGUOUS`, `E_SNAPSHOT` (the file exists), a failed request |
|
|
51
|
+
| 3 | Config invalid, `E_NO_TARGETS`, `E_UNKNOWN_OBJECT` |
|
|
52
|
+
| 4 | `E_TARGET_PORTAL_MISMATCH` |
|
package/docs/state.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# State
|
|
2
|
+
|
|
3
|
+
Kalup keeps what it knows about each portal in `.kalup/state/portal-<portalId>.json`: which resources it created or adopted there, and per unit the value config and the portal last agreed on (the base). `plan` reads it; `apply`, `state rebuild --write` and `target rebind` write it, and `pull` (so `init`) records the units the files and the portal agree on (pull.md): an address no entry owns gets origin `pulled`, which owns nothing. Every worktree of one clone shares it; `KALUP_STATE_DIR` moves it. Never edit it by hand. `kalup status` prints its path, lineage, serial and last apply per target; "an apply did not finish" means run `kalup plan`.
|
|
4
|
+
|
|
5
|
+
This page is the reference. For the walk-through with examples, see [kalup state](https://kalup.dev/docs/commands/state) on the website.
|
|
6
|
+
|
|
7
|
+
## state rebuild
|
|
8
|
+
|
|
9
|
+
`kalup state rebuild [--target <name>] [--write] [--json]` rebuilds state from what the portal holds by name.
|
|
10
|
+
|
|
11
|
+
Without `--write` it is read-only: it checks the read key's portal, reads the target as `plan` does, and reports:
|
|
12
|
+
|
|
13
|
+
- `found`: config resources the portal holds, with how many units config and the portal agree on.
|
|
14
|
+
- `missing`: config resources a complete read did not find.
|
|
15
|
+
- `stale`: current entries that record another portal name, whose resource the portal no longer holds, or whose address is no longer in config.
|
|
16
|
+
- `excluded`: tombstoned addresses, skipped ones, and ones the read could not see or Kalup does not write.
|
|
17
|
+
|
|
18
|
+
With `--write`, only a person at a terminal may run it (else `E_APPROVAL_REQUIRED`, exit 4); `--yes` and `--approve` are refused. It checks the write key's portal and refuses an incomplete read (`E_INCOMPLETE`: a rebuild would drop what it could not check). It shows the report and what the current file loses for good (the `created` origin, agreed values, entries not in config), asks for the target name, takes the portal lock, refuses a state file changed since the report (`E_STATE_CHANGED`), archives the current file under `.kalup/state/archive/` (ending its lineage), and writes a new lineage at serial 1: an `adopted` entry for every found resource, with a base for the units that agree. A tombstoned address is never adopted. Nothing is sent to the portal.
|
|
19
|
+
|
|
20
|
+
Plans saved before a rebuild are refused by apply (`E_STATE_CHANGED`); plan again.
|
|
21
|
+
|
|
22
|
+
## target rebind
|
|
23
|
+
|
|
24
|
+
`kalup target rebind <target> --portal <id>` moves a target to a recreated test portal or sandbox (targets.md): at a terminal only, and only to a `DEVELOPER_TEST` or `SANDBOX` account (`E_REBIND_STANDARD`). It takes both portal locks in ascending portal ID order, checks that the old portal's state file is readable and the read complete, reports how many managed resources the new portal holds by name, asks for the target name, writes the new portal's state as `--write` does, writes the new `portalId` into `kalup.config.ts` (validated, with a history copy), and archives the old portal's state file.
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
|
|
28
|
+
`--json` data for rebuild: `target`, `portalId`, `statePath`, `found`, `missing`, `stale`, `excluded`, `loses` and `written`, always `false`, since `--write --json` exits 4. Rebind with `--json` exits 4 with no data.
|
|
29
|
+
|
|
30
|
+
## Exit codes
|
|
31
|
+
|
|
32
|
+
| Exit | When |
|
|
33
|
+
|---|---|
|
|
34
|
+
| 0 | Reported, or written |
|
|
35
|
+
| 1 | `E_USAGE`, `E_CANCELLED`, `E_MISSING_KEY`, `E_LOCKED`, `E_INCOMPLETE`, `E_STATE_CHANGED`, `E_STATE_INVALID`, `E_STATE_WRITE`, a failed request |
|
|
36
|
+
| 3 | Config invalid, `E_DUPLICATE_PORTAL` |
|
|
37
|
+
| 4 | `E_APPROVAL_REQUIRED` (no terminal), `E_TARGET_PORTAL_MISMATCH`, `E_REBIND_STANDARD` |
|