Configuration as code
An organisation’s curation — a connection’s modules and field annotations, the organisation’s helper libraries and shared templates, and a few organisation settings — can also live in a repository and be pushed with the SDK. Discovery still runs on the server; the bundle is the curation layered on top. People, tokens, the connections themselves and billing are managed in Sloose, not pushed.
product-config/ org.json date format, expression context, AI data policy modules.json which discovered modules are enabled, in what order, with match defaults fields/<module>.json descriptions, formats, aliases, Exclude from AI flags, default mappings libraries/<name>/ manifest.json and src/index.ts templates/<name>.json reusable import recipes, shared with the whole orgA file in templates/ is either a bare TemplateConfig — in which case the file name is the
template’s name — or a wrapper with name, description and config. Older configurations are
migrated to the current shape before they are validated, so a file written by an earlier build
still pushes. Pushing again updates the same template rather than creating a second copy, and
sharing a template with the organisation is a plan feature: a bundle carrying templates/ is refused with
FEATURE_SHARED_TEMPLATES on a plan without it.
A template may enable a module modules.json leaves out — one saved from an import that was set
up for a module since switched off, say. validate passes such a template and names the module in
a warning, since only the push can say what becomes of it, against the connection. When the
connection has the module — switched off, or gone from the CRM, removed or not — the template is stored and push
prints a warning, because an import started from it keeps that module’s setup aside until the
module is chosen again. When the connection has never had it, the push is refused with
UNKNOWN_MODULES, naming the template and the module. The key is what counts: a template still
naming a module by a key modules.json renames is refused too, since no module keeps the old key
once the push applies it.
@sloose/sdk is not on npm yet, so the two commands below find nothing today. Until it is
published, send the bundle to the API yourself: POST /orgs/{org}/config/bundle applies one, and
POST /orgs/{org}/config/bundle/dry-run checks it exactly as a push would and writes nothing
(both in the API reference, with an API token).
What follows describes the CLI as it will be run.
npx @sloose/sdk validate ./product-confignpx @sloose/sdk push ./product-config --org <org id> --connection <id> --env productionA push can be made against a version, so a change somebody made in Sloose since is not
overwritten unseen. --version-file <path> keeps the configuration’s version between runs: diff
writes the version it read, and push sends it and writes back the version it made. When the
organisation’s configuration has changed since, the push is refused, says the version it is at
now, and leaves everything as it was: run diff to see what changed, then push again. When
something else changes it while the push is being applied, the push lands and says so: if that
change landed between the push’s own steps, it can vouch for no version and leaves the file as it
was; if it landed as the push finished, it keeps the push’s version, which the organisation is
already past. Either way, run diff before the next push. The file also names the organisation,
the connection and the API it was read from, since every connection of an organisation shares one
version: using it for a push anywhere else is refused, and so is naming a file that holds no
version, rather than pushing without one. Or pass a version yourself with --if-match <n>.
Without either, a push is unconditional.
A bundle is one connection’s. Its modules and field annotations are the modules and fields of
one CRM, and an organisation may hold several — production and a sandbox, or a client’s CRM each — so a push
and a diff name the connection they are about with --connection (or SLOOSE_CONNECTION). One
that names none is refused, and the CLI prints the organisation’s connections to choose from; a
connection’s id is also the last part of its Overview’s address, under Connections in the app
bar. The helper libraries, the
organisation settings and the templates in a bundle are the organisation’s, and a pushed template names no
connection: it is chosen when an import is started from it.
push and diff authenticate with an organisation API token. An administrator creates one in Sloose
under Settings → API tokens (or with POST /orgs/:org/tokens), copies the secret once, and
stores it as SLOOSE_TOKEN in the CI environment or passes --token. The token is scoped to that
one organisation with the admin role — or its creator’s, if lower (see API tokens);
revoke it from the same screen and every pipeline using it stops.
validate checks the bundle against the published schemas (and its module and field references
against a discovery snapshot, if you give it one with --discovery). A push is checked again on
the server, against the connection’s discovery, so a module or field the connection has never had
fails at push time, not at run time. A field the CRM has stopped returning is not refused: its
module’s page keeps it, and its annotation is written.
A key in a file that Sloose does not read — dateFromat for dateFormat, say — is not refused
either: a push leaves it out and answers as usual. validate names each such key by its file,
as a warning, and diff says the same and prints no line for it, since no push would write it.
A fields/<module>.json is the module’s whole set of annotations. On a push, every field the
CRM still returns that the file does not list has its annotation cleared: its description, its
aliases, and anything else set on its page in Sloose. A module with no fields file is left as it
is, and so is a field the CRM no longer returns.
The Hidden mark is the exception. A push writes it only where a file says it: excluded: true
hides the field, excluded: false offers it again, and a field the file leaves out, or names
without excluded, keeps the mark it has. So a field somebody hid on its module’s page stays
hidden through every deploy, and a module’s excludedFields hides a field whether or not its
fields file mentions it.
A push of what is already there changes nothing, version included. The registry, each module’s field annotations and the libraries are each written only where they differ from what the connection holds, and only a part written moves the configuration’s version. So a pipeline that pushes the same repository on every deploy does not tell anybody with Sloose open that the configuration changed, nor refuse their next save as a conflict.
Helper libraries are compiled and loaded before a push writes anything, so a library that does not load refuses the push whole, saying which and why. Each one is published as a new version only when it differs from what the organisation already has — a push that sends the same libraries every time makes no new versions and leaves the configuration’s version where it was. A library someone has taken over in the browser is left as it is, and the push says so in its warnings — it is not compiled or checked against the plan either, so a copy in the repository that no longer loads does not stop the rest of the push. A library the push makes is the repository’s, read only on its page. Pushing libraries needs a plan with the helper libraries feature; see Helper libraries.