kalup 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/{commands-Bk1w1oup.mjs → commands-CHucC_PV.mjs} +3458 -1065
- package/dist/commands.d.mts +1 -1
- package/dist/commands.mjs +1 -1
- package/dist/{context-8pARDYRR.d.mts → context-B7bI9BjL.d.mts} +52 -0
- package/dist/{host-BoqS00po.mjs → host-D1HtgrVd.mjs} +1 -1
- package/dist/host.d.mts +1 -1
- package/dist/host.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/schemas/ir-1.schema.json +41 -1
- package/dist/schemas/plan-1.schema.json +18 -0
- package/docs/apply.md +6 -4
- package/docs/config.md +38 -9
- package/docs/dictionary.md +3 -3
- package/docs/errors/E_BLUEPRINT_SCHEMA.md +2 -2
- package/docs/errors/E_DEFINITION_FIELD.md +1 -1
- package/docs/errors/E_DIR_AMBIGUOUS.md +4 -4
- package/docs/errors/E_DUPLICATE_LABEL.md +24 -0
- package/docs/errors/E_HS_PREFIX.md +3 -3
- package/docs/errors/E_HTTP.md +1 -1
- package/docs/errors/E_MISSING_EXPORT.md +2 -2
- package/docs/errors/E_PIPELINE_FIELD.md +23 -0
- package/docs/errors/E_PIPELINE_ID.md +17 -0
- package/docs/errors/E_PIPELINE_STAGES.md +17 -0
- package/docs/errors/E_PULL_INVALID.md +2 -2
- package/docs/errors/E_TOMBSTONE_ADDRESS.md +3 -3
- package/docs/errors/E_UNSUPPORTED_FILE.md +3 -3
- package/docs/errors/E_WRITE_IN_READ_MODE.md +1 -1
- package/docs/errors/E_WRITE_NOT_ALLOWED.md +1 -1
- package/docs/errors/W_LEGACY_DIR.md +2 -2
- package/docs/errors/W_LIMIT_UNREADABLE.md +2 -2
- package/docs/errors/W_RATE_HEADERS.md +1 -1
- package/docs/errors/W_RATE_LIMIT.md +1 -1
- package/docs/errors/W_WRITE_SCOPE.md +17 -0
- package/docs/plan.md +22 -9
- package/docs/pull.md +10 -6
- package/docs/rm.md +6 -4
- package/docs/snapshot.md +2 -2
- package/docs/targets.md +3 -3
- package/package.json +3 -3
package/dist/commands.d.mts
CHANGED
package/dist/commands.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as TargetRebindCommand, a as CompareCommand, c as InitCommand, d as PlanCommand, f as PullCommand, g as StatusCommand, h as StateRebuildCommand, i as COMMANDS, l as IrCommand, m as SnapshotCommand, n as ApplyCommand, o as DocsCommand, p as RmCommand, r as BlueprintUpgradeCommand, s as FmtCommand, t as AddCommand, u as KalupCommand, v as ValidateCommand } from "./commands-
|
|
1
|
+
import { _ as TargetRebindCommand, a as CompareCommand, c as InitCommand, d as PlanCommand, f as PullCommand, g as StatusCommand, h as StateRebuildCommand, i as COMMANDS, l as IrCommand, m as SnapshotCommand, n as ApplyCommand, o as DocsCommand, p as RmCommand, r as BlueprintUpgradeCommand, s as FmtCommand, t as AddCommand, u as KalupCommand, v as ValidateCommand } from "./commands-CHucC_PV.mjs";
|
|
2
2
|
export { AddCommand, ApplyCommand, BlueprintUpgradeCommand, COMMANDS, CompareCommand, DocsCommand, FmtCommand, InitCommand, IrCommand, KalupCommand, PlanCommand, PullCommand, RmCommand, SnapshotCommand, StateRebuildCommand, StatusCommand, TargetRebindCommand, ValidateCommand };
|
|
@@ -297,6 +297,17 @@ declare const issues: {
|
|
|
297
297
|
output: string[];
|
|
298
298
|
};
|
|
299
299
|
};
|
|
300
|
+
E_DUPLICATE_LABEL: {
|
|
301
|
+
exit: string;
|
|
302
|
+
title: string;
|
|
303
|
+
summary: string;
|
|
304
|
+
when: string[];
|
|
305
|
+
fix: string[];
|
|
306
|
+
example: {
|
|
307
|
+
config: string[];
|
|
308
|
+
output: string[];
|
|
309
|
+
};
|
|
310
|
+
};
|
|
300
311
|
E_DUPLICATE_OPTION: {
|
|
301
312
|
exit: string;
|
|
302
313
|
title: string;
|
|
@@ -516,6 +527,37 @@ declare const issues: {
|
|
|
516
527
|
output: string[];
|
|
517
528
|
};
|
|
518
529
|
};
|
|
530
|
+
E_PIPELINE_FIELD: {
|
|
531
|
+
exit: string;
|
|
532
|
+
title: string;
|
|
533
|
+
summary: string;
|
|
534
|
+
when: string[];
|
|
535
|
+
fix: string[];
|
|
536
|
+
example: {
|
|
537
|
+
config: string[];
|
|
538
|
+
output: string[];
|
|
539
|
+
};
|
|
540
|
+
};
|
|
541
|
+
E_PIPELINE_ID: {
|
|
542
|
+
exit: string;
|
|
543
|
+
title: string;
|
|
544
|
+
summary: string;
|
|
545
|
+
when: string[];
|
|
546
|
+
fix: string[];
|
|
547
|
+
example: {
|
|
548
|
+
output: string[];
|
|
549
|
+
};
|
|
550
|
+
};
|
|
551
|
+
E_PIPELINE_STAGES: {
|
|
552
|
+
exit: string;
|
|
553
|
+
title: string;
|
|
554
|
+
summary: string;
|
|
555
|
+
when: string[];
|
|
556
|
+
fix: string[];
|
|
557
|
+
example: {
|
|
558
|
+
output: string[];
|
|
559
|
+
};
|
|
560
|
+
};
|
|
519
561
|
E_PLAN_DELETE: {
|
|
520
562
|
exit: string;
|
|
521
563
|
title: string;
|
|
@@ -1237,6 +1279,16 @@ declare const issues: {
|
|
|
1237
1279
|
output: string[];
|
|
1238
1280
|
};
|
|
1239
1281
|
};
|
|
1282
|
+
W_WRITE_SCOPE: {
|
|
1283
|
+
exit: string;
|
|
1284
|
+
title: string;
|
|
1285
|
+
summary: string;
|
|
1286
|
+
when: string[];
|
|
1287
|
+
fix: string[];
|
|
1288
|
+
example: {
|
|
1289
|
+
output: string[];
|
|
1290
|
+
};
|
|
1291
|
+
};
|
|
1240
1292
|
};
|
|
1241
1293
|
type IssueCode = keyof typeof issues;
|
|
1242
1294
|
/** One entry of a command's issues[], as the envelope contract defines it. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as sanitize, C as usageError, D as disclaimer, E as bin, O as escapeJson, S as versionText, T as KalupError, b as formats, k as exitCodes, u as KalupCommand, w as IssueError, x as version, y as PATH_MAX } from "./commands-
|
|
1
|
+
import { A as sanitize, C as usageError, D as disclaimer, E as bin, O as escapeJson, S as versionText, T as KalupError, b as formats, k as exitCodes, u as KalupCommand, w as IssueError, x as version, y as PATH_MAX } from "./commands-CHucC_PV.mjs";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
package/dist/host.d.mts
CHANGED
package/dist/host.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as isInteractive, r as run, t as execute } from "./host-
|
|
1
|
+
import { n as isInteractive, r as run, t as execute } from "./host-D1HtgrVd.mjs";
|
|
2
2
|
export { execute, isInteractive, run };
|
package/dist/index.mjs
CHANGED
|
@@ -93,6 +93,14 @@
|
|
|
93
93
|
{
|
|
94
94
|
"if": { "required": ["type"], "properties": { "type": { "const": "object" } } },
|
|
95
95
|
"then": { "properties": { "definition": { "$ref": "#/$defs/objectDefinition" } } }
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"if": { "required": ["type"], "properties": { "type": { "const": "pipeline" } } },
|
|
99
|
+
"then": { "properties": { "definition": { "$ref": "#/$defs/pipelineDefinition" } } }
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"if": { "required": ["type"], "properties": { "type": { "const": "stage" } } },
|
|
103
|
+
"then": { "properties": { "definition": { "$ref": "#/$defs/stageDefinition" } } }
|
|
96
104
|
}
|
|
97
105
|
]
|
|
98
106
|
},
|
|
@@ -185,6 +193,29 @@
|
|
|
185
193
|
},
|
|
186
194
|
"additionalProperties": false
|
|
187
195
|
},
|
|
196
|
+
"pipelineDefinition": {
|
|
197
|
+
"description": "A pipeline. Its stages are resources of their own; stages lists their IDs in display order.",
|
|
198
|
+
"type": "object",
|
|
199
|
+
"required": ["label", "displayOrder", "stages"],
|
|
200
|
+
"properties": {
|
|
201
|
+
"label": { "type": "string" },
|
|
202
|
+
"displayOrder": { "type": "integer", "minimum": 0 },
|
|
203
|
+
"stages": { "type": "array", "items": { "type": "string" } }
|
|
204
|
+
},
|
|
205
|
+
"additionalProperties": false
|
|
206
|
+
},
|
|
207
|
+
"stageDefinition": {
|
|
208
|
+
"description": "A pipeline stage. Its metadata field depends on the object: probability on deals, ticketState on tickets, state on custom objects.",
|
|
209
|
+
"type": "object",
|
|
210
|
+
"required": ["label"],
|
|
211
|
+
"properties": {
|
|
212
|
+
"label": { "type": "string" },
|
|
213
|
+
"probability": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
214
|
+
"ticketState": { "enum": ["OPEN", "CLOSED"] },
|
|
215
|
+
"state": { "enum": ["OPEN", "CLOSED"] }
|
|
216
|
+
},
|
|
217
|
+
"additionalProperties": false
|
|
218
|
+
},
|
|
188
219
|
"binding": {
|
|
189
220
|
"description": "App terms, filled in by the loader. key and codec for properties, export for objects.",
|
|
190
221
|
"type": "object",
|
|
@@ -322,7 +353,9 @@
|
|
|
322
353
|
"properties": {
|
|
323
354
|
"property": { "type": "array", "items": { "type": "string" } },
|
|
324
355
|
"group": { "type": "array", "items": { "type": "string" } },
|
|
325
|
-
"object": { "type": "array", "items": { "type": "string" } }
|
|
356
|
+
"object": { "type": "array", "items": { "type": "string" } },
|
|
357
|
+
"pipeline": { "type": "array", "items": { "type": "string" } },
|
|
358
|
+
"stage": { "type": "array", "items": { "type": "string" } }
|
|
326
359
|
},
|
|
327
360
|
"additionalProperties": false
|
|
328
361
|
}
|
|
@@ -380,6 +413,13 @@
|
|
|
380
413
|
"type": "object",
|
|
381
414
|
"patternProperties": { "^[a-z]+:\\S+$": { "type": "string" } },
|
|
382
415
|
"additionalProperties": false
|
|
416
|
+
},
|
|
417
|
+
"pipelines": {
|
|
418
|
+
"description": "Whether the object's pipelines list was read. Absent when its pipelines are not in scope.",
|
|
419
|
+
"type": "object",
|
|
420
|
+
"required": ["status"],
|
|
421
|
+
"properties": { "status": { "enum": ["read", "unreadable"] }, "missingScope": { "type": "string" } },
|
|
422
|
+
"additionalProperties": false
|
|
383
423
|
}
|
|
384
424
|
},
|
|
385
425
|
"additionalProperties": false
|
|
@@ -469,6 +469,24 @@
|
|
|
469
469
|
"description": "Logical form, $ref included, resolved at apply. A create holds the full definition.",
|
|
470
470
|
"type": "object"
|
|
471
471
|
},
|
|
472
|
+
"stages": {
|
|
473
|
+
"description": "A pipeline create only: the stages it carries, in display order, each with its full definition. HubSpot refuses a pipeline without a stage.",
|
|
474
|
+
"type": "array",
|
|
475
|
+
"items": {
|
|
476
|
+
"type": "object",
|
|
477
|
+
"required": ["address", "desired"],
|
|
478
|
+
"properties": {
|
|
479
|
+
"address": { "$ref": "#/$defs/address" },
|
|
480
|
+
"desired": { "type": "object" }
|
|
481
|
+
},
|
|
482
|
+
"additionalProperties": false
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
"stageLabels": {
|
|
486
|
+
"description": "Display only, never approved: the label of each stage a pipeline step's stage order names, config's else the portal's, so the plan text shows labels and not IDs.",
|
|
487
|
+
"type": "object",
|
|
488
|
+
"additionalProperties": { "type": "string" }
|
|
489
|
+
},
|
|
472
490
|
"ignoreChanges": {
|
|
473
491
|
"description": "Create only: set on create, released after.",
|
|
474
492
|
"type": "array",
|
package/docs/apply.md
CHANGED
|
@@ -28,18 +28,20 @@ Before any write, in order:
|
|
|
28
28
|
6. Each delete has a `destroy` tombstone in `hubspot/removed.ts` or takeover's leave (takeover mode, in the pull scope, not excluded), is gone from config, and no address in config names its portal resource, read as data (`E_PLAN_DELETE`). The object files also tell takeover's option removals from config's own.
|
|
29
29
|
7. Approval, then the portal lock (`E_LOCKED`).
|
|
30
30
|
8. State: a plan already applied with outcome `done` exits 0 ("Already applied"); otherwise lineage and serial equal the plan's (`E_STATE_CHANGED`).
|
|
31
|
-
9. A fresh read of each object the plan changes, and of the schemas list for a custom object (`E_INCOMPLETE` on a 403, `E_BINDING_CHANGED` for another type ID). Every `expect` must hold (`E_PLAN_STALE`), a delete's covering each field its base holds. Kalup derives each step's risk, labels and blocked status again (`E_PLAN_RISK` when the plan states less); a takeover removal needs `allowDestroy`, and never takes a HubSpot-defined property.
|
|
31
|
+
9. A fresh read of each object the plan changes, its pipelines when a step touches one, and of the schemas list for a custom object (`E_INCOMPLETE` on a 403, `E_BINDING_CHANGED` for another type ID). Every `expect` must hold (`E_PLAN_STALE`), a delete's covering each field its base holds. Kalup derives each step's risk, labels and blocked status again (`E_PLAN_RISK` when the plan states less); a takeover removal needs `allowDestroy`, and never takes a HubSpot-defined property.
|
|
32
32
|
10. Three calls per write plus the reads use at most half of HubSpot's daily remainder (`E_BUDGET`).
|
|
33
33
|
|
|
34
34
|
## Running the steps
|
|
35
35
|
|
|
36
|
-
Apply records `lastApply.outcome: running`, then runs the steps one at a time: groups, properties, releases, then deletes
|
|
36
|
+
Apply records `lastApply.outcome: running`, then runs the steps one at a time: groups, properties, pipeline creates, stage creates and updates (those that close a stage first, so a ticket pipeline keeps a closed stage), pipeline updates, releases, then deletes: properties, groups, stages, pipelines. Each write reads the resource again and compares it with `expect`, builds the request from that read, sends it once, and reads it back for up to 60 seconds, saying so on stderr after a few seconds.
|
|
37
37
|
|
|
38
38
|
- A property update sends the approved fields with the live `type` and `fieldType`; options go as the full live list with the approved changes, new ones last.
|
|
39
|
+
- A pipeline create sends its stages with it. A stage update sends only the approved fields. A stage order change moves each stage that is out of place onto the slot of the stage it follows, one request each, reading the pipeline before each move: HubSpot renumbers the pipeline when a stage lands on a taken slot. If a move waits out a rate limit, the step stops stale; plan again to see what is left. Kalup never sends a pipeline PUT, which deletes every stage it does not name.
|
|
40
|
+
- A pipeline or stage is read back with a GET of its pipeline. A stage delete is done only when that read lacks the stage: HubSpot answers 204 to a delete of any stage, one that does not exist included.
|
|
39
41
|
- A 429, 423 or 477 is waited out three times, reading again before each resend. A daily 429 stops the run.
|
|
40
42
|
- A timeout, network failure or 5xx is `uncertain` and never resent: HubSpot documents no idempotency keys. Only reading back the approved values settles it (`E_UNCERTAIN_WRITE`).
|
|
41
43
|
- A value HubSpot stores differently is `W_UNVERIFIED`: state records both, and the next plan notes it instead of writing again.
|
|
42
|
-
- A delete runs only once every earlier step verified. HubSpot keeps an archived property restorable in its UI for 90 days.
|
|
44
|
+
- A delete runs only once every earlier step verified. HubSpot keeps an archived property restorable in its UI for 90 days; a deleted pipeline or stage is gone for good.
|
|
43
45
|
|
|
44
46
|
State is saved after each step that changes an entry, then the outcome: `done`, `partial` or `uncertain`. Each request is journaled in `.kalup/journal/portal-<id>/`, never with a key or body. SIGINT or SIGTERM stops before the next request and saves state. A second one exits at once, unless it comes within a second (npx passes one Ctrl-C on twice).
|
|
45
47
|
|
|
@@ -61,4 +63,4 @@ There is no resume and no rollback. After a run that did not finish, run `kalup
|
|
|
61
63
|
|
|
62
64
|
## Limits
|
|
63
65
|
|
|
64
|
-
A read and the write after it are not atomic: an edit in HubSpot between the two is overwritten for that field. The lock keeps apart one user's commands on one machine only; in CI, one workflow per portal applies, in a concurrency group. A delete checks no use first; HubSpot
|
|
66
|
+
A read and the write after it are not atomic: an edit in HubSpot between the two is overwritten for that field. The lock keeps apart one user's commands on one machine only; in CI, one workflow per portal applies, in a concurrency group. A delete checks no use first; HubSpot refuses to archive a property a workflow, list, form or calculation uses, and apply names each use.
|
package/docs/config.md
CHANGED
|
@@ -8,14 +8,15 @@ This page is the reference. For the walk-through with examples, see [Config file
|
|
|
8
8
|
|
|
9
9
|
- `kalup.config.ts`: one `export default defineConfig({...})` and nothing after it. Fields: `name` (default: the name in the nearest `package.json` up to the repository root, else the directory name), `dir` (the folder of object files, relative to `kalup.config.ts` and inside the project, default `hubspot`; `E_SETTING_VALUE` otherwise), `state` (`'local'`, the default, or `'repo'`, state.md), `prefix`, `defaultTarget` (targets.md), `mode` (below), `objects` (the pull scope, pull.md) and `targets` (targets.md). A setting at a level that does not take it is `E_SETTING_LEVEL`, whose fix lists the levels that do; a value it does not take is `E_SETTING_VALUE`, with the nearest allowed one.
|
|
10
10
|
- `hubspot/objects/<object>.ts`: one or more `export const <Name> = defineObject('<object>', {...})` or `defineCustomObject('<name>', {...})`. The writer adds an `export type <Name>Data = ...` line after each. A file with no such export is `E_MISSING_EXPORT`.
|
|
11
|
-
- `hubspot/
|
|
11
|
+
- `hubspot/pipelines/<object>.ts`: one or more `export const <Name> = definePipeline('<object>', {...})`, below. A `definePipeline` export anywhere else is `E_UNSUPPORTED_FILE`.
|
|
12
|
+
- `hubspot/index.ts`: the barrel, written by `pull` and `fmt`. It imports each object file as `./objects/<object>.js` and each pipeline file as `./pipelines/<object>.js`, which resolves under TypeScript `NodeNext`, `Node16` and `Bundler` resolution, bundlers such as Vite and Next.js, and plain Node running `tsc` output. Under `NodeNext`, import it as `./hubspot/index.js`.
|
|
12
13
|
- `hubspot/removed.ts`: tombstones, below.
|
|
13
|
-
-
|
|
14
|
+
- A `defineConfig` or `defineRemoved` file elsewhere under `hubspot/` is `E_UNSUPPORTED_FILE`.
|
|
14
15
|
- `hubspot/blueprints.lock.json` and `hubspot/.blueprints/`: written by `kalup add` (blueprints.md).
|
|
15
16
|
|
|
16
17
|
The folder belongs to Kalup alone: every `.ts` file in it is read as config, `pull` rewrites its `index.ts`, and `init` takes it out of the formatter's checks. Point `dir` at a folder of its own (`lib/config/hubspot`, not `lib/config` next to the app's modules); `init` refuses a folder that holds other `.ts` files (`E_DIR_IN_USE`). A 0.1 project keeps its `kalup/` folder while `dir` is unset and `hubspot/` holds no `.ts` file, with `W_LEGACY_DIR` on every command until you set `dir: 'kalup'` or move the folder. When both hold `.ts` files, every command stops with `E_DIR_AMBIGUOUS` until `dir` says which.
|
|
17
18
|
|
|
18
|
-
Commit `kalup.config.ts` and the folder: the object files, `index.ts`, `removed.ts`, the blueprints lock, and `state/` only with `state: 'repo'` (state.md). Never commit `.kalup/` (local state, saved plans, journals, history, snapshots), a plan file, `.env` or any file holding a key.
|
|
19
|
+
Commit `kalup.config.ts` and the folder: the object and pipeline files, `index.ts`, `removed.ts`, the blueprints lock, and `state/` only with `state: 'repo'` (state.md). Never commit `.kalup/` (local state, saved plans, journals, history, snapshots), a plan file, `.env` or any file holding a key.
|
|
19
20
|
|
|
20
21
|
## The grammar
|
|
21
22
|
|
|
@@ -23,7 +24,7 @@ Anything else is `E_NOT_DATA`.
|
|
|
23
24
|
|
|
24
25
|
- `import` lines. Imports from `@kalup/core` and `kalup` are rewritten; others are kept, for `p.json` validators.
|
|
25
26
|
- Object literals of `key: value` entries, arrays, strings in single or double quotes on one line, numbers, `true` and `false`. No template strings, identifiers as values, spreads, computed keys, shorthand, or calls other than the builders.
|
|
26
|
-
- A `//` comment on its own line above an export, a group entry or a
|
|
27
|
+
- A `//` comment on its own line above an export, a group entry, a property entry or a stage entry, and a comment block above the imports (the file header). Every other comment is an error, including any in `kalup.config.ts` but the header.
|
|
27
28
|
- `p.<kind>('<internal name>')` or `p.<kind>('<internal name>', {...})`, then any of `.strict()` (`p.enum` and `p.multiEnum` only), `.required()`, `.readonly()` and `.managed(false)`, each once. Any other chain call is `E_BAD_CHAIN`. A kind not listed below is `E_UNKNOWN_BUILDER`.
|
|
28
29
|
- `p.json('<name>', <validator>, {...})`. The validator is opaque text and may not hold a `//` comment.
|
|
29
30
|
- A custom object needs `labels: { singular, plural }` and `primaryDisplayProperty`, and may set `requiredProperties`, `searchableProperties` and `secondaryDisplayProperties`.
|
|
@@ -56,7 +57,7 @@ A definition with `label`, `group` and `fieldType` is managed: the fields presen
|
|
|
56
57
|
- On `p.string`, `p.stringArray`, `p.json` and `p.phoneNumber`: `textDisplayHint` (`unformatted_single_line`, `multi_line`, `email`, `phone_number`, `domain_name`, `ip_address`, `physical_address`, `postal_code`). HubSpot takes no value that removes a hint.
|
|
57
58
|
- `calculationFormula` with `fieldType: 'calculation_equation'`, in HubSpot's formula syntax. HubSpot stores its own spelling (`a+1` as `a + 1`); write it as pull does, or plan notes the difference. A formula change is risky.
|
|
58
59
|
|
|
59
|
-
A field another builder or field rules out is `E_DEFINITION_FIELD`. HubSpot ignores `dateDisplayHint`, so it is no field. `group` must name a group declared under `groups` for the same object, in any export or file (`E_UNKNOWN_GROUP`). A managed internal name starting with `hs_` is `E_HS_PREFIX
|
|
60
|
+
A field another builder or field rules out is `E_DEFINITION_FIELD`. HubSpot ignores `dateDisplayHint`, so it is no field. `group` must name a group declared under `groups` for the same object, in any export or file (`E_UNKNOWN_GROUP`). A managed internal name starting with `hs_` or `a<digits>_` is `E_HS_PREFIX`: HubSpot reserves `hs_` for its own properties and `a<appId>_` for an integration's, and refuses a create with either. A reference may carry the prefix, and pull writes such properties as references.
|
|
60
61
|
|
|
61
62
|
No definition makes a reference: never created, changed or removed. `p.enum` or `p.multiEnum` with `options` and nothing else is a reference with typed options, which pull refreshes.
|
|
62
63
|
|
|
@@ -78,13 +79,13 @@ The object key is the app's name for the property. Two exports of one object usi
|
|
|
78
79
|
|
|
79
80
|
## Per-target definitions
|
|
80
81
|
|
|
81
|
-
A target's override `definition` (targets.md) replaces each field it states there, whole, and owns it, empty values included: a property's `label`, `description`, `group`, `fieldType`, `formField`, `options` (no `as`), `hidden`, `displayOrder`, the display fields, `calculationFormula` and lifecycle but `preventDestroy`; a group's `label
|
|
82
|
+
A target's override `definition` (targets.md) replaces each field it states there, whole, and owns it, empty values included: a property's `label`, `description`, `group`, `fieldType`, `formField`, `options` (no `as`), `hidden`, `displayOrder`, the display fields, `calculationFormula` and lifecycle but `preventDestroy`; a group's `label`; a pipeline's `label` and `displayOrder`; a stage's `label` and metadata field. Else `E_OVERRIDE_DEFINITION`. `pull` writes these fields into the override.
|
|
82
83
|
|
|
83
84
|
## Mode: addon and takeover
|
|
84
85
|
|
|
85
86
|
`mode: 'addon' | 'takeover'` at the top level, under `objects.<object>`, under `targets.<target>`, or under `targets.<target>.objects.<object>`. The most specific wins, in that order from the last, and the default is `addon`: Kalup manages only what config names. A target `mode` that differs from an object's `mode` the target says nothing more about is `W_MODE_SHADOWED`.
|
|
86
87
|
|
|
87
|
-
Under `takeover`, `plan` archives every custom property and group in the object's pull scope that config lacks and `hubspot/removed.ts` does not name (a group only once every property in it goes, and after them), and removes enum options only the portal holds. Never a HubSpot-defined or calculated property, a kind Kalup does not write, anything an object file lists, a name `exclude` covers,
|
|
88
|
+
Under `takeover`, `plan` archives every custom property and group in the object's pull scope that config lacks and `hubspot/removed.ts` does not name (a group only once every property in it goes, and after them), and removes enum options only the portal holds. Never a HubSpot-defined or calculated property, a kind Kalup does not write, anything an object file lists, a name `exclude` covers, a property a custom object schema names, or a pipeline or stage. Every takeover removal is destructive: it needs `allowDestroy: true` on the target and a person at a terminal, and `--yes` and `--approve` never cover it. Without `allowDestroy` it is blocked, reason `policy`; after an incomplete read, reason `scope`.
|
|
88
89
|
|
|
89
90
|
## Removed resources
|
|
90
91
|
|
|
@@ -97,8 +98,36 @@ export default defineRemoved({
|
|
|
97
98
|
})
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
`destroy` deletes the resource, only on a target with `allowDestroy: true` (default false). `release` stops managing it, leaving it there. Keys are property or
|
|
101
|
+
`destroy` deletes the resource, only on a target with `allowDestroy: true` (default false). `release` stops managing it, leaving it there. Keys are property, group, pipeline or stage addresses (`E_TOMBSTONE_ADDRESS`) that config no longer defines (`E_TOMBSTONE_CONFLICT`). A pipeline's tombstone covers its stages. HubSpot keeps no archive of a pipeline or stage: a delete is permanent.
|
|
102
|
+
|
|
103
|
+
## Pipelines
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
// hubspot/pipelines/deals.ts
|
|
107
|
+
import { definePipeline } from '@kalup/core'
|
|
108
|
+
|
|
109
|
+
export const RenewalsPipeline = definePipeline('deals', {
|
|
110
|
+
id: 'renewals',
|
|
111
|
+
label: 'Renewals',
|
|
112
|
+
displayOrder: 2,
|
|
113
|
+
stages: {
|
|
114
|
+
open: { id: 'renewals_open', label: 'Open', probability: 0.2 },
|
|
115
|
+
won: { id: 'renewals_won', label: 'Won', probability: 1 },
|
|
116
|
+
lost: { id: 'renewals_lost', label: 'Lost', probability: 0 },
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- The first argument is the object key as in `objects`. Kalup writes the pipelines of `deals`, `tickets` and custom objects; the pipelines of contacts, companies, appointments, services, listings, courses, orders and leads are read and compared, never written. A pipeline on an object HubSpot gives none is `E_PIPELINE_FIELD`.
|
|
122
|
+
- `id` is the pipeline or stage ID HubSpot stores, and the address: `pipeline:deals/renewals`, `stage:deals/renewals/renewals_won`. It never changes; a new ID is a new pipeline or stage. A pipeline ID is at most 36 characters, a stage ID at most 100, neither holds whitespace or `/`, a pipeline ID is used once in the project and a stage ID once per object (`E_PIPELINE_ID`): HubSpot keeps pipeline IDs unique across objects and stage IDs across one object's pipelines.
|
|
123
|
+
- `label` and `displayOrder` are required on a pipeline, `label` on a stage. Labels update in place. Two stages of a pipeline, or two pipelines of an object, may not share a label, ignoring case (`E_DUPLICATE_LABEL`).
|
|
124
|
+
- Stage order is the order of the entries under `stages`. Stages take no `displayOrder`.
|
|
125
|
+
- Each stage takes the metadata field of its object, and no other (`E_PIPELINE_FIELD`): `probability` on a deal stage, required, from 0 to 1; `ticketState` on a ticket stage, `'OPEN'` or `'CLOSED'`, default `'OPEN'`; `state` on a custom object stage, the same. HubSpot derives whether a stage is closed from it.
|
|
126
|
+
- A pipeline needs a stage, and a ticket pipeline a stage with `ticketState: 'CLOSED'` (`E_PIPELINE_STAGES`).
|
|
127
|
+
- The export name and the stage keys are free. In the app, `RenewalsPipeline.stages.won.id` is typed `'renewals_won'`, and `StageId<typeof RenewalsPipeline>` is the union of its stage IDs.
|
|
128
|
+
|
|
129
|
+
A target's override takes a pipeline's or stage's ID on that portal as `name`, `skip` (a skipped pipeline takes its stages), and a `definition` with a pipeline's `label` and `displayOrder` or a stage's `label` and metadata field.
|
|
101
130
|
|
|
102
131
|
## Canonical form
|
|
103
132
|
|
|
104
|
-
`kalup fmt` validates, then rewrites `kalup.config.ts`, `hubspot/removed.ts`, every object file and the barrel: groups and properties sorted by internal name, tombstones by address, fields in a fixed order, options in display order, quotes as biome writes them, 120 columns. It keeps every value you wrote, `description: ''`, `options: []`, `false` and an empty `lifecycle` included: a present field is owned. `fmt --check` lists the files it would change and exits 2 when there are any. Old files go to `.kalup/history/<timestamp>/` first; the last 20 runs are kept.
|
|
133
|
+
`kalup fmt` validates, then rewrites `kalup.config.ts`, `hubspot/removed.ts`, every object and pipeline file and the barrel: groups and properties sorted by internal name, stages in the order written, tombstones by address, fields in a fixed order, options in display order, quotes as biome writes them, 120 columns. It keeps every value you wrote, `description: ''`, `options: []`, `false` and an empty `lifecycle` included: a present field is owned. `fmt --check` lists the files it would change and exits 2 when there are any. Old files go to `.kalup/history/<timestamp>/` first; the last 20 runs are kept.
|
package/docs/dictionary.md
CHANGED
|
@@ -10,8 +10,8 @@ This page is the reference. For the walk-through with examples, see [kalup docs]
|
|
|
10
10
|
## Layout
|
|
11
11
|
|
|
12
12
|
1. `# <project> data dictionary` and a line naming the source: the config files, or the target, portal ID and `observedAt` of the snapshot.
|
|
13
|
-
2. `## Coverage`. For a snapshot: whether the read was complete, the objects not read with the missing scope, objects absent from the portal, what `skip` overrides left out, unsupported properties (Kalup does not write them), schemas without a label, `name` overrides, config properties in a group no address can hold, counts out of scope and shadowed, custom objects config does not name, and the fields Kalup does not capture. Reference properties record only their options.
|
|
14
|
-
3. One `## <object>` section per object key, sorted: a custom object's labels, display property and property lists; a groups table (internal name, label); a properties table (internal name, label, type, field type, group, managed or reference, description);
|
|
13
|
+
2. `## Coverage`. For a snapshot: whether the read was complete, the objects not read with the missing scope, objects absent from the portal, what `skip` overrides left out, unsupported properties (Kalup does not write them), schemas without a label, `name` overrides, config properties in a group no address can hold, counts out of scope and shadowed, custom objects config does not name, objects whose pipelines were not read, and the fields Kalup does not capture. Reference properties record only their options.
|
|
14
|
+
3. One `## <object>` section per object key, sorted: a custom object's labels, display property and property lists; a groups table (internal name, label); a properties table (internal name, label, type, field type, group, managed or reference, description); one options table per enumeration (value, label, hidden, description), in display order; and per pipeline, sorted by ID, a `### Pipeline <label> (<id>)` heading, its display order, and a stages table in stage order (stage ID, label, and its probability or state; config adds the key). Config adds the key, codec, required and alias columns. Unsupported properties appear only under Coverage.
|
|
15
15
|
4. Config with `definition` overrides: `## Per-target overrides`, one row per address, field and target with its value, sorted.
|
|
16
16
|
|
|
17
17
|
## Escaping
|
|
@@ -20,7 +20,7 @@ Every string from a file or a portal is shown as text: newlines become spaces, c
|
|
|
20
20
|
|
|
21
21
|
## Deterministic
|
|
22
22
|
|
|
23
|
-
The same source gives the same bytes: objects, groups and
|
|
23
|
+
The same source gives the same bytes: objects, groups, properties and pipelines sorted, options and stages in display order, and no timestamp but a snapshot's own `observedAt`. Commit the dictionary and check it in CI:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
26
|
npx --no-install kalup docs --out DATA-DICTIONARY.md
|
|
@@ -4,7 +4,7 @@ A blueprint is not a valid `blueprint/1` document. Exit 1. Nothing was written.
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
`kalup add` and `kalup blueprint upgrade` parse the source as JSON, never as code. Another `blueprintVersion` is refused first. Then they check `blueprint-1.schema.json` and the rules the schema cannot state: addresses of the form `group:<object>/<name>` or `property:<object>/<name>` that match their type, plain names that never start with `hs_` (a group `$ref` names a plain group too), unique option values, aliases that name an option, and a codec that fits the HubSpot type and field type. Text that is not JSON or UTF-8 is refused, and so are names a prefix makes invalid. Each issue names its path; quoted text is sanitized.
|
|
7
|
+
`kalup add` and `kalup blueprint upgrade` parse the source as JSON, never as code. Another `blueprintVersion` is refused first. Then they check `blueprint-1.schema.json` and the rules the schema cannot state: addresses of the form `group:<object>/<name>` or `property:<object>/<name>` that match their type, plain names that never start with a prefix HubSpot reserves, `hs_` or `a<digits>_` (a group `$ref` names a plain group too), unique option values, aliases that name an option, and a codec that fits the HubSpot type and field type. Text that is not JSON or UTF-8 is refused, and so are names a prefix makes invalid. Each issue names its path; quoted text is sanitized.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -13,5 +13,5 @@ A blueprint is third-party data: ask its author for a version that passes, `blue
|
|
|
13
13
|
## Example
|
|
14
14
|
|
|
15
15
|
```
|
|
16
|
-
E_BLUEPRINT_SCHEMA: property name 'hs_renewal_flag' starts with hs_,
|
|
16
|
+
E_BLUEPRINT_SCHEMA: property name 'hs_renewal_flag' starts with hs_, a prefix HubSpot reserves (fix: a blueprint is third-party data: ask its author for a version that passes, or fix your own copy of the file) (docs: errors/E_BLUEPRINT_SCHEMA.md)
|
|
17
17
|
```
|
|
@@ -8,7 +8,7 @@ A property definition states a field HubSpot would refuse or misread for this pr
|
|
|
8
8
|
|
|
9
9
|
- `numberDisplayHint`, `showCurrencySymbol` and `currencyPropertyName` belong to `p.number`, and `textDisplayHint` to `p.string`, `p.stringArray`, `p.json` and `p.phoneNumber`. HubSpot stores them on any property but shows them only on those.
|
|
10
10
|
- `calculationFormula` needs `fieldType: 'calculation_equation'`. Sent with another field type, HubSpot turns the property into a calculation.
|
|
11
|
-
- `currencyPropertyName` needs `showCurrencySymbol: true`. HubSpot refuses it otherwise (`ONLY_CURRENCY_PROPERTIES_CAN_SPECIFY_CURRENCY`).
|
|
11
|
+
- `currencyPropertyName` needs `showCurrencySymbol: true`. HubSpot refuses it otherwise (`ONLY_CURRENCY_PROPERTIES_CAN_SPECIFY_CURRENCY`), and an empty `''` is refused by Kalup: HubSpot stores it as a value and then never turns the symbol off again (live runs, 2026-10-01).
|
|
12
12
|
- `displayOrder` is an integer from -1 up.
|
|
13
13
|
- `p.owner` takes no `options`: HubSpot fills them with the account's users and refuses a create that sends any.
|
|
14
14
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# E_DIR_AMBIGUOUS
|
|
2
2
|
|
|
3
|
-
Both `hubspot/` and the
|
|
3
|
+
Both `hubspot/` and the old default folder `kalup/` hold .ts files, and `kalup.config.ts` does not say which one holds the object files. Exit 3. Nothing was read or written.
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
Without `dir` in `kalup.config.ts`, Kalup reads `hubspot/`, or
|
|
7
|
+
Without `dir` in `kalup.config.ts`, Kalup reads `hubspot/`, or an older project's `kalup/` while `hubspot/` holds no .ts file (`W_LEGACY_DIR`). When both hold .ts files, such as a half-done move or a HubSpot developer project in `hubspot/`, Kalup does not guess: reading the wrong folder would make everything in the other look removed from config.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
11
|
-
Add `dir: 'kalup'` to `kalup.config.ts` to keep the
|
|
11
|
+
Add `dir: 'kalup'` to `kalup.config.ts` to keep the old folder, or `dir: 'hubspot'` when the object files are there. Then move or remove the other folder's copy of the object files.
|
|
12
12
|
|
|
13
13
|
## Example
|
|
14
14
|
|
|
15
15
|
```
|
|
16
|
-
kalup.config.ts: E_DIR_AMBIGUOUS: both hubspot/ and kalup/ hold .ts files, and kalup.config.ts does not say which one holds the object files (fix: add dir: 'kalup' to kalup.config.ts to keep the
|
|
16
|
+
kalup.config.ts: E_DIR_AMBIGUOUS: both hubspot/ and kalup/ hold .ts files, and kalup.config.ts does not say which one holds the object files (fix: add dir: 'kalup' to kalup.config.ts to keep the old folder, or dir: 'hubspot' when the object files are there) (docs: errors/E_DIR_AMBIGUOUS.md)
|
|
17
17
|
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# E_DUPLICATE_LABEL
|
|
2
|
+
|
|
3
|
+
Two stages of one pipeline, or two pipelines of one object, share a label. Exit 3.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
HubSpot refuses a stage whose label another stage of the same pipeline has, ignoring case and spaces around it, and a pipeline whose label another pipeline of the same object has, ignoring case (live runs, 2026-10-05). The same label on stages of two pipelines, or on pipelines of two objects, is fine.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Give one of the two another label.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
stages: {
|
|
17
|
+
tasting: { id: 'orchard_tasting', label: 'Tasting', probability: 0.2 },
|
|
18
|
+
retasting: { id: 'orchard_retasting', label: 'tasting', probability: 0.3 },
|
|
19
|
+
},
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
hubspot/pipelines/deals.ts:9: E_DUPLICATE_LABEL: stages 'orchard_tasting' and 'orchard_retasting' of pipeline:deals/orchard_sales share the label 'tasting', ignoring case (fix: give one of the two another label) (docs: errors/E_DUPLICATE_LABEL.md)
|
|
24
|
+
```
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# E_HS_PREFIX
|
|
2
2
|
|
|
3
|
-
A managed property's internal name starts with `hs_`. Exit 3.
|
|
3
|
+
A managed property's internal name starts with `hs_` or `a<digits>_`. Exit 3.
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
HubSpot
|
|
7
|
+
HubSpot reserves `hs_` for its own properties and `a<appId>_` for an integration's, and refuses a create with either (400, live runs 2026-10-01). Kalup never claims those prefixes for a property it would own; `pull` writes such a property as a reference. A reference (no definition) may carry the prefix.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -17,5 +17,5 @@ plotCount: p.number('hs_plot_count', { label: 'Plot count', group: 'orchard', fi
|
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
hubspot/objects/companies.ts:9: E_HS_PREFIX: 'hs_plot_count' starts with hs_,
|
|
20
|
+
hubspot/objects/companies.ts:9: E_HS_PREFIX: 'hs_plot_count' starts with hs_, a prefix HubSpot reserves (hs_ for its own properties, a<digits>_ for an integration's) (fix: rename the property, or drop label, group and fieldType to reference it) (docs: errors/E_HS_PREFIX.md)
|
|
21
21
|
```
|
package/docs/errors/E_HTTP.md
CHANGED
|
@@ -4,7 +4,7 @@ HubSpot returned an error Kalup has no other code for. Exit 1.
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
A 400, a 404, a 5xx that three retries did not clear, or a success whose body is not JSON (often a proxy's HTML page). The issue holds the status, the method, the path and HubSpot's message when it sent one. In `apply`, a refusal whose reason HubSpot names and Kalup knows says it in plain words: a property in use, a group that still holds properties,
|
|
7
|
+
A 400, a 404, a 5xx that three retries did not clear, or a success whose body is not JSON (often a proxy's HTML page). The issue holds the status, the method, the path and HubSpot's message when it sent one. In `apply`, a refusal whose reason HubSpot names and Kalup knows says it in plain words: a property in use (each workflow, list, form or calculation named), a group that still holds active properties, a property name that exists, a currency symbol HubSpot never turns off again, or a sensitive property on a portal with sensitive data turned off.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# E_MISSING_EXPORT
|
|
2
2
|
|
|
3
|
-
A file under `hubspot/` has no `defineObject` or `defineCustomObject` export. Exit 3.
|
|
3
|
+
A file under `hubspot/` has no `defineObject` or `defineCustomObject` export, or a file under `hubspot/pipelines/` no `definePipeline` export. Exit 3.
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
Kalup reads every `.ts` file in the folder of object files (`hubspot/`, or the folder `dir` in `kalup.config.ts` names) except `index.ts` and `removed.ts` as an object file. A file with only imports, or an empty file, has nothing to read.
|
|
7
|
+
Kalup reads every `.ts` file in the folder of object files (`hubspot/`, or the folder `dir` in `kalup.config.ts` names) except `index.ts` and `removed.ts` as an object file, and each one under `pipelines/` as a pipeline file. A file with only imports, or an empty file, has nothing to read.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# E_PIPELINE_FIELD
|
|
2
|
+
|
|
3
|
+
A pipeline or stage states a field HubSpot would refuse, or drop without a word. Exit 3.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
A stage carries one metadata field, by its pipeline's object: `probability` on deals, from 0 to 1 and required, `ticketState` on tickets and `state` on custom objects, each `'OPEN'` or `'CLOSED'`. HubSpot drops any other metadata without an error, so a write would change nothing and every plan would show it again; HubSpot derives `isClosed` itself. A pipeline on another object, such as the contacts lifecycle pipeline, is read and compared, never written, so its stages carry no metadata. A pipeline's `displayOrder` is an integer from 0 up (live runs, 2026-10-01 and 2026-10-05).
|
|
8
|
+
|
|
9
|
+
A target's definition override that breaks one of these rules is `E_OVERRIDE_DEFINITION`.
|
|
10
|
+
|
|
11
|
+
## Fix
|
|
12
|
+
|
|
13
|
+
Change or remove the field the message names.
|
|
14
|
+
|
|
15
|
+
## Example
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
won: { id: 'orchard_signed', label: 'Signed', ticketState: 'CLOSED' },
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
hubspot/pipelines/deals.ts:10: E_PIPELINE_FIELD: ticketState is for ticket stages; a deal stage takes probability (fix: replace ticketState with probability, from 0 to 1) (docs: errors/E_PIPELINE_FIELD.md)
|
|
23
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# E_PIPELINE_ID
|
|
2
|
+
|
|
3
|
+
A pipeline or stage ID cannot be used: it forms no address, is too long, or another one holds it. Exit 3.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
A pipeline or stage ID is its address and what HubSpot stores, so it must hold no whitespace or slash. HubSpot stores a pipeline ID of at most 36 characters and a stage ID of at most 100, and answers 500 to a longer one. A pipeline ID is unique across the portal, deals and tickets included, and a stage ID across the pipelines of one object (live runs, 2026-10-01 and 2026-10-05), so two in config may not share one.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Give the pipeline or stage another ID. An ID is permanent once HubSpot creates it, so choose a short, readable one, such as the pipeline ID followed by the stage, `orchard_signed`.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
hubspot/pipelines/tickets.ts:5: E_PIPELINE_ID: pipeline:tickets/orchard_sales has the ID of pipeline:deals/orchard_sales, and HubSpot keeps pipeline IDs unique across objects (fix: give one of the two another ID) (docs: errors/E_PIPELINE_ID.md)
|
|
17
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# E_PIPELINE_STAGES
|
|
2
|
+
|
|
3
|
+
A pipeline has no stage, or a ticket pipeline has no closed stage. Exit 3.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
HubSpot refuses a pipeline with no stage, and a ticket pipeline with no stage whose `ticketState` is `'CLOSED'` (live runs, 2026-10-05). `kalup rm` refuses to remove such a pipeline's last stage, or its last closed one, for the same reason.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Add a stage, or mark one ticket stage `ticketState: 'CLOSED'`. To drop the whole pipeline, run `kalup rm` on the pipeline.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
hubspot/pipelines/tickets.ts:3: E_PIPELINE_STAGES: pipeline:tickets/orchard_desk has no stage with ticketState 'CLOSED', and HubSpot needs one (fix: mark the stage tickets end in ticketState: 'CLOSED') (docs: errors/E_PIPELINE_STAGES.md)
|
|
17
|
+
```
|
|
@@ -6,7 +6,7 @@ The files `pull` merged would not load or validate, so it wrote nothing. Exit 3,
|
|
|
6
6
|
|
|
7
7
|
Pull merges the portal into the object files, then loads and validates the whole project as it would write it, before saving anything. The issues after this one are what `validate` would report, with the file and line in the merged text, not the file on disk.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
An example: a new property whose internal name another key of the same object already uses (`E_DUPLICATE_KEY`).
|
|
10
10
|
|
|
11
11
|
## Fix
|
|
12
12
|
|
|
@@ -16,5 +16,5 @@ Change the portal or the file so the two agree, then pull again. To pull everyth
|
|
|
16
16
|
|
|
17
17
|
```
|
|
18
18
|
E_PULL_INVALID: the pulled project would not validate; nothing was written (fix: the issues that follow point at the files as pull would write them: change the portal or the file so they agree, or leave the resource out with --only) (docs: errors/E_PULL_INVALID.md)
|
|
19
|
-
hubspot/objects/companies.ts:20:
|
|
19
|
+
hubspot/objects/companies.ts:20: E_DUPLICATE_KEY: internal name 'plot_count' is used by two keys of Company: 'plotCount' and 'plotTotal' (fix: remove or rename one of the two entries) (docs: errors/E_DUPLICATE_KEY.md)
|
|
20
20
|
```
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# E_TOMBSTONE_ADDRESS
|
|
2
2
|
|
|
3
|
-
A key in `hubspot/removed.ts`, or the address given to `kalup rm`, is not the address of a property or
|
|
3
|
+
A key in `hubspot/removed.ts`, or the address given to `kalup rm`, is not the address of a property, group, pipeline or stage. Exit 3.
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
Each key in `hubspot/removed.ts` is an address, such as `property:companies/legacy_score`: the type, a colon, the object, a slash and the name. A key with no object, such as `property:legacy_score`, names nothing and is refused. This version removes properties
|
|
7
|
+
Each key in `hubspot/removed.ts` is an address, such as `property:companies/legacy_score`: the type, a colon, the object, a slash and the name. A key with no object, such as `property:legacy_score`, names nothing and is refused. A stage address names its pipeline as well: `stage:deals/renewals/won`. This version removes properties, property groups, pipelines and stages only, so a key of another type, such as `object:parcels`, is refused as well.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -19,5 +19,5 @@ export default defineRemoved({
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
hubspot/removed.ts:4: E_TOMBSTONE_ADDRESS: 'legacyScore' is not an address (fix: write the address of a property or
|
|
22
|
+
hubspot/removed.ts:4: E_TOMBSTONE_ADDRESS: 'legacyScore' is not an address (fix: write the address of a property, group, pipeline or stage, such as 'property:companies/legacy_score') (docs: errors/E_TOMBSTONE_ADDRESS.md)
|
|
23
23
|
```
|