@revoengine/cli 1.0.9 → 1.0.10
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 +181 -4
- package/dist/src/cli.js +9 -1
- package/dist/src/client.d.ts +10 -0
- package/dist/src/client.js +157 -0
- package/dist/src/commands/component.js +604 -296
- package/dist/src/commands/env.d.ts +2 -0
- package/dist/src/commands/env.js +360 -0
- package/dist/src/commands/index.d.ts +2 -0
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/metadata.d.ts +2 -0
- package/dist/src/commands/metadata.js +137 -0
- package/dist/src/commands/project.js +75 -1
- package/dist/src/component-lock.d.ts +28 -2
- package/dist/src/component-lock.js +89 -15
- package/dist/src/config.d.ts +12 -0
- package/dist/src/config.js +82 -2
- package/dist/src/env-sync.d.ts +86 -0
- package/dist/src/env-sync.js +331 -0
- package/dist/src/metadata-backfill.d.ts +46 -0
- package/dist/src/metadata-backfill.js +125 -0
- package/dist/src/project.d.ts +15 -9
- package/dist/src/project.js +89 -0
- package/dist/src/resource-metadata.d.ts +24 -0
- package/dist/src/resource-metadata.js +129 -0
- package/dist/src/tracked-resources.d.ts +7 -0
- package/dist/src/tracked-resources.js +37 -0
- package/dist/src/types.d.ts +3 -0
- package/dist/src/ui.js +12 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -54,11 +54,39 @@ Initialize a project for ambient low-code editor globals:
|
|
|
54
54
|
```bash
|
|
55
55
|
revo project
|
|
56
56
|
revo project init ./app
|
|
57
|
+
revo project init ./app --identity-mode stableKey --stable-key-name resourceKey --resources component
|
|
58
|
+
revo project init ./app --identity-mode componentId
|
|
57
59
|
revo project update ./app
|
|
58
60
|
```
|
|
59
61
|
|
|
60
62
|
This writes or refreshes root `.revoengine/types/revo.editor.d.ts`, `.revoengine/types/revo.editor.definitions.json`, and `.revoengine/revo.json`, patches the workspace `tsconfig.json` or `jsconfig.json`, and updates root `.gitignore` so the generated editor bundle stays local by default. Pass a path such as `./backend` when the RevoEngine workspace is nested; the path is saved as `.revoengine/revo.json.workspace` while root `.revoengine/` remains the single state directory.
|
|
61
63
|
|
|
64
|
+
When compiler options are missing, project initialization sets `target: ES2022`, `module: ESNext`, and `moduleDetection: force`. Revo TypeScript elements are runtime-wrapped snippets, so module detection is required for top-level `await`; existing explicit compiler options are preserved.
|
|
65
|
+
|
|
66
|
+
`--identity-mode stableKey|componentId` stores the component matching strategy in `.revoengine/revo.json`. New projects default to `stableKey`; existing projects without this setting retain legacy `componentId` matching. In stable-key mode, `--stable-key-name <name>` selects the metadata property and defaults to `stableKey`.
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"componentIdentity": {
|
|
71
|
+
"mode": "stableKey",
|
|
72
|
+
"metadataProperty": "resourceKey"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Stable-key mode manages only components that contain the configured metadata property. Component UUIDs remain in API requests and lock diagnostics, but they are not used as durable identity. Duplicate stable-key values block plan, pull, and push. Use `--identity-mode componentId` only for a workspace that intentionally remains bound to one RevoEngine instance.
|
|
78
|
+
|
|
79
|
+
During interactive init, the CLI asks for the stable key metadata name and which resource families it should track. Press Enter to use `stableKey` and track `all` resources. For non-interactive runs, `--resources <list>` accepts `all` or comma-separated resource names such as `component`; when omitted it defaults to `all`. Project initialization also backfills missing stable-key metadata for every tracked component while preserving existing keys. Pass `--no-metadata-backfill` only when initialization must not write remote component metadata.
|
|
80
|
+
|
|
81
|
+
Backfill stable-key metadata for tracked resources with an explicit plan/apply flow:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
revo metadata plan --env test
|
|
85
|
+
revo metadata apply --env test --yes
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Backfill only writes resources missing the configured stable-key metadata unless `--force` is passed. Generated component keys use `category/name#<random-hex>`.
|
|
89
|
+
|
|
62
90
|
## Common commands
|
|
63
91
|
|
|
64
92
|
```bash
|
|
@@ -135,12 +163,12 @@ Components/
|
|
|
135
163
|
Each component folder keeps its `component.json` manifest alongside an `elements/` directory with the source payload for every element.
|
|
136
164
|
Components with `category: null` are stored under `Components/__no_category__/...` while the manifest keeps `"category": null`.
|
|
137
165
|
|
|
138
|
-
Remote sync state is stored separately in `.revoengine/revo.lock.json`. The lock
|
|
166
|
+
Remote sync state is stored separately in `.revoengine/revo.lock.json`. Lock schema v2 records the configured identity strategy and, in stable-key mode, keys each entry by the configured metadata value while retaining both the repository-local and current remote UUID. The lock records the last remote component hash/version that this workspace was pulled from or successfully pushed to. It is a baseline, not the deployed source of truth; the server remains authoritative for deployed state and local component files remain the desired source.
|
|
139
167
|
|
|
140
168
|
Bulk sync behavior:
|
|
141
169
|
|
|
142
170
|
- `revo component pull --all` and `revo component push --all` require terminal confirmation unless `--yes` or `--force` is passed.
|
|
143
|
-
- `--yes` only confirms the bulk operation. `--force`
|
|
171
|
+
- `--yes` only confirms the bulk operation. `--force` bypasses lock-content safety, but never bypasses stable-key ownership: missing or duplicate configured identities are still blocked or ignored.
|
|
144
172
|
- `revo component pull --all` requests only active remote components where `deletedAt` is empty.
|
|
145
173
|
- Pull compares local files, `.revoengine/revo.lock.json`, and the current remote component before overwriting anything.
|
|
146
174
|
- Pull fast-forwards clean local files when the remote moved and the local copy still matches the lock.
|
|
@@ -148,6 +176,7 @@ Bulk sync behavior:
|
|
|
148
176
|
- Pull writes stable source manifests and keeps remote version churn out of `Components/**/component.json`.
|
|
149
177
|
- `revo component plan --all --strict` exits with an error when conflicts or missing lock entries are present.
|
|
150
178
|
- Push checks the current remote component against the lock before saving local changes.
|
|
179
|
+
- In stable-key mode, push resolves the target remote UUID from metadata and remaps element IDs by element key before saving. Source-instance UUIDs are never sent as target identity.
|
|
151
180
|
- Push rejects stale remote state instead of trusting the version inside local component JSON.
|
|
152
181
|
- Push treats backend `Not modified` responses as skipped instead of failing the whole run.
|
|
153
182
|
- Push treats backend `404` responses as skipped with `doesn't exist remotely`; restore the component in RevoEngine before pushing local changes to it.
|
|
@@ -158,13 +187,161 @@ Bulk sync behavior:
|
|
|
158
187
|
- Sync logs show direction explicitly: `RevoEngine -> path` for pull and `RevoEngine <- path` for push.
|
|
159
188
|
- Bulk runs print a summary such as `Deployed 54/67, Skipped 13/67 in 13s`.
|
|
160
189
|
|
|
190
|
+
## Environments
|
|
191
|
+
|
|
192
|
+
Save named environment profiles once, then compare and mirror components between environments terraform-style:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
revo env add develop --url https://api.dev.revong.com
|
|
196
|
+
revo env add e2e --url https://api.e2e.revong.com
|
|
197
|
+
revo env list
|
|
198
|
+
revo env remove e2e
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`revo env add` prompts for the API key, validates it against `/api/v1/me`, and stores the profile (with the inferred instance) next to your default login in the OS config directory. Use `--offline` to skip validation. `revo auth logout` clears only the default login and keeps named environment profiles.
|
|
202
|
+
|
|
203
|
+
Compare two environments:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
revo env diff --from develop --to e2e
|
|
207
|
+
revo env diff --from develop --to e2e --json
|
|
208
|
+
revo env diff --from develop --to e2e --strict # CI drift gate: non-zero exit on any difference
|
|
209
|
+
revo env diff --from develop --to e2e --prune # classify target-only components as deletes
|
|
210
|
+
revo env diff --from develop --to e2e --key Forms/Card --category Shared
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The special name `default` refers to your normal login (`revo auth login` / `REVO_TOKEN`), so `--from default --to e2e` works without saving a profile for the default.
|
|
214
|
+
|
|
215
|
+
Components are matched across environments by a **stable key** stored in resource metadata by the platform. The metadata property defaults to `stableKey`; initialize a workspace with `revo project init --stable-key-name <name>` or pass `--stable-key-name <name>` to env commands to use a different property. `revo metadata apply` writes this value to `metadata` for components. Env sync reads `metadata`, `metaData`, and `resourceMetadata` for compatibility, but it does not generate stable keys while creating resources. Project metadata also stores `trackedResources`, defaulting to `all`; env commands read it or accept `--resources <list>` directly. For older components that do not have metadata yet, the CLI falls back to normalized `category/name`, because component ids and element ids are instance-local. Content comparison uses a portable hash that ignores ids, versions, and timestamps. Two components in one environment that share the same stable key are reported as a `collision` and block `apply` until resolved or scoped out with `--key`/`--category`.
|
|
216
|
+
|
|
217
|
+
Mirror components from a source environment into a target:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
revo env apply --from develop --to e2e # prints the plan, then asks for confirmation
|
|
221
|
+
revo env apply --from develop --to e2e --dry-run # plan only, never writes
|
|
222
|
+
revo env apply --from develop --to e2e --yes # confirm creates/updates non-interactively
|
|
223
|
+
revo env apply --from develop --to e2e --yes --prune --force # full mirror including deletes
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Apply semantics:
|
|
227
|
+
|
|
228
|
+
- Creates run first, then updates, then deletes; failures are reported per component and the run continues.
|
|
229
|
+
- `--yes` only confirms the bulk apply. Deletes additionally require `--force` (non-interactive) or a second dedicated confirmation (interactive), matching the component sync semantics.
|
|
230
|
+
- Deletes only happen with `--prune`; without it, target-only components are reported as `orphan` and left alone.
|
|
231
|
+
- Element identity is preserved on update: source elements are matched to the target's elements by key, so the target keeps its own element ids.
|
|
232
|
+
- After every create/update the CLI re-fetches the target component and verifies its content hash against the source; mismatches are flagged as `verification mismatch`.
|
|
233
|
+
|
|
234
|
+
## Environment sync runbook
|
|
235
|
+
|
|
236
|
+
Use this flow when preparing one environment to track and mirror resources with the CLI. The examples use `test` as the source and `dev` as the target.
|
|
237
|
+
|
|
238
|
+
1. Initialize the local project metadata:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
revo project init
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
For CI or non-interactive setup:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
revo project init --identity-mode stableKey --stable-key-name stableKey --resources all
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
This writes `.revoengine/revo.json` with:
|
|
251
|
+
|
|
252
|
+
- `componentIdentity.mode`: `stableKey` for portable identity or `componentId` for legacy instance-bound identity.
|
|
253
|
+
- `componentIdentity.metadataProperty`: the component metadata property used in stable-key mode. Default: `stableKey`.
|
|
254
|
+
- `trackedResources`: resource families the CLI should track. Default: `all`; this CLI currently tracks components.
|
|
255
|
+
|
|
256
|
+
2. Save environment profiles:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
revo env add test --url https://api.test.revong.com
|
|
260
|
+
revo env add dev --url https://api.dev.revong.com
|
|
261
|
+
revo env list
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
For one-off commands without a saved profile, use `default` plus environment variables:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
REVO_URL="https://api.dev.revong.com" REVO_TOKEN="$REVO_DEV_TOKEN" revo metadata plan --env default
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
3. Plan metadata backfill before writing anything:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
revo metadata plan --env test
|
|
274
|
+
revo metadata plan --env dev
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Use JSON when reviewing exact resources:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
revo metadata plan --env test --json
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
4. Apply metadata backfill:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
revo metadata apply --env test --yes
|
|
287
|
+
revo metadata apply --env dev --yes
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Important: if the same resources already exist in multiple environments, do not treat independently generated random suffixes as interchangeable. `category/name#aaaa1111` and `category/name#bbbb2222` are different stable keys, even when the readable prefix matches. Pick a source of truth and make the target environment use the exact same metadata value, including casing, before running destructive sync.
|
|
291
|
+
|
|
292
|
+
5. Verify metadata backfill:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
revo metadata plan --env test
|
|
296
|
+
revo metadata plan --env dev
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Expected after a successful backfill:
|
|
300
|
+
|
|
301
|
+
```text
|
|
302
|
+
Plan: 0 to update, N already populated.
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
6. Preview environment drift:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
revo env diff --from test --to dev
|
|
309
|
+
revo env diff --from test --to dev --json
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Use `--strict` in CI:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
revo env diff --from test --to dev --strict
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
7. Apply non-destructive sync:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
revo env apply --from test --to dev --dry-run
|
|
322
|
+
revo env apply --from test --to dev --yes
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Without `--prune`, target-only resources are reported as `orphan` and left untouched.
|
|
326
|
+
|
|
327
|
+
8. Apply full mirror with orphan removal:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
revo env diff --from test --to dev --prune
|
|
331
|
+
revo env apply --from test --to dev --prune --dry-run
|
|
332
|
+
revo env apply --from test --to dev --prune --yes --force
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Only use `--prune --force` after reviewing the plan. It deletes target resources that are missing from the source.
|
|
336
|
+
|
|
161
337
|
## Configuration
|
|
162
338
|
|
|
163
339
|
The CLI reads configuration in this order:
|
|
164
340
|
|
|
165
341
|
1. CLI flags such as `--url` and `--token`
|
|
166
|
-
2.
|
|
167
|
-
3.
|
|
342
|
+
2. A named environment profile when a command is invoked with `--from`/`--to` (profiles resolve exclusively and are not mixed with environment variables or the stored default login)
|
|
343
|
+
3. Environment variables such as `REVO_URL` and `REVO_TOKEN`
|
|
344
|
+
4. Stored config in the OS config directory
|
|
168
345
|
|
|
169
346
|
This means environment variables override the locally stored config, which is the intended setup for CI and non-interactive automation.
|
|
170
347
|
|
package/dist/src/cli.js
CHANGED
|
@@ -2,7 +2,7 @@ import { realpathSync } from 'node:fs';
|
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
3
|
import { RevoClient } from "./client.js";
|
|
4
4
|
import { resolveRuntimeConfig } from "./config.js";
|
|
5
|
-
import { handleAuthCommand, handleComponentCommand, handleEndpointsCommand, handleInfoCommand, handleProjectCommand, handleRequestCommand, handleSearchCommand, } from "./commands/index.js";
|
|
5
|
+
import { handleAuthCommand, handleComponentCommand, handleEndpointsCommand, handleEnvCommand, handleInfoCommand, handleMetadataCommand, handleProjectCommand, handleRequestCommand, handleSearchCommand, } from "./commands/index.js";
|
|
6
6
|
import { buildRuntimeViewModel } from "./runtime-view.js";
|
|
7
7
|
import { renderHelp, renderOverview } from "./ui.js";
|
|
8
8
|
import { maybeNotifyUpdateAvailable } from "./update-notifier.js";
|
|
@@ -196,6 +196,14 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
196
196
|
await handleEndpointsCommand(context);
|
|
197
197
|
return;
|
|
198
198
|
}
|
|
199
|
+
if (command === 'env') {
|
|
200
|
+
await handleEnvCommand(context);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (command === 'metadata') {
|
|
204
|
+
await handleMetadataCommand(context);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
199
207
|
if (command === 'project') {
|
|
200
208
|
await handleProjectCommand(context);
|
|
201
209
|
return;
|
package/dist/src/client.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export type ClientOptions = {
|
|
|
17
17
|
baseUrl?: string;
|
|
18
18
|
instance?: string;
|
|
19
19
|
token?: string;
|
|
20
|
+
envName?: string;
|
|
20
21
|
fetch?: typeof fetch;
|
|
21
22
|
};
|
|
22
23
|
export type ApiResponse<T = unknown> = {
|
|
@@ -37,6 +38,7 @@ export declare class PermissionDeniedError extends ApiError {
|
|
|
37
38
|
path: string;
|
|
38
39
|
constructor(path: string, message: string, data: unknown);
|
|
39
40
|
}
|
|
41
|
+
declare const COMPONENT_LIST_PAGE_SIZE = 200;
|
|
40
42
|
declare function buildUrl(baseUrl: string, requestPath: string, query?: Record<string, unknown>): URL;
|
|
41
43
|
declare function normalizeToken(token?: string): string;
|
|
42
44
|
export declare function extractProfileInstanceId(profile: unknown): string;
|
|
@@ -71,7 +73,15 @@ export declare class RevoClient {
|
|
|
71
73
|
debugComponentStream(requestPath: string, body: unknown): AsyncGenerator<DebugStreamEvent>;
|
|
72
74
|
search(params: Record<string, unknown>): Promise<unknown>;
|
|
73
75
|
listComponents(options?: ComponentListRequest): Promise<unknown>;
|
|
76
|
+
listAllComponents(): Promise<{
|
|
77
|
+
components: Record<string, unknown>[];
|
|
78
|
+
discoveredTotal: number | null;
|
|
79
|
+
}>;
|
|
74
80
|
getComponent(componentId: string): Promise<unknown>;
|
|
81
|
+
createComponent(body: unknown): Promise<unknown>;
|
|
82
|
+
updateComponent(componentId: string, body: unknown): Promise<unknown>;
|
|
83
|
+
deleteComponent(componentId: string): Promise<ApiResponse<unknown>>;
|
|
75
84
|
saveComponentElements(componentId: string, body: unknown): Promise<ApiResponse<unknown>>;
|
|
76
85
|
}
|
|
86
|
+
export { COMPONENT_LIST_PAGE_SIZE };
|
|
77
87
|
export { buildUrl, normalizeToken };
|
package/dist/src/client.js
CHANGED
|
@@ -25,6 +25,102 @@ export class PermissionDeniedError extends ApiError {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
const REQUEST_DELAY_MS = 50;
|
|
28
|
+
const COMPONENT_LIST_PAGE_SIZE = 200;
|
|
29
|
+
const ACTIVE_COMPONENT_LIST_FILTER = {
|
|
30
|
+
'filter[and][0][field]': 'deletedAt',
|
|
31
|
+
'filter[and][0][op]': 'isNull',
|
|
32
|
+
};
|
|
33
|
+
function isRecord(value) {
|
|
34
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
35
|
+
}
|
|
36
|
+
function readNumber(value) {
|
|
37
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
38
|
+
}
|
|
39
|
+
function unwrapList(value) {
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
if (value && typeof value === 'object') {
|
|
44
|
+
const candidate = value;
|
|
45
|
+
if (Array.isArray(candidate.data)) {
|
|
46
|
+
return candidate.data;
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(candidate.items)) {
|
|
49
|
+
return candidate.items;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
function resolveNextComponentListRequest(value) {
|
|
55
|
+
if (typeof value === 'string' && value) {
|
|
56
|
+
return {
|
|
57
|
+
path: value,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (!isRecord(value)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
for (const key of ['path', 'url', 'href']) {
|
|
64
|
+
if (typeof value[key] === 'string' && value[key]) {
|
|
65
|
+
return {
|
|
66
|
+
path: value[key],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const query = {};
|
|
71
|
+
for (const key of ['cursor', 'page', 'skip', 'take', 'limit', 'offset']) {
|
|
72
|
+
const candidate = value[key];
|
|
73
|
+
if (typeof candidate === 'string' || typeof candidate === 'number') {
|
|
74
|
+
query[key] = candidate;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return Object.keys(query).length > 0 ? { query } : null;
|
|
78
|
+
}
|
|
79
|
+
function buildActiveComponentListQuery(skip, take = COMPONENT_LIST_PAGE_SIZE) {
|
|
80
|
+
return {
|
|
81
|
+
take,
|
|
82
|
+
skip,
|
|
83
|
+
count: true,
|
|
84
|
+
...ACTIVE_COMPONENT_LIST_FILTER,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function withActiveComponentListFilter(request) {
|
|
88
|
+
if (!request.query) {
|
|
89
|
+
return request;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
...request,
|
|
93
|
+
query: {
|
|
94
|
+
...request.query,
|
|
95
|
+
count: request.query.count ?? true,
|
|
96
|
+
...ACTIVE_COMPONENT_LIST_FILTER,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function unwrapComponentListPage(value) {
|
|
101
|
+
const items = unwrapList(value);
|
|
102
|
+
if (!isRecord(value)) {
|
|
103
|
+
return {
|
|
104
|
+
items,
|
|
105
|
+
total: null,
|
|
106
|
+
nextRequest: null,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const meta = isRecord(value.meta) ? value.meta : null;
|
|
110
|
+
const pagination = isRecord(value.pagination) ? value.pagination : null;
|
|
111
|
+
const total = readNumber(value.total)
|
|
112
|
+
?? readNumber(value.count)
|
|
113
|
+
?? (meta ? readNumber(meta.total) ?? readNumber(meta.count) : null)
|
|
114
|
+
?? (pagination ? readNumber(pagination.total) ?? readNumber(pagination.count) : null);
|
|
115
|
+
const nextRequest = resolveNextComponentListRequest(value.next)
|
|
116
|
+
?? (meta ? resolveNextComponentListRequest(meta.next) : null)
|
|
117
|
+
?? (pagination ? resolveNextComponentListRequest(pagination.next) : null);
|
|
118
|
+
return {
|
|
119
|
+
items,
|
|
120
|
+
total,
|
|
121
|
+
nextRequest,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
28
124
|
function normalizeBaseUrl(baseUrl) {
|
|
29
125
|
const url = new URL(baseUrl || DEFAULT_BASE_URL);
|
|
30
126
|
url.pathname = url.pathname.replace(/\/+$/, '');
|
|
@@ -428,11 +524,72 @@ export class RevoClient {
|
|
|
428
524
|
query: options.query,
|
|
429
525
|
});
|
|
430
526
|
}
|
|
527
|
+
async listAllComponents() {
|
|
528
|
+
const components = [];
|
|
529
|
+
const seenRequests = new Set();
|
|
530
|
+
let nextRequest = {
|
|
531
|
+
query: buildActiveComponentListQuery(0),
|
|
532
|
+
};
|
|
533
|
+
let discoveredTotal = null;
|
|
534
|
+
while (nextRequest) {
|
|
535
|
+
const requestKey = JSON.stringify(nextRequest);
|
|
536
|
+
if (seenRequests.has(requestKey)) {
|
|
537
|
+
throw new Error('Component list pagination loop detected while listing all components.');
|
|
538
|
+
}
|
|
539
|
+
seenRequests.add(requestKey);
|
|
540
|
+
const pageValue = await this.listComponents(nextRequest);
|
|
541
|
+
const page = unwrapComponentListPage(pageValue);
|
|
542
|
+
if (page.total !== null) {
|
|
543
|
+
discoveredTotal = page.total;
|
|
544
|
+
}
|
|
545
|
+
for (const item of page.items) {
|
|
546
|
+
if (isRecord(item)) {
|
|
547
|
+
components.push(item);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (page.total !== null && components.length >= page.total) {
|
|
551
|
+
nextRequest = null;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (page.nextRequest) {
|
|
555
|
+
nextRequest = withActiveComponentListFilter(page.nextRequest);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (nextRequest.query
|
|
559
|
+
&& typeof nextRequest.query.take === 'number'
|
|
560
|
+
&& page.items.length === nextRequest.query.take) {
|
|
561
|
+
nextRequest = {
|
|
562
|
+
query: buildActiveComponentListQuery(components.length, nextRequest.query.take),
|
|
563
|
+
};
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
nextRequest = null;
|
|
567
|
+
}
|
|
568
|
+
return { components, discoveredTotal };
|
|
569
|
+
}
|
|
431
570
|
async getComponent(componentId) {
|
|
432
571
|
return this.requestData('GET', `/api/v1/component/${componentId}`);
|
|
433
572
|
}
|
|
573
|
+
async createComponent(body) {
|
|
574
|
+
return this.requestData('POST', '/api/v1/component', {
|
|
575
|
+
body,
|
|
576
|
+
spinnerLabel: 'Creating component',
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
async updateComponent(componentId, body) {
|
|
580
|
+
return this.requestData('PUT', `/api/v1/component/${componentId}`, {
|
|
581
|
+
body,
|
|
582
|
+
spinnerLabel: 'Updating component',
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
async deleteComponent(componentId) {
|
|
586
|
+
return this.request('DELETE', `/api/v1/component/${componentId}`, {
|
|
587
|
+
spinnerLabel: 'Deleting component',
|
|
588
|
+
});
|
|
589
|
+
}
|
|
434
590
|
async saveComponentElements(componentId, body) {
|
|
435
591
|
return this.request('POST', `/api/v1/component/${componentId}/save`, { body });
|
|
436
592
|
}
|
|
437
593
|
}
|
|
594
|
+
export { COMPONENT_LIST_PAGE_SIZE };
|
|
438
595
|
export { buildUrl, normalizeToken };
|