@pixlcore/xycli 1.0.6 → 1.0.7
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/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/docs/help.md +2 -0
- package/docs/sync.md +5 -1
- package/docs/two-way-git-tutorial.md +1 -1
- package/lib/jobs.js +2 -2
- package/lib/sync.js +55 -7
- package/package.json +1 -1
- package/test/sync-unit.test.js +161 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# xyCLI Changelog
|
|
2
2
|
|
|
3
|
+
## Version v1.0.6
|
|
4
|
+
|
|
5
|
+
> September 22, 2026
|
|
6
|
+
|
|
7
|
+
- [`985c132`](https://github.com/pixlcore/xycli/commit/985c132607e1dcdabbadc829d6f21e599a2eb52b): Version 1.0.6
|
|
8
|
+
- [`f0aac32`](https://github.com/pixlcore/xycli/commit/f0aac320cbd301a5dc45862edaec9f842c2c6c79): Feature: Add "xy upgrade" command.
|
|
9
|
+
- [`a9b653b`](https://github.com/pixlcore/xycli/commit/a9b653b9649afd15d93cb3d29ee4f23da47bde0a): Bug Fix: Sync files with similar stems and neighbors can get mixed up. Fixes #4.
|
|
10
|
+
|
|
3
11
|
## Version v1.0.5
|
|
4
12
|
|
|
5
13
|
> September 21, 2026
|
package/README.md
CHANGED
|
@@ -499,7 +499,7 @@ cd xyops-automation
|
|
|
499
499
|
xy sync setup events plugins categories --file_props script,params.script
|
|
500
500
|
```
|
|
501
501
|
|
|
502
|
-
Setup creates one file per definition and can extract scripts into adjacent source files. Events and workflows are
|
|
502
|
+
Setup creates one file per definition and can extract scripts into adjacent source files. The same `--file_props script,params.script` selection also extracts matching scripts from workflow nodes automatically. Events and workflows are grouped into subfolders named after their Categories. You can reorganize the tree afterward; sync scans subdirectories and matches definitions by resource type and exact ID.
|
|
503
503
|
|
|
504
504
|
Preview a push or pull, then apply the direction you want:
|
|
505
505
|
|
package/docs/help.md
CHANGED
|
@@ -308,6 +308,8 @@ Setup creates one folder per resource type and one XYPDF JSON file per object, u
|
|
|
308
308
|
|
|
309
309
|
For `--file_props`, use comma-separated property names or dot paths, such as `script,params.script`. Only nonempty string values are extracted. The JSON value becomes `(External)`, and sync loads the external file automatically. Setup chooses extensions from executable commands, shebangs, and JSON content when possible. Use `--default_ext ps1` (or `--default_ext .ps1`) to replace the `.txt` fallback for properties whose type cannot be detected. The override does not replace an extension that setup successfully detects.
|
|
310
310
|
|
|
311
|
+
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
|
+
|
|
311
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.
|
|
312
314
|
|
|
313
315
|
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.
|
package/docs/sync.md
CHANGED
|
@@ -96,7 +96,7 @@ Setup always writes below the current working directory. Type names after `setup
|
|
|
96
96
|
xy sync setup events plugins categories --file_props script,params.script --dry
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
The `--file_props` switch will externalize specific JSON property paths into individually named files, for easy editing. This is great for storing Plugin scripts
|
|
99
|
+
The `--file_props` switch will externalize specific JSON property paths into individually named files, for easy editing. This is great for storing Plugin scripts, Event shell scripts, and workflow node scripts in separate files alongside the main JSON definition files.
|
|
100
100
|
|
|
101
101
|
The preview lists the folders and files that would be written. `--dry` creates no directories or files.
|
|
102
102
|
|
|
@@ -122,6 +122,7 @@ xyops-automation/
|
|
|
122
122
|
workflows/
|
|
123
123
|
Deployments/
|
|
124
124
|
Release-Pipeline.json
|
|
125
|
+
Release-Pipeline-workflow.nt3sr3y4.params.script.sh
|
|
125
126
|
```
|
|
126
127
|
|
|
127
128
|
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.
|
|
@@ -242,9 +243,12 @@ This extracts the selected content, if found, and moves it to a neighboring file
|
|
|
242
243
|
| --- | --- | --- |
|
|
243
244
|
| `My-Plugin.json` | `script` | `My-Plugin-script.js` |
|
|
244
245
|
| `My-Event.json` | `params.script` | `My-Event-params.script.sh` |
|
|
246
|
+
| `Release-Pipeline.json` | `workflow.nt3sr3y4.params.script` | `Release-Pipeline-workflow.nt3sr3y4.params.script.sh` |
|
|
245
247
|
|
|
246
248
|
The general naming pattern is **`BASENAME-PROPERTY.EXT`**, in the same directory as `BASENAME.json`. Dot paths such as `params.script` identify nested properties. Setup chooses an extension from the executable command or a script's shebang when possible, falling back to JSON-looking content or `.txt`. Normal sync accepts your chosen extension.
|
|
247
249
|
|
|
250
|
+
The same `--file_props script,params.script` selection checks every node in a workflow automatically. If a node has a nonempty string at `data.params.script`, setup writes a neighbor for that node and leaves `(External)` in the workflow JSON. In its filename, `workflow.NODE_ID.params.script` identifies the node by its ID and then reads `data.params.script`. No additional option is needed for workflow nodes.
|
|
251
|
+
|
|
248
252
|
Use `--default_ext EXT` to replace the `.txt` fallback when setup cannot detect a type. For example, `--default_ext ps1` and `--default_ext .ps1` both produce `.ps1` neighbors. Detected interpreter and JSON extensions still take precedence. The fallback applies to every undetected property selected by that setup command, so group properties with the same expected file type or run setup separately when needed.
|
|
249
253
|
|
|
250
254
|
External properties are detected automatically when sync runs; you do not need to repeat `--file_props`. The neighbor's text replaces that property in the local definition before comparison and before an upload. A pull writes the latest value into the existing neighbor file.
|
|
@@ -67,7 +67,7 @@ xy sync setup events --file_props script,params.script --default_ext ps1 --dry
|
|
|
67
67
|
|
|
68
68
|
The `events` selection includes both Events and workflows. Setup places workflows in their own `workflows/` directory, then groups Events and workflows into subfolders named after their Categories.
|
|
69
69
|
|
|
70
|
-
`--file_props script,params.script` extracts common
|
|
70
|
+
`--file_props script,params.script` extracts common script properties into neighboring files. It also checks each workflow node automatically, so a node's `data.params.script` gets its own file named with that node's ID. If a property uses another path, add its dot path to the list. For example, use `script,params.script,params.command` when an Event stores its PowerShell source in `params.command`.
|
|
71
71
|
|
|
72
72
|
The optional `--default_ext ps1` uses `.ps1` when setup cannot detect a neighbor file's language from an executable command, shebang, or JSON content. Remove it or choose another extension if PowerShell is not the appropriate fallback for your repository.
|
|
73
73
|
|
package/lib/jobs.js
CHANGED
package/lib/sync.js
CHANGED
|
@@ -253,13 +253,13 @@ module.exports = {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
// External files may only replace string properties that already exist in
|
|
256
|
-
// the definition.
|
|
257
|
-
if (typeof(
|
|
256
|
+
// the definition. setSyncPropByPath() creates missing keys, so validate first.
|
|
257
|
+
if (typeof(this.getSyncPropByPath(item.data, prop_path)) != 'string') {
|
|
258
258
|
this.logSyncWarning("Neighbor file does not match an existing string property: " + neighbor.path + " (" + prop_path + ")");
|
|
259
259
|
neighbor_error = true;
|
|
260
260
|
return;
|
|
261
261
|
}
|
|
262
|
-
if (!
|
|
262
|
+
if (!this.setSyncPropByPath(item.data, prop_path, content)) {
|
|
263
263
|
this.logSyncWarning("Invalid neighbor property path: " + neighbor.path + " (" + prop_path + ")");
|
|
264
264
|
neighbor_error = true;
|
|
265
265
|
return;
|
|
@@ -703,9 +703,9 @@ module.exports = {
|
|
|
703
703
|
if (item.neighbors) {
|
|
704
704
|
for (var path in item.neighbors) {
|
|
705
705
|
var file = item.neighbors[path];
|
|
706
|
-
var value =
|
|
706
|
+
var value = this.getSyncPropByPath( data, path ) ?? "";
|
|
707
707
|
|
|
708
|
-
if (!
|
|
708
|
+
if (!this.setSyncPropByPath( data, path, "(External)" )) {
|
|
709
709
|
throw "External neighbor file path not found: " + file + ": " + path;
|
|
710
710
|
}
|
|
711
711
|
|
|
@@ -1006,10 +1006,22 @@ module.exports = {
|
|
|
1006
1006
|
var item = Tools.copyHash(plan.source_item, true);
|
|
1007
1007
|
var extras = [];
|
|
1008
1008
|
(opts.file_props || []).forEach( path => {
|
|
1009
|
-
var extra =
|
|
1009
|
+
var extra = this.getSyncPropByPath(item, path);
|
|
1010
1010
|
if ((typeof(extra) == 'string') && extra.match(/\S/)) {
|
|
1011
1011
|
extras.push({ path, content: extra });
|
|
1012
|
-
|
|
1012
|
+
this.setSyncPropByPath( item, path, "(External)" );
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// handle workflow nodes separately
|
|
1016
|
+
if (item.workflow && item.workflow.nodes && Array.isArray(item.workflow.nodes)) {
|
|
1017
|
+
item.workflow.nodes.forEach( node => {
|
|
1018
|
+
if (!node.data || !Tools.isaHash(node.data)) return; // sanity
|
|
1019
|
+
var extra = this.getSyncPropByPath(node.data, path);
|
|
1020
|
+
if ((typeof(extra) == 'string') && extra.match(/\S/)) {
|
|
1021
|
+
extras.push({ path: `workflow.${node.id}.${path}`, content: extra });
|
|
1022
|
+
this.setSyncPropByPath( node.data, path, "(External)" );
|
|
1023
|
+
}
|
|
1024
|
+
} );
|
|
1013
1025
|
}
|
|
1014
1026
|
});
|
|
1015
1027
|
|
|
@@ -1188,6 +1200,42 @@ module.exports = {
|
|
|
1188
1200
|
}
|
|
1189
1201
|
|
|
1190
1202
|
return ext;
|
|
1203
|
+
},
|
|
1204
|
+
|
|
1205
|
+
getSyncPropByPath(data, path) {
|
|
1206
|
+
// special-case Tools.getPath for handling shorthand for named workflow nodes
|
|
1207
|
+
// e.g. `workflow.nabcd1234.params.foo` == `workflow.nodes[id='nabcd1234'].data.params.foo`
|
|
1208
|
+
if (path.match(/^workflow\.(\w+)\.(.+)$/) && data.workflow && data.workflow.nodes && Array.isArray(data.workflow.nodes)) {
|
|
1209
|
+
var node_id = RegExp.$1;
|
|
1210
|
+
var sub_path = RegExp.$2;
|
|
1211
|
+
if (node_id === 'nodes') return Tools.getPath(data, path);
|
|
1212
|
+
|
|
1213
|
+
var node = Tools.findObject( data.workflow.nodes, { id: node_id } );
|
|
1214
|
+
if (!node || !node.data) return undefined;
|
|
1215
|
+
|
|
1216
|
+
data = node.data;
|
|
1217
|
+
path = sub_path;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return Tools.getPath(data, path);
|
|
1221
|
+
},
|
|
1222
|
+
|
|
1223
|
+
setSyncPropByPath(data, path, value) {
|
|
1224
|
+
// special-case Tools.setPath for handling shorthand for named workflow nodes
|
|
1225
|
+
// e.g. `workflow.nabcd1234.params.foo` == `workflow.nodes[id='nabcd1234'].data.params.foo`
|
|
1226
|
+
if (path.match(/^workflow\.(\w+)\.(.+)$/) && data.workflow && data.workflow.nodes && Array.isArray(data.workflow.nodes)) {
|
|
1227
|
+
var node_id = RegExp.$1;
|
|
1228
|
+
var sub_path = RegExp.$2;
|
|
1229
|
+
if (node_id === 'nodes') return Tools.setPath(data, path, value);
|
|
1230
|
+
|
|
1231
|
+
var node = Tools.findObject( data.workflow.nodes, { id: node_id } );
|
|
1232
|
+
if (!node || !node.data) return undefined;
|
|
1233
|
+
|
|
1234
|
+
data = node.data;
|
|
1235
|
+
path = sub_path;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
return Tools.setPath(data, path, value);
|
|
1191
1239
|
}
|
|
1192
1240
|
|
|
1193
1241
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixlcore/xycli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "A complete command-line interface for the xyOps workflow automation system.",
|
|
5
5
|
"author": "Joseph Huckaby <jhuckaby@pixlcore.com>",
|
|
6
6
|
"homepage": "https://github.com/pixlcore/xycli",
|
package/test/sync-unit.test.js
CHANGED
|
@@ -432,6 +432,61 @@ test('sync setup groups Events and workflows into Category folders', t => {
|
|
|
432
432
|
assert.equal(result.stdout.indexOf('events/Operations-Team/', operationsHeader + 1), -1);
|
|
433
433
|
});
|
|
434
434
|
|
|
435
|
+
test('sync setup externalizes each matching workflow node parameter', t => {
|
|
436
|
+
const shellScript = '#!/bin/sh\necho first\n';
|
|
437
|
+
const nodeScript = '#!/usr/bin/env node\nconsole.log("second");\n';
|
|
438
|
+
const workflow = {
|
|
439
|
+
id: 'flow', title: 'Flow', type: 'workflow', category: 'operations',
|
|
440
|
+
workflow: { nodes: [
|
|
441
|
+
{ id: 'ntrigger1', type: 'trigger' },
|
|
442
|
+
{ id: 'nfirst01', type: 'event', data: { params: { script: shellScript } } },
|
|
443
|
+
{ id: 'nsecond1', type: 'job', data: { params: { script: nodeScript } } },
|
|
444
|
+
{ id: 'nempty001', type: 'job', data: { params: { script: '' } } }
|
|
445
|
+
] }
|
|
446
|
+
};
|
|
447
|
+
const result = runSync(t, {
|
|
448
|
+
setup: ['events'],
|
|
449
|
+
categories: [{ id: 'operations', title: 'Operations' }],
|
|
450
|
+
events: [workflow],
|
|
451
|
+
args: { file_props: 'params.script' }
|
|
452
|
+
});
|
|
453
|
+
const dir = Path.join(result.dir, 'workflows', 'Operations');
|
|
454
|
+
const source = JSON.parse(fs.readFileSync(Path.join(dir, 'Flow.json'), 'utf8')).items[0].data;
|
|
455
|
+
|
|
456
|
+
assert.equal(result.status, 0);
|
|
457
|
+
assert.deepEqual(result.report.errors, []);
|
|
458
|
+
assert.equal(fs.readFileSync(Path.join(dir, 'Flow-workflow.nfirst01.params.script.sh'), 'utf8'), shellScript);
|
|
459
|
+
assert.equal(fs.readFileSync(Path.join(dir, 'Flow-workflow.nsecond1.params.script.js'), 'utf8'), nodeScript);
|
|
460
|
+
assert.equal(source.workflow.nodes[1].data.params.script, '(External)');
|
|
461
|
+
assert.equal(source.workflow.nodes[2].data.params.script, '(External)');
|
|
462
|
+
assert.equal(source.workflow.nodes[3].data.params.script, '');
|
|
463
|
+
assert.equal(fs.readdirSync(dir).length, 3, 'Empty and trigger nodes have no neighbor files');
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test('sync setup accepts a shorthand path for one workflow node', t => {
|
|
467
|
+
const workflow = {
|
|
468
|
+
id: 'flow', title: 'Flow', type: 'workflow', category: 'operations',
|
|
469
|
+
workflow: { nodes: [
|
|
470
|
+
{ id: 'nfirst01', type: 'job', data: { params: { script: 'First\n' } } },
|
|
471
|
+
{ id: 'nsecond1', type: 'job', data: { params: { script: 'Second\n' } } }
|
|
472
|
+
] }
|
|
473
|
+
};
|
|
474
|
+
const result = runSync(t, {
|
|
475
|
+
setup: ['events'],
|
|
476
|
+
categories: [{ id: 'operations', title: 'Operations' }],
|
|
477
|
+
events: [workflow],
|
|
478
|
+
args: { file_props: 'workflow.nfirst01.params.script' }
|
|
479
|
+
});
|
|
480
|
+
const dir = Path.join(result.dir, 'workflows', 'Operations');
|
|
481
|
+
const source = JSON.parse(fs.readFileSync(Path.join(dir, 'Flow.json'), 'utf8')).items[0].data;
|
|
482
|
+
|
|
483
|
+
assert.equal(result.status, 0);
|
|
484
|
+
assert.equal(fs.readFileSync(Path.join(dir, 'Flow-workflow.nfirst01.params.script.txt'), 'utf8'), 'First\n');
|
|
485
|
+
assert.equal(source.workflow.nodes[0].data.params.script, '(External)');
|
|
486
|
+
assert.equal(source.workflow.nodes[1].data.params.script, 'Second\n');
|
|
487
|
+
assert.equal(fs.readdirSync(dir).length, 2);
|
|
488
|
+
});
|
|
489
|
+
|
|
435
490
|
test('sync setup validates all Event Categories before writing any files', t => {
|
|
436
491
|
const result = runSync(t, {
|
|
437
492
|
setup: ['plugins', 'events'],
|
|
@@ -655,6 +710,112 @@ test('property neighbor matching requires an exact source basename boundary', t
|
|
|
655
710
|
assert.equal(result.report.requests.some(request => request.method === 'update_event'), false);
|
|
656
711
|
});
|
|
657
712
|
|
|
713
|
+
test('workflow node neighbors replace placeholders before upload', t => {
|
|
714
|
+
const shellScript = '#!/bin/sh\necho local\n';
|
|
715
|
+
const nodeScript = '#!/usr/bin/env node\nconsole.log("local");\n';
|
|
716
|
+
const remote = {
|
|
717
|
+
id: 'flow', title: 'Flow', type: 'workflow', category: 'operations',
|
|
718
|
+
workflow: { nodes: [
|
|
719
|
+
{ id: 'nfirst01', type: 'event', data: { params: { script: 'Remote shell\n' } } },
|
|
720
|
+
{ id: 'nsecond1', type: 'job', data: { params: { script: 'Remote node\n' } } }
|
|
721
|
+
] }
|
|
722
|
+
};
|
|
723
|
+
const local = JSON.parse(JSON.stringify(remote));
|
|
724
|
+
local.workflow.nodes.forEach(node => { node.data.params.script = '(External)'; });
|
|
725
|
+
const result = runSync(t, {
|
|
726
|
+
events: [remote],
|
|
727
|
+
existingSources: [
|
|
728
|
+
{ path: 'Flow.json', type: 'event', data: local },
|
|
729
|
+
{ path: 'Flow-workflow.nfirst01.params.script.sh', contents: shellScript },
|
|
730
|
+
{ path: 'Flow-workflow.nsecond1.params.script.js', contents: nodeScript }
|
|
731
|
+
],
|
|
732
|
+
args: { up: 'events' }
|
|
733
|
+
});
|
|
734
|
+
const updates = result.report.requests.filter(request => request.method === 'update_event');
|
|
735
|
+
|
|
736
|
+
assert.equal(result.status, 0);
|
|
737
|
+
assert.deepEqual(result.report.warnings, []);
|
|
738
|
+
assert.deepEqual(result.report.errors, []);
|
|
739
|
+
assert.equal(updates.length, 1);
|
|
740
|
+
assert.equal(updates[0].data.workflow.nodes[0].data.params.script, shellScript);
|
|
741
|
+
assert.equal(updates[0].data.workflow.nodes[1].data.params.script, nodeScript);
|
|
742
|
+
const source = JSON.parse(fs.readFileSync(Path.join(result.dir, 'Flow.json'), 'utf8')).items[0].data;
|
|
743
|
+
assert.equal(source.workflow.nodes[0].data.params.script, '(External)');
|
|
744
|
+
assert.equal(source.workflow.nodes[1].data.params.script, '(External)');
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
test('workflow node neighbors receive pulled values while JSON keeps placeholders', t => {
|
|
748
|
+
const shellScript = '#!/bin/sh\necho remote\n';
|
|
749
|
+
const nodeScript = '#!/usr/bin/env node\nconsole.log("remote");\n';
|
|
750
|
+
const remote = {
|
|
751
|
+
id: 'flow', title: 'Flow', type: 'workflow', category: 'operations',
|
|
752
|
+
workflow: { nodes: [
|
|
753
|
+
{ id: 'nfirst01', type: 'event', data: { params: { script: shellScript } } },
|
|
754
|
+
{ id: 'nsecond1', type: 'job', data: { params: { script: nodeScript } } }
|
|
755
|
+
] }
|
|
756
|
+
};
|
|
757
|
+
const local = JSON.parse(JSON.stringify(remote));
|
|
758
|
+
local.workflow.nodes.forEach(node => { node.data.params.script = '(External)'; });
|
|
759
|
+
const result = runSync(t, {
|
|
760
|
+
events: [remote],
|
|
761
|
+
existingSources: [
|
|
762
|
+
{ path: 'Flow.json', type: 'event', data: local },
|
|
763
|
+
{ path: 'Flow-workflow.nfirst01.params.script.sh', contents: 'Old shell\n' },
|
|
764
|
+
{ path: 'Flow-workflow.nsecond1.params.script.js', contents: 'Old node\n' }
|
|
765
|
+
],
|
|
766
|
+
args: { down: 'events' }
|
|
767
|
+
});
|
|
768
|
+
const source = JSON.parse(fs.readFileSync(Path.join(result.dir, 'Flow.json'), 'utf8')).items[0].data;
|
|
769
|
+
|
|
770
|
+
assert.equal(result.status, 0);
|
|
771
|
+
assert.deepEqual(result.report.warnings, []);
|
|
772
|
+
assert.deepEqual(result.report.errors, []);
|
|
773
|
+
assert.equal(fs.readFileSync(Path.join(result.dir, 'Flow-workflow.nfirst01.params.script.sh'), 'utf8'), shellScript);
|
|
774
|
+
assert.equal(fs.readFileSync(Path.join(result.dir, 'Flow-workflow.nsecond1.params.script.js'), 'utf8'), nodeScript);
|
|
775
|
+
assert.equal(source.workflow.nodes[0].data.params.script, '(External)');
|
|
776
|
+
assert.equal(source.workflow.nodes[1].data.params.script, '(External)');
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
test('workflow node shorthand preserves ordinary indexed dot paths', () => {
|
|
780
|
+
const sync = require('../lib/sync.js');
|
|
781
|
+
const data = { workflow: { nodes: [
|
|
782
|
+
{ id: 'nfirst01', data: { params: { script: 'Original\n' } } }
|
|
783
|
+
] } };
|
|
784
|
+
|
|
785
|
+
// The literal "nodes" segment must not be mistaken for a workflow node ID.
|
|
786
|
+
assert.equal(sync.getSyncPropByPath(data, 'workflow.nfirst01.params.script'), 'Original\n');
|
|
787
|
+
assert.equal(sync.getSyncPropByPath(data, 'workflow.nodes.0.data.params.script'), 'Original\n');
|
|
788
|
+
assert.equal(sync.setSyncPropByPath(data, 'workflow.nodes.0.data.params.script', 'Indexed\n'), true);
|
|
789
|
+
assert.equal(sync.getSyncPropByPath(data, 'workflow.nfirst01.params.script'), 'Indexed\n');
|
|
790
|
+
assert.equal(sync.setSyncPropByPath(data, 'workflow.nfirst01.params.script', 'Shorthand\n'), true);
|
|
791
|
+
assert.equal(sync.getSyncPropByPath(data, 'workflow.nodes.0.data.params.script'), 'Shorthand\n');
|
|
792
|
+
assert.equal(sync.getSyncPropByPath(data, 'workflow.nmissing1.params.script'), undefined);
|
|
793
|
+
assert.equal(sync.setSyncPropByPath(data, 'workflow.nmissing1.params.script', 'Missing\n'), undefined);
|
|
794
|
+
assert.equal(data.workflow.nodes.length, 1);
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
test('a workflow neighbor for a missing node stops sync before upload', t => {
|
|
798
|
+
const workflow = {
|
|
799
|
+
id: 'flow', title: 'Flow', type: 'workflow', category: 'operations',
|
|
800
|
+
workflow: { nodes: [
|
|
801
|
+
{ id: 'nfirst01', type: 'job', data: { params: { script: 'Inline\n' } } }
|
|
802
|
+
] }
|
|
803
|
+
};
|
|
804
|
+
const result = runSync(t, {
|
|
805
|
+
events: [workflow],
|
|
806
|
+
existingSources: [
|
|
807
|
+
{ path: 'Flow.json', type: 'event', data: workflow },
|
|
808
|
+
{ path: 'Flow-workflow.nmissing1.params.script.sh', contents: 'Orphan\n' }
|
|
809
|
+
],
|
|
810
|
+
args: { up: 'events' }
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
assert.equal(result.status, 1);
|
|
814
|
+
assert.match(result.report.warnings[0], /does not match an existing string property/);
|
|
815
|
+
assert.deepEqual(result.report.errors, []);
|
|
816
|
+
assert.equal(result.report.requests.some(request => request.method === 'update_event'), false);
|
|
817
|
+
});
|
|
818
|
+
|
|
658
819
|
test('a property neighbor must resolve to an existing string property', t => {
|
|
659
820
|
const category = { id: 'fixture', title: 'Fixture', notes: '' };
|
|
660
821
|
const result = runSync(t, {
|