@pixlcore/xycli 1.0.7 → 1.0.9
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 +2 -2
- package/docs/help.md +22 -3
- package/docs/sync.md +71 -9
- package/docs/two-way-git-tutorial.md +19 -3
- package/index.js +32 -5
- package/lib/apikey.js +1 -1
- package/lib/buckets.js +1 -5
- package/lib/marketplace.js +1 -1
- package/lib/sync-buckets.js +315 -0
- package/lib/sync-syshook.js +207 -0
- package/lib/sync.js +128 -43
- package/lib/transfer.js +3 -3
- package/lib/utils.js +8 -3
- package/package.json +2 -2
- package/test/README.md +5 -3
- package/test/help.test.js +56 -0
- package/test/sync-buckets-unit.test.js +188 -0
- package/test/sync-buckets.test.js +133 -0
- package/test/sync-syshook-unit.test.js +231 -0
- package/test/sync-unit.test.js +118 -2
- package/test/toast.test.js +3 -3
- package/test/utils-unit.test.js +50 -0
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ These switches control output or execution across many commands. Add them to the
|
|
|
134
134
|
| Switch | Short form | What it does |
|
|
135
135
|
| --- | --- | --- |
|
|
136
136
|
| `--verbose` | `-v` | Show extra detail. Resource views may expand scripts, parameters, or monitoring sections; many mutation commands also display API requests and responses. |
|
|
137
|
-
| `--quiet` | `-q` | Suppress ordinary terminal output and progress displays. JSON data still prints, and
|
|
137
|
+
| `--quiet` | `-q` | Suppress ordinary terminal output and progress displays. JSON data still prints, and human-readable warnings and errors remain visible on stderr. |
|
|
138
138
|
| `--dry` | | Preview a supported operation without applying it. Resource mutations, imports, exports, sync, and API calls use this to show planned changes or requests. It does not prevent connection checks or reads, and does not apply to local `xy config` updates. |
|
|
139
139
|
| `--format json` | `-f json` | Display supported results as indented JSON instead of the usual report or table. |
|
|
140
140
|
| `--format jsonc` | `-f jsonc` | Display supported results as compact JSON on one line. Here, `jsonc` means compact JSON. |
|
|
@@ -157,7 +157,7 @@ XYOPS_COLOR=false xy events --format json --quiet > ./events.json
|
|
|
157
157
|
xy categories -f jsonc -q > ./categories.json
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
Human-readable warnings and errors print to stderr even in quiet mode, so redirecting JSON output to a file leaves those diagnostics visible in your terminal. Structured JSON results may also include warnings and errors. Sync warnings and errors still produce a nonzero exit status. Check exit status and any structured results when scripting.
|
|
161
161
|
|
|
162
162
|
JSON output varies by command: some local collection views return all matches even when their table view is paginated, while database searches may return only the selected page. Some commands print multiple JSON values, such as the request and response from `xy api`. Check the command reference when using the output in a script.
|
|
163
163
|
|
package/docs/help.md
CHANGED
|
@@ -200,6 +200,8 @@ xy sync ./ --down events,plugins
|
|
|
200
200
|
xy sync ./ --up categories,plugins --down events --dry
|
|
201
201
|
xy sync ./ --up events --down events --dry
|
|
202
202
|
xy sync ./ --up all --dry
|
|
203
|
+
xy sync ./ --up buckets --dry
|
|
204
|
+
xy sync ./ --down buckets
|
|
203
205
|
xy sync --up events,plugins --verbose
|
|
204
206
|
```
|
|
205
207
|
|
|
@@ -211,8 +213,9 @@ Pass one or more base directories, or omit them to scan the current directory. F
|
|
|
211
213
|
- `--down TYPES` updates local files from xyOps.
|
|
212
214
|
- `--dry` previews changes without applying them.
|
|
213
215
|
- `--verbose` shows full diffs and API requests and responses.
|
|
216
|
+
- `--ignore_missing` skips local sources whose type and ID are absent from xyOps, without a warning or failure. It leaves their files untouched.
|
|
214
217
|
|
|
215
|
-
You must enable at least one direction. `TYPES` is a comma-separated list of `alerts`, `api_keys`, `categories`, `channels`, `events`, `groups`, `monitors`, `plugins`, `tags`, or `web_hooks`. Use `all` to select every supported type. Workflows are included under `events`.
|
|
218
|
+
You must enable at least one direction. `TYPES` is a comma-separated list of `alerts`, `api_keys`, `buckets`, `categories`, `channels`, `events`, `groups`, `monitors`, `plugins`, `tags`, or `web_hooks`. Use `all` to select every supported type. Workflows are included under `events`. Bucket sync requires xyOps v1.1.2 or later. Secrets, Users, and Roles are not supported.
|
|
216
219
|
|
|
217
220
|
Enable both directions for the same type to use experimental two-way sync. The newest modification time wins, including external property files. Git checkouts can reset filesystem timestamps, so prefer one-way sync when reliable conflict handling matters. Dry runs never update xyOps, write files, run completion commands, or send notifications.
|
|
218
221
|
|
|
@@ -222,6 +225,8 @@ Repeated sources with the same type and ID generate warnings, even if their cont
|
|
|
222
225
|
|
|
223
226
|
External string properties are loaded automatically from adjacent files named `BASENAME-PROPERTY.EXT`, such as `My-Plugin-script.js` beside `My-Plugin.json`, or `My-Event-params.script.sh` beside `My-Event.json`. The extension is your choice. Keep only one external file per property, and rename its basename along with the JSON file.
|
|
224
227
|
|
|
228
|
+
Bucket metadata uses a normal XYPDF source. For a Bucket titled `My Bucket Name`, its separate JSON data and uploaded files live beside `buckets/My-Bucket-Name.json` in `buckets/My-Bucket-Name/data.json` and `buckets/My-Bucket-Name/files/`. Both `data.json` and `files/` are required for a selected local bucket. Bucket data upsync replaces the entire remote data object. Bucket files are matched by xyOps filename normalization and transferred according to size and modification time. Two-way bucket sync uses the data record and file timestamps from xyOps; a transfer updates local file times to match. Bucket files missing locally are deleted remotely only with up-only `--delete buckets`.
|
|
229
|
+
|
|
225
230
|
**Delete mode**
|
|
226
231
|
|
|
227
232
|
Add `--delete TYPES` to delete xyOps objects that have no matching source in the scanned directories. Deletion requires up-only sync and a complete local inventory. The CLI rejects deletion if upsync is disabled or any downsync direction is enabled, including saved defaults and dry runs. Use `--down false` to disable inherited downsync. See [Delete mode](sync.md#delete-mode) before using it.
|
|
@@ -235,6 +240,16 @@ xy sync ./ --up events,plugins --delete events
|
|
|
235
240
|
|
|
236
241
|
**The scanned directories must contain a complete inventory of the other objects you intend to keep for every type selected for deletion.** Delete mode is not limited to previously synced objects. A missing file, incomplete export, or unavailable mount can cause unintended deletions of your own definitions. Verify that all source directories are available and inspect a dry run before removing `--dry`. Source warnings or scan errors stop the run before any updates begin. If an update or sync-tracking API operation fails, earlier successful updates remain in place, but the entire delete pass is skipped for that run.
|
|
237
242
|
|
|
243
|
+
**System Hook deletions**
|
|
244
|
+
|
|
245
|
+
Use `--syshook ACTION` when a matching xyOps System Hook launches the CLI. This supports deletion hooks for every standard sync definition type except Buckets. It reads the activity JSON from STDIN and removes only the XYPDF with the matching type and ID, plus its neighboring property files. It does not run ordinary sync, compare definitions, or call the xyOps API. Pass exactly one sync directory, and use an absolute path because System Hooks start in a temporary directory. See the [supported actions](sync.md#system-hook-deletion) for the exact hook names, including `apikey_delete` for API Keys.
|
|
246
|
+
|
|
247
|
+
```sh
|
|
248
|
+
xy sync /srv/xyops-automation --syshook event_delete --down_cmd /usr/local/sbin/commit-xyops-deletion
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`--down_cmd` runs from the sync directory only after files are actually removed. A missing local source is a successful no-op; duplicate matching sources stop the command without deleting either one. `--dry` previews the files without deleting them or running `down_cmd`. No xyOps URL or API Key is needed. See [System Hook deletion](sync.md#system-hook-deletion) for configuration and operational details.
|
|
252
|
+
|
|
238
253
|
**Completion commands and notifications**
|
|
239
254
|
|
|
240
255
|
```sh
|
|
@@ -250,7 +265,7 @@ xy sync ./ --up events,plugins \
|
|
|
250
265
|
- `--error_email ADDRESS` sends warnings and errors by email through xyOps.
|
|
251
266
|
- `--error_event EVENT_ID` runs an Event on warnings or errors.
|
|
252
267
|
|
|
253
|
-
Completion commands run in a local shell in the first base directory.
|
|
268
|
+
Completion commands run in a local shell in the first base directory. Successful up-only deletion triggers `up_cmd`; local System Hook deletion triggers an explicitly supplied `down_cmd`. Event notifications receive `input.data.errors` and `input.data.warnings`.
|
|
254
269
|
|
|
255
270
|
The configured API Key needs the appropriate resource permissions and `update_state` for sync tracking updates. Notification actions also need their usual permissions. Earlier successful changes are not rolled back if a later operation fails. Retry the sync after correcting an error; previously applied updates compare equal, and deletion remains disabled until the pre-delete phase completes without warnings or errors.
|
|
256
271
|
|
|
@@ -282,6 +297,7 @@ Export existing xyOps definitions into a starter sync tree in the current direct
|
|
|
282
297
|
|
|
283
298
|
```sh
|
|
284
299
|
xy sync setup events plugins categories --dry
|
|
300
|
+
xy sync setup buckets
|
|
285
301
|
xy sync setup events plugins categories --file_props script,params.script
|
|
286
302
|
xy sync setup events --file_props params.script --default_ext ps1
|
|
287
303
|
xy sync setup events plugins --new
|
|
@@ -296,9 +312,12 @@ Choose one or more resource types separated by spaces, or use `all`. Supported t
|
|
|
296
312
|
|
|
297
313
|
Setup creates one folder per resource type and one XYPDF JSON file per object, using its title as a filename slug. Selecting `events` exports both Events and workflows, with workflows placed in a separate `workflows` folder. Events and workflows are grouped into subfolders using their Category title as a slug. Both are synced with `--up events` or `--down events`. Setup stops before writing if an Event or workflow selected for export refers to a missing Category.
|
|
298
314
|
|
|
315
|
+
For each Bucket, setup also writes `BUCKET-SLUG/data.json` and downloads its files into `BUCKET-SLUG/files/` beside the XYPDF file. `--new` skips Bucket definitions already present locally, including their content. `--force` permits overwriting existing Bucket content files.
|
|
316
|
+
|
|
299
317
|
- `--file_props PATHS` extracts string properties into adjacent files.
|
|
300
318
|
- `--default_ext EXT` sets the fallback neighbor extension instead of `txt`.
|
|
301
319
|
- `--new` writes only remote definitions not already represented locally.
|
|
320
|
+
- `--ignore_missing` lets `--new` skip local sources whose type and ID are absent from xyOps.
|
|
302
321
|
- `--down_cmd COMMAND` runs after setup writes one or more definitions.
|
|
303
322
|
- `--cmd_timeout SECONDS` changes the command timeout from 30 seconds.
|
|
304
323
|
- `--stock` includes stock objects, which are omitted by default.
|
|
@@ -310,7 +329,7 @@ For `--file_props`, use comma-separated property names or dot paths, such as `sc
|
|
|
310
329
|
|
|
311
330
|
The same property selection also checks every workflow node automatically. For example, `params.script` in a node's `data` becomes a neighbor such as `My-Workflow-workflow.nt3sr3y4.params.script.sh`. The node ID in the filename identifies which node owns the property. No additional setup option is needed.
|
|
312
331
|
|
|
313
|
-
Use `--new` from the root of an existing sync tree to add definitions that exist in xyOps but have no local source. Setup scans recursively and matches existing sources by item type and exact ID, so renamed files and custom folder layouts are preserved. New definitions use the normal suggested type and Category folders. Malformed, duplicate, unsupported, or orphaned local sources stop the operation before any new files are written. `--new` cannot be combined with `--force`, and it does not create new definitions in xyOps.
|
|
332
|
+
Use `--new` from the root of an existing sync tree to add definitions that exist in xyOps but have no local source. Setup scans recursively and matches existing sources by item type and exact ID, so renamed files and custom folder layouts are preserved. New definitions use the normal suggested type and Category folders. Malformed, duplicate, unsupported, or orphaned local sources stop the operation before any new files are written. With `--ignore_missing`, only sources whose type and ID are absent from xyOps are skipped. `--new` cannot be combined with `--force`, and it does not create new definitions in xyOps.
|
|
314
333
|
|
|
315
334
|
An explicitly supplied `--down_cmd` runs from the current setup directory after at least one definition is written. It does not run when `--new` finds nothing or during a dry run. Saved sync completion commands are not inherited by setup.
|
|
316
335
|
|
package/docs/sync.md
CHANGED
|
@@ -54,6 +54,7 @@ Use these names with `--up`, `--down`, `--delete`, and setup:
|
|
|
54
54
|
| --- | --- |
|
|
55
55
|
| `alerts` | Alert definitions, rather than their invocations. |
|
|
56
56
|
| `api_keys` | API Key definitions. |
|
|
57
|
+
| `buckets` | Bucket definitions, JSON data, and uploaded files. Requires xyOps 1.1.2 or later. |
|
|
57
58
|
| `categories` | Event Categories. |
|
|
58
59
|
| `channels` | Notification Channels. |
|
|
59
60
|
| `events` | Events and workflows. |
|
|
@@ -65,7 +66,7 @@ Use these names with `--up`, `--down`, `--delete`, and setup:
|
|
|
65
66
|
|
|
66
67
|
`all` selects every supported type. Supply multiple types as a comma-separated string without spaces, such as `events,plugins,categories`.
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
Secret Vaults, Users, and Roles are not supported by sync. Individual workers, jobs, Tickets, Alert history, and snapshots are also outside its definition-sync scope. Workflows use the `events` selection even though setup places them in a separate folder (for convenience).
|
|
69
70
|
|
|
70
71
|
### Permissions
|
|
71
72
|
|
|
@@ -127,6 +128,8 @@ xyops-automation/
|
|
|
127
128
|
|
|
128
129
|
Setup creates one folder per selected resource type and one JSON file per definition. Event and workflow exports are separated into `events/` and `workflows/`, then grouped into subfolders using their Category titles. Both participate in normal sync through the `events` selection.
|
|
129
130
|
|
|
131
|
+
Bucket setup also creates a sibling directory for each Bucket's content. For example, a Bucket titled `My Bucket Name` has `buckets/My-Bucket-Name.json`, `buckets/My-Bucket-Name/data.json`, and `buckets/My-Bucket-Name/files/` for its definition, data, and uploaded files. Move or rename the sibling directory with the XYPDF file. A normal Bucket sync requires both the data file and files directory, even when they contain an empty object and no files. Bucket data upsync replaces the full remote data object, including removed keys. File transfers match xyOps normalized filenames and align local modification times with the server's timestamps after each transfer.
|
|
132
|
+
|
|
130
133
|
Folder and filenames come from Category and definition titles: sequences of non-word characters become hyphens, and leading or trailing hyphens are removed. For example, the Event `Nightly Backup` in the `Operations` Category becomes `events/Operations/Nightly-Backup.json`. Different titles can produce the same path, so review the preview for collisions. Setup stops before writing anything if an Event or workflow selected for export refers to a Category that is not present in xyOps.
|
|
131
134
|
|
|
132
135
|
### 4. Review and version the tree
|
|
@@ -169,7 +172,7 @@ xy sync setup events plugins categories --new --file_props script,params.script
|
|
|
169
172
|
xy sync setup events plugins categories --new --file_props script,params.script
|
|
170
173
|
```
|
|
171
174
|
|
|
172
|
-
New-only setup scans the current directory recursively and indexes existing XYPDF sources by item type and exact ID. Renamed files and custom folders are preserved. Remote definitions not found in that index are written to setup's normal type and Category folders; existing definitions are skipped without being rewritten. Malformed, duplicate, unsupported, or locally orphaned sources stop the operation before new files are written. `--new` cannot be combined with `--force`.
|
|
175
|
+
New-only setup scans the current directory recursively and indexes existing XYPDF sources by item type and exact ID. Renamed files and custom folders are preserved. Remote definitions not found in that index are written to setup's normal type and Category folders; existing definitions are skipped without being rewritten. Malformed, duplicate, unsupported, or locally orphaned sources stop the operation before new files are written. Add `--ignore_missing` to skip only local sources whose type and ID are absent from xyOps. `--new` cannot be combined with `--force`.
|
|
173
176
|
|
|
174
177
|
This is an additive local export. It does not create definitions in xyOps, and ordinary upsync or downsync still updates only definitions that already exist on both sides.
|
|
175
178
|
|
|
@@ -391,15 +394,17 @@ Relative paths resolve against the CLI's current working directory. Completion c
|
|
|
391
394
|
| `--up TYPES` | `sync.up` | Disabled | Sync upwards to xyOps from local sources for these types. |
|
|
392
395
|
| `--down TYPES` | `sync.down` | Disabled | Down downwards to existing local sources from xyOps for these types. |
|
|
393
396
|
| `--delete TYPES` | `sync.delete` | Disabled | Delete remote objects with no local source. Use only in an up-only workflow; see [Delete mode](#delete-mode). |
|
|
397
|
+
| `--syshook ACTION` | n/a | Disabled | Handle one supported definition deletion activity from STDIN as a local-only deletion; see [System Hook deletion](#system-hook-deletion). |
|
|
398
|
+
| `--ignore_missing` | `sync.ignore_missing` | Off | Skip local sources whose type and ID are absent from xyOps, without a warning or failure. Leave their files untouched. |
|
|
394
399
|
| `--up_cmd COMMAND` | `sync.up_cmd` | None | Run a local shell command after at least one successful upsync. |
|
|
395
|
-
| `--down_cmd COMMAND` | `sync.down_cmd` | None | Run a local shell command after at least one successful downsync. |
|
|
400
|
+
| `--down_cmd COMMAND` | `sync.down_cmd` | None | Run a local shell command after at least one successful downsync, or an explicit command after a System Hook deletion. |
|
|
396
401
|
| `--cmd_timeout SECONDS` | `sync.cmd_timeout` | `30` | Timeout for each completion command. |
|
|
397
402
|
| `--error_email ADDRESS` | `sync.error_email` | None | Email collected warnings and errors through xyOps. |
|
|
398
403
|
| `--error_event EVENT_ID` | `sync.error_event` | None | Launch an Event with collected warnings and errors in its input data. |
|
|
399
404
|
| `--lock_file PATH` | `sync.lock_file` | Temporary file keyed by `base_url` | Use an explicit path for the built-in PID lock. |
|
|
400
405
|
| `--dry` | n/a | Off | Dry-Run Mode: Preview without writes, completion commands, or notifications. |
|
|
401
406
|
| `--verbose`, `-v` | n/a | Off | Show complete diffs and additional request, response, and write details. |
|
|
402
|
-
| `--quiet`, `-q` | n/a | Off | Suppress routine output and progress.
|
|
407
|
+
| `--quiet`, `-q` | n/a | Off | Suppress routine output and progress. Warnings and errors remain visible on standard error. |
|
|
403
408
|
|
|
404
409
|
At least one direction must be enabled. The command is a single run, not a background watcher; repeat it through a scheduler or hook for automatic operation.
|
|
405
410
|
|
|
@@ -414,6 +419,7 @@ At least one direction must be enabled. The command is a single run, not a backg
|
|
|
414
419
|
| `--file_props PATHS` | Extract string properties into adjacent neighbor files. Accepts comma-separated dot paths or a JSON array. |
|
|
415
420
|
| `--default_ext EXT` | Use this extension instead of `txt` when a neighbor's type cannot be detected. A leading dot is optional. |
|
|
416
421
|
| `--new` | Export only remote definitions whose type and ID are not already present in the current local tree. |
|
|
422
|
+
| `--ignore_missing` | With `--new`, skip local sources whose type and ID are absent from xyOps. Leave their files untouched. |
|
|
417
423
|
| `--down_cmd COMMAND` | Run a local shell command after setup writes one or more definitions. |
|
|
418
424
|
| `--cmd_timeout SECONDS` | Set the setup completion-command timeout in seconds. The default is `30`. |
|
|
419
425
|
| `--stock` | Include stock definitions that ships wit xyOps (e.g. "Shell Plugin"), which setup normally omits. |
|
|
@@ -439,7 +445,61 @@ For each differing definition, the engine compares xyOps's `modified` timestamp
|
|
|
439
445
|
|
|
440
446
|
Two-way sync is experimental. Keep clocks synchronized, review dry runs, and preserve a history of both configuration and scripts. Git checkout, clone, restore, or file copying can give old content a new filesystem timestamp.
|
|
441
447
|
|
|
442
|
-
|
|
448
|
+
Bucket data and files use their own modification times, separate from the Bucket definition. When contents differ, two-way sync compares `data.json` with the data record's modification time and each local file with the matching manifest date. Equal timestamps with differing Bucket content are reported as conflicts. After a transfer, xyCLI sets the local modification time to the server's returned time.
|
|
449
|
+
|
|
450
|
+
Bucket file comparison uses size and modification time because the manifest has no content hash. A same-size change with an unchanged timestamp cannot be detected.
|
|
451
|
+
|
|
452
|
+
For automatic two-way operation, use a persistent tree and run every minute. Both directions are considered on each run; you do not need separate alternating up and down jobs. Newly created definitions still need a setup export or individual local source.
|
|
453
|
+
|
|
454
|
+
### Remove a definition during two-way sync
|
|
455
|
+
|
|
456
|
+
Two-way sync does not propagate deletions. Definition deletions are usually infrequent, so coordinating them manually is generally practical. Keep `--delete false` in the scheduled command, and remove a definition from both sides deliberately:
|
|
457
|
+
|
|
458
|
+
1. Delete the definition in the xyOps web interface first.
|
|
459
|
+
2. Remove its local XYPDF JSON file and any neighboring external property files. If the tree is stored in Git, commit and push their removal so the dedicated sync checkout receives the deletion.
|
|
460
|
+
3. Run sync again, or resume the schedule, after the local files are gone. Without `--ignore_missing`, pause the schedule during these steps if you want to avoid a failed pass between the two removals.
|
|
461
|
+
|
|
462
|
+
If a sync runs after the xyOps deletion but before local cleanup, the leftover source normally produces a missing-object warning and stops the run before any definition changes. Add `--ignore_missing` to ordinary sync and any following `sync setup --new` command to skip that source and keep processing other definitions. This does not remove the local files or propagate the deletion to Git. Remove those files yourself to complete the deletion. Other scan warnings and errors still stop the run.
|
|
463
|
+
|
|
464
|
+
Do not remove the Git files first while the definition still exists in xyOps. A scheduled `sync setup --new` pass can export the still-existing definition back into the repository. See the [Two-Way Git Sync Tutorial](two-way-git-tutorial.md#delete-a-definition-from-both-sides) for the Git workflow.
|
|
465
|
+
|
|
466
|
+
### System Hook deletion
|
|
467
|
+
|
|
468
|
+
xyOps [System Hooks](https://docs.xyops.io/#Docs/syshooks) can launch xyCLI when a standard synced definition is deleted. Configure each deletion action you need separately. The `--syshook` command reads the activity JSON from STDIN, checks that its action matches the command, and uses the deleted object's type and exact ID to find one local XYPDF source. It removes that source and its neighboring external property files. Other local definitions, including new ones not yet in xyOps, are left alone.
|
|
469
|
+
|
|
470
|
+
| Sync selection | System Hook action |
|
|
471
|
+
| --- | --- |
|
|
472
|
+
| `alerts` | `alert_delete` |
|
|
473
|
+
| `api_keys` | `apikey_delete` |
|
|
474
|
+
| `categories` | `category_delete` |
|
|
475
|
+
| `channels` | `channel_delete` |
|
|
476
|
+
| `events` (including workflows) | `event_delete` |
|
|
477
|
+
| `groups` | `group_delete` |
|
|
478
|
+
| `monitors` | `monitor_delete` |
|
|
479
|
+
| `plugins` | `plugin_delete` |
|
|
480
|
+
| `tags` | `tag_delete` |
|
|
481
|
+
| `web_hooks` | `web_hook_delete` |
|
|
482
|
+
|
|
483
|
+
Buckets are excluded because a Bucket may have separate JSON data and uploaded files alongside its XYPDF source. The `bucket_delete` activity is not supported by `--syshook`.
|
|
484
|
+
|
|
485
|
+
For example, configure the following hooks on the primary conductor, adjusting executable and checkout paths:
|
|
486
|
+
|
|
487
|
+
```json
|
|
488
|
+
{
|
|
489
|
+
"hooks": {
|
|
490
|
+
"event_delete": {
|
|
491
|
+
"shell_exec": "/usr/local/bin/xy sync /srv/xyops-automation --syshook event_delete --down_cmd /usr/local/sbin/commit-xyops-deletion"
|
|
492
|
+
},
|
|
493
|
+
"plugin_delete": {
|
|
494
|
+
"shell_exec": "/usr/local/bin/xy sync /srv/xyops-automation --syshook plugin_delete --down_cmd /usr/local/sbin/commit-xyops-deletion"
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
The `--down_cmd` script can stage, commit, and push the deletions in a dedicated Git checkout. It runs only after at least one file was removed. A missing source is a successful no-op, and duplicate matching sources stop the command before any files are removed. Use `--dry` to preview the selected files without changing them or running the completion command.
|
|
501
|
+
|
|
502
|
+
This mode does not run ordinary sync, fetch remote definitions, or change xyOps. It ignores saved sync direction settings and requires exactly one directory. No xyOps URL or API Key is needed. The CLI still uses a host-local lock, with a fixed default lock key when no `base_url` is configured. System shell hooks run on the primary conductor from a temporary directory, and xyOps limits them to 60 seconds. The conductor must be able to access the checkout and Git credentials. If regular sync runs on another host, its local lock cannot protect this checkout; arrange for both operations to use the same host and lock if they share a checkout. Watch hook failures and Git push failures, because a failed hook is not automatically retried.
|
|
443
503
|
|
|
444
504
|
## Remote-management warnings
|
|
445
505
|
|
|
@@ -499,6 +559,8 @@ Deleting an Event does not automatically remove its dependencies. Remote API rul
|
|
|
499
559
|
|
|
500
560
|
Deletion only begins after every update has completed and the sync-tracking state has been written without warnings or errors. If either phase fails, the entire delete pass is skipped for that run. Earlier successful updates remain in place, so correct the error and retry; definitions already updated will compare equal. Once deletion begins, successful earlier deletions are not rolled back if a later deletion fails.
|
|
501
561
|
|
|
562
|
+
With `--delete buckets`, a remote Bucket file missing from a selected local Bucket's `files/` directory is deleted individually. Removing the Bucket XYPDF source deletes the whole Bucket, including all its data and files. A missing or invalid `data.json`, incomplete file inventory, or failed transfer prevents Bucket file deletion and the whole-object delete pass.
|
|
563
|
+
|
|
502
564
|
An unavailable base directory stops the scan, but an existing empty directory can look like an empty inventory. Do not treat that guard as protection against an empty mount, accidental file removal, or missing exports of your own definitions.
|
|
503
565
|
|
|
504
566
|
Keep deletion disabled in general-purpose cron, Git hooks, and CI until you have a deliberate policy for maintaining and checking the complete inventory. Disable saved deletion for other runs with `--delete false`.
|
|
@@ -523,7 +585,7 @@ xy sync ./ --down events,plugins --down_cmd "./commit-downloads.sh" --cmd_timeou
|
|
|
523
585
|
### When commands run
|
|
524
586
|
|
|
525
587
|
- `up_cmd` runs if at least one object was successfully upsynced (this includes deletes).
|
|
526
|
-
- `down_cmd` runs if at least one object was successfully downsynced.
|
|
588
|
+
- `down_cmd` runs if at least one object was successfully downsynced. In `--syshook` mode, an explicitly supplied `down_cmd` runs after the matching local files are deleted.
|
|
527
589
|
- During setup, an explicitly supplied `down_cmd` runs if at least one definition was written.
|
|
528
590
|
- If both apply, `up_cmd` runs first, then `down_cmd`.
|
|
529
591
|
- Neither runs for an identical/no-op run, a scan that stopped before changes, or a dry run.
|
|
@@ -614,7 +676,7 @@ Runs with no warnings or errors exit with status `0`. Scan warnings prevent chan
|
|
|
614
676
|
|
|
615
677
|
Every `xy sync` command acquires a host-local PID lock before it loads remote definitions or touches local files. This includes ordinary up, down, two-way, delete, dry-run, and setup operations. The lock remains held through completion commands and error notifications, so every CLI entry point uses the same overlap protection automatically.
|
|
616
678
|
|
|
617
|
-
By default, xyCLI creates a lock file in the operating system's temporary directory. Its filename contains a hash of the normalized `base_url`, so syncs targeting the same xyOps instance on one host serialize even when they use different directories or API Keys. Syncs for different instances use different default locks.
|
|
679
|
+
By default, xyCLI creates a lock file in the operating system's temporary directory. Its filename contains a hash of the normalized `base_url`, so syncs targeting the same xyOps instance on one host serialize even when they use different directories or API Keys. Syncs for different instances use different default locks. System Hook deletions without a configured `base_url` use a fixed local key instead.
|
|
618
680
|
|
|
619
681
|
When another live sync process owns the lock, the new command stops before doing any work, prints the owning PID and lock path, and exits nonzero. It does not wait or queue. Schedulers can try again on their next run. If a process was forcibly terminated and left its PID file behind, the next invocation detects that the PID is dead, removes the stale file, atomically acquires a replacement, and continues.
|
|
620
682
|
|
|
@@ -670,7 +732,7 @@ PATH=/usr/local/bin:/usr/bin:/bin
|
|
|
670
732
|
|
|
671
733
|
Choose only the item types your tree manages. This example explicitly disables downsync and deletion. Add `--error_event EVENT_ID` if you prefer an error-handling Event, or configure notifications in the account's CLI config file.
|
|
672
734
|
|
|
673
|
-
`--quiet` keeps routine progress and diffs out of cron's output, avoiding an email for every ordinary run.
|
|
735
|
+
`--quiet` keeps routine progress and diffs out of cron's output, avoiding an email for every ordinary run. Warnings and errors still print to standard error. Configure notifications if you also want xyOps to send a message or launch an Event when sync encounters a problem.
|
|
674
736
|
|
|
675
737
|
If the previous run is still active, the built-in PID lock rejects the overlapping invocation with a nonzero exit status. The next minute tries again. A single sync can therefore take longer than a minute without starting another copy.
|
|
676
738
|
|
|
@@ -925,7 +987,7 @@ Repository concurrency controls these workflow runs. Coordinate all writers to t
|
|
|
925
987
|
| `down_cmd` does not run | Normal sync requires a successful download. Setup requires at least one written definition and an explicit `--down_cmd`. No-op and dry runs do not trigger it. |
|
|
926
988
|
| A download commit launches another sync | Disable deployment Git hooks for commits made by `down_cmd`. The nested sync will otherwise be rejected by the built-in PID lock. |
|
|
927
989
|
| Sync reports another PID is running | Another local sync owns the lock. Let it finish. If the PID is no longer alive, the next run recovers the stale file automatically. If the PID belongs to an unrelated recycled process, verify that no sync is active before removing the reported lock file. |
|
|
928
|
-
| A scheduled run prints nothing | `--quiet` suppresses routine output and
|
|
990
|
+
| A scheduled run prints nothing | `--quiet` suppresses routine output. Warnings and errors still print to standard error. Check that the scheduler captures it. |
|
|
929
991
|
| Sync exits nonzero after a warning report | Scan warnings prevent changes from being applied, so warnings and errors both exit with status `1`. Resolve the reported problem before retrying. |
|
|
930
992
|
| Notifications never arrive | Check API access, email setup and privileges, the error Event's manual trigger, and failures occurring before reporting. |
|
|
931
993
|
| API Key updates behave differently after import | Portable definitions do not transfer usable secret key material. Treat key credentials separately from their definitions. |
|
|
@@ -108,7 +108,7 @@ This considers both directions for every local Event and workflow source:
|
|
|
108
108
|
- A newer xyOps definition is downloaded to its existing local file.
|
|
109
109
|
- Identical definitions are left alone.
|
|
110
110
|
|
|
111
|
-
The `--delete false` switch is intentional. Two-way sync does not propagate deletions
|
|
111
|
+
The `--delete false` switch is intentional. Two-way sync does not propagate deletions. Keep deletion disabled in the scheduled command, and follow the [coordinated deletion steps](#delete-a-definition-from-both-sides) when retiring an item.
|
|
112
112
|
|
|
113
113
|
Review the displayed diffs carefully. If the wrong side would win, check the clocks and file modification times before continuing.
|
|
114
114
|
|
|
@@ -151,6 +151,7 @@ GIT_DOWN_CMD="git add --all && git commit -m 'Sync from xyOps' && git push"
|
|
|
151
151
|
--up events \
|
|
152
152
|
--down events \
|
|
153
153
|
--delete false \
|
|
154
|
+
--ignore_missing \
|
|
154
155
|
--down_cmd "$GIT_DOWN_CMD" \
|
|
155
156
|
--cmd_timeout 120
|
|
156
157
|
|
|
@@ -159,6 +160,7 @@ GIT_DOWN_CMD="git add --all && git commit -m 'Sync from xyOps' && git push"
|
|
|
159
160
|
# The setup completion command runs only when new files were actually written.
|
|
160
161
|
"$XY_BIN" sync setup events \
|
|
161
162
|
--new \
|
|
163
|
+
--ignore_missing \
|
|
162
164
|
--file_props script,params.script \
|
|
163
165
|
--default_ext ps1 \
|
|
164
166
|
--down_cmd "$GIT_DOWN_CMD" \
|
|
@@ -185,6 +187,8 @@ The two uses of `--down_cmd` are deliberately identical:
|
|
|
185
187
|
- `sync setup --new` runs it only after at least one new local definition is written.
|
|
186
188
|
- A no-op or dry run does not create a commit or push.
|
|
187
189
|
|
|
190
|
+
The two `--ignore_missing` switches let the script continue while a definition deleted in xyOps still has a local XYPDF source. Other definitions can sync and new xyOps definitions can be exported during that interval. The stale source and its neighboring files remain in Git until you remove and push them. Other scan problems still stop the run.
|
|
191
|
+
|
|
188
192
|
The command stages the entire dedicated repository with `git add --all`. Do not use this script in a checkout containing unrelated work. If your repository contains other material, narrow the Git paths in `GIT_DOWN_CMD`.
|
|
189
193
|
|
|
190
194
|
## 5. Schedule the script with cron
|
|
@@ -207,7 +211,7 @@ Run the script every minute, with `flock` outside the script:
|
|
|
207
211
|
* * * * * /usr/bin/flock --nonblock --conflict-exit-code 0 /tmp/xyops-git-sync.lock /usr/local/sbin/xyops-git-sync >/dev/null
|
|
208
212
|
```
|
|
209
213
|
|
|
210
|
-
Cron normally emails job output when local mail delivery is configured. This entry discards routine standard output so successful runs remain silent, but leaves standard error alone so failures can still trigger mail. The xyCLI commands
|
|
214
|
+
Cron normally emails job output when local mail delivery is configured. This entry discards routine standard output so successful runs remain silent, but leaves standard error alone so failures can still trigger mail. The xyCLI commands show progress when run manually; you can add `--quiet` to suppress that progress while keeping warnings and errors on standard error.
|
|
211
215
|
|
|
212
216
|
To keep successful output in a log instead, replace `>/dev/null` with an append redirect to a file that the sync account can write. Do not place that log inside the Git repository because the completion command stages the whole tree.
|
|
213
217
|
|
|
@@ -245,6 +249,18 @@ When someone creates a new Event in xyOps, the ordinary sync pass does not know
|
|
|
245
249
|
|
|
246
250
|
`--new` is additive. It does not overwrite existing sources, and it matches existing definitions by type and exact ID rather than filename. This preserves renamed files and custom folder layouts.
|
|
247
251
|
|
|
252
|
+
### Delete a definition from both sides
|
|
253
|
+
|
|
254
|
+
You can automate deletions made in xyOps with [System Hook deletion](sync.md#system-hook-deletion). For manual coordination, the `--ignore_missing` switches let the scheduled script keep running while you complete these steps:
|
|
255
|
+
|
|
256
|
+
1. Delete the definition in the xyOps web interface first.
|
|
257
|
+
2. Remove its XYPDF JSON file and all neighboring property files from Git, then commit and push the deletion.
|
|
258
|
+
3. Check that the dedicated sync checkout receives the commit and completes a later sync run.
|
|
259
|
+
|
|
260
|
+
If the script runs after the xyOps deletion but before the Git deletion reaches its checkout, both sync commands skip that local source without treating it as a warning. They leave its files in place and continue processing other definitions. Remove and push the stale files to complete the deletion. Without `--ignore_missing` on both commands, the missing source stops the script.
|
|
261
|
+
|
|
262
|
+
Do not remove the Git files first. While the definition still exists in xyOps, `sync setup --new` can export it again and commit the files back to Git. This is a manual deletion procedure, not automatic two-way deletion. See the [Sync Guide](sync.md#remove-a-definition-during-two-way-sync) for the general behavior.
|
|
263
|
+
|
|
248
264
|
## Troubleshooting
|
|
249
265
|
|
|
250
266
|
| Symptom | What to check |
|
|
@@ -255,7 +271,7 @@ When someone creates a new Event in xyOps, the ordinary sync pass does not know
|
|
|
255
271
|
| The wrong side wins a two-way conflict | Check NTP, the xyOps conductor clock, and the modification times of the JSON and neighboring files. A checkout, restore, or file copy can refresh local timestamps. |
|
|
256
272
|
| A new Event is not exported | Confirm `sync setup events --new` runs from the repository root and that the API Key can see the Event. Add the correct property path if its script is stored somewhere other than `params.script`. |
|
|
257
273
|
| A new Git file is not created in xyOps | Ordinary sync updates definitions that already exist on both sides. Create or import the definition into xyOps first, then let sync manage subsequent changes. |
|
|
258
|
-
| A deleted item reappears or causes an error |
|
|
274
|
+
| A deleted item reappears or causes an error | Follow the [coordinated deletion steps](#delete-a-definition-from-both-sides): delete in xyOps first, then remove and commit its local files. Use `--ignore_missing` on both sync commands to keep the script running during the gap. |
|
|
259
275
|
| A cron run is skipped | Another cron invocation may hold the `flock` lock. The lock file itself may remain when no lock is held, so check for a running sync process rather than treating the file as stale. |
|
|
260
276
|
| A completion command times out | Increase `--cmd_timeout`, investigate Git network latency, and confirm that Git cannot wait for interactive input. |
|
|
261
277
|
|
package/index.js
CHANGED
|
@@ -18,6 +18,15 @@ if (!process.env.XYOPS_USER_AGENT) {
|
|
|
18
18
|
process.env.XYOPS_USER_AGENT = `xyOps CLI v${pkg.version} (Node.js); ${os.hostname()} (${os.platform()}/${os.arch()})`;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// Warnings and errors must reach stderr even in quiet mode. Install this
|
|
22
|
+
// before cli.global() so its global warn() helper is bound to cli as usual.
|
|
23
|
+
cli.warn = function(msg) {
|
|
24
|
+
if (this.progress.running) this.progress.erase();
|
|
25
|
+
process.stderr.write(msg);
|
|
26
|
+
if (this.progress.running) this.progress.draw();
|
|
27
|
+
this.log(msg);
|
|
28
|
+
};
|
|
29
|
+
|
|
21
30
|
cli.global();
|
|
22
31
|
|
|
23
32
|
cli.mapArgs({
|
|
@@ -26,7 +35,8 @@ cli.mapArgs({
|
|
|
26
35
|
'd': 'debug',
|
|
27
36
|
'f': 'format',
|
|
28
37
|
'r': 'raw',
|
|
29
|
-
'h': 'help'
|
|
38
|
+
'h': 'help',
|
|
39
|
+
'ignore-missing': 'ignore_missing'
|
|
30
40
|
});
|
|
31
41
|
|
|
32
42
|
process.on('SIGINT', function() { cli.progress.end(); process.exit(130); } );
|
|
@@ -39,6 +49,14 @@ Uncatch.on('uncaughtException', function(err) {
|
|
|
39
49
|
cli.progress.end();
|
|
40
50
|
});
|
|
41
51
|
|
|
52
|
+
cli.emoji = function(emoji) {
|
|
53
|
+
// override cli.emoji with our own TTY-aware version (FUTURE: Remove this patch after pixl-cli v1.1.7+)
|
|
54
|
+
// Draw the emoji, restore its starting cursor position, then explicitly move
|
|
55
|
+
// forward two cells. This overrides each terminal's automatic cursor advance.
|
|
56
|
+
if (!cli.tty()) return emoji;
|
|
57
|
+
return '\u001b7' + emoji + '\u001b8\u001b[2C';
|
|
58
|
+
};
|
|
59
|
+
|
|
42
60
|
const { api } = require('@pixlcore/xyops-sdk');
|
|
43
61
|
var highlight = require('cli-highlight').highlight;
|
|
44
62
|
const Tools = cli.Tools;
|
|
@@ -232,6 +250,14 @@ const app = {
|
|
|
232
250
|
delete args.quiet;
|
|
233
251
|
delete args.verbose;
|
|
234
252
|
|
|
253
|
+
// A System Hook deletion uses only its STDIN payload and local XYPDF
|
|
254
|
+
// files. Run it before the normal xyOps API credential and config fetch.
|
|
255
|
+
if ((cmd == 'sync') && ('syshook' in args)) {
|
|
256
|
+
await this.cmd_sync();
|
|
257
|
+
print("\n");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
235
261
|
// create temp dir if needed
|
|
236
262
|
this.tempDir = this.config.temp_dir || Path.join( os.tmpdir(), 'xyops', 'cli' );
|
|
237
263
|
if (!fs.existsSync(this.tempDir)) Tools.mkdirpSync( this.tempDir );
|
|
@@ -357,10 +383,7 @@ const app = {
|
|
|
357
383
|
print( "\n " + cyan.bold("Request:") );
|
|
358
384
|
this.jsonOutput(this.args);
|
|
359
385
|
|
|
360
|
-
if (this.dry)
|
|
361
|
-
this.toast('⚠️', 'orange', bold("DRY RUN: ") + "Exiting without sending request.");
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
386
|
+
if (this.dry) return;
|
|
364
387
|
|
|
365
388
|
cli.progress.start({ amount: 1, pct: false, text: gray('→ ' + name) });
|
|
366
389
|
var { err, data } = await this.api[name](this.args);
|
|
@@ -408,6 +431,8 @@ const app = {
|
|
|
408
431
|
|
|
409
432
|
dieUsage(heading) {
|
|
410
433
|
// print help and exit
|
|
434
|
+
if (cli.args.quiet) this.die("Invalid usage. Run 'xy help " + heading + "' for details.");
|
|
435
|
+
|
|
411
436
|
this.printHelp(heading);
|
|
412
437
|
println("");
|
|
413
438
|
process.exit(1);
|
|
@@ -453,6 +478,8 @@ Tools.mergeHashInto( app, require('./lib/webhook.js') );
|
|
|
453
478
|
Tools.mergeHashInto( app, require('./lib/transfer.js') );
|
|
454
479
|
Tools.mergeHashInto( app, require('./lib/marketplace.js') );
|
|
455
480
|
Tools.mergeHashInto( app, require('./lib/sync.js') );
|
|
481
|
+
Tools.mergeHashInto( app, require('./lib/sync-buckets.js') );
|
|
482
|
+
Tools.mergeHashInto( app, require('./lib/sync-syshook.js') );
|
|
456
483
|
|
|
457
484
|
global.app = app;
|
|
458
485
|
|
package/lib/apikey.js
CHANGED
|
@@ -241,7 +241,7 @@ module.exports = {
|
|
|
241
241
|
println( "\n " + cyan.bold("API Key Secret: ") + bold(data.plain_key) );
|
|
242
242
|
println( " " + yellow.bold("Save this secret now. It will never be displayed again.") );
|
|
243
243
|
if (state_error) {
|
|
244
|
-
|
|
244
|
+
warnln( "\n " + red.bold("Disable Error: ") + (state_error.message || state_error) );
|
|
245
245
|
process.exitCode = 1;
|
|
246
246
|
return;
|
|
247
247
|
}
|
package/lib/buckets.js
CHANGED
|
@@ -218,7 +218,6 @@ module.exports = {
|
|
|
218
218
|
if (this.dry) {
|
|
219
219
|
if (files.length) {
|
|
220
220
|
this.jsonOutput({ files: files });
|
|
221
|
-
this.toast('⚠️', 'orange', bold("DRY RUN: ") + "Exiting without sending request.");
|
|
222
221
|
}
|
|
223
222
|
return;
|
|
224
223
|
}
|
|
@@ -475,10 +474,7 @@ module.exports = {
|
|
|
475
474
|
output: dest_file
|
|
476
475
|
});
|
|
477
476
|
}
|
|
478
|
-
if (this.dry)
|
|
479
|
-
this.toast('⚠️', 'orange', bold("DRY RUN: ") + "Exiting without sending request.");
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
477
|
+
if (this.dry) return;
|
|
482
478
|
|
|
483
479
|
println( "\n " + this.color('theme').bold('BUCKET FILE: ' + file.filename) );
|
|
484
480
|
println( " " + gray("Downloading to file: ") + dest_file );
|
package/lib/marketplace.js
CHANGED
|
@@ -235,7 +235,7 @@ module.exports = {
|
|
|
235
235
|
]
|
|
236
236
|
});
|
|
237
237
|
|
|
238
|
-
if (warning)
|
|
238
|
+
if (warning) warnln("\n " + yellow(warning));
|
|
239
239
|
println("\n This is the Plugin definition that will be " + (operation == 'create' ? 'created' : 'used to update the installed Plugin') + " when you confirm:");
|
|
240
240
|
this.jsonOutput(display.plugin);
|
|
241
241
|
if (display.script) this.printPluginScript(plugin);
|