@ansible-flow/core 0.85.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/ai/handlers.js +8 -8
- package/dist/ai/scenarios.js +3 -3
- package/dist/awx/import.js +3 -3
- package/dist/chunks/{chunk-QMIXDOU6.js → chunk-5XGAGNH5.js} +5 -5
- package/dist/chunks/{chunk-22D2NC2L.js → chunk-DHU774ES.js} +5 -5
- package/dist/chunks/{chunk-6M3OG3E4.js → chunk-H7Z2FBRD.js} +4 -4
- package/dist/chunks/{chunk-V2SPY2EI.js → chunk-NT62IE3Y.js} +2 -2
- package/dist/chunks/{chunk-2HMLC2AO.js → chunk-TPRZZHXA.js} +3 -3
- package/dist/flow/merge.d.ts +27 -0
- package/dist/flow/merge.js +422 -0
- package/dist/flow/merge.js.map +7 -0
- package/dist/flow/mergeText.d.ts +3 -0
- package/dist/flow/mergeText.js +71 -0
- package/dist/flow/mergeText.js.map +7 -0
- package/dist/flow/valueText.d.ts +6 -0
- package/dist/flow/valueText.js +56 -0
- package/dist/flow/valueText.js.map +7 -0
- package/dist/import/outline.js +4 -4
- package/dist/import/playbook.js +2 -2
- package/dist/import/source.js +2 -2
- package/dist/lens/index.js +10 -10
- package/dist/messageCheck.js +2 -0
- package/dist/messageCheck.js.map +2 -2
- package/dist/protocol.d.ts +28 -0
- package/package.json +1 -1
- /package/dist/chunks/{chunk-QMIXDOU6.js.map → chunk-5XGAGNH5.js.map} +0 -0
- /package/dist/chunks/{chunk-22D2NC2L.js.map → chunk-DHU774ES.js.map} +0 -0
- /package/dist/chunks/{chunk-6M3OG3E4.js.map → chunk-H7Z2FBRD.js.map} +0 -0
- /package/dist/chunks/{chunk-V2SPY2EI.js.map → chunk-NT62IE3Y.js.map} +0 -0
- /package/dist/chunks/{chunk-2HMLC2AO.js.map → chunk-TPRZZHXA.js.map} +0 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Ansible Flow Designer core: the flow model, commands, validation and playbook generation, for any JavaScript host.
|
|
4
4
|
|
|
5
|
-
Built from [Ansible Flow Designer](https://github.com/ChistokhinSV/ansible-flow-designer) 0.
|
|
5
|
+
Built from [Ansible Flow Designer](https://github.com/ChistokhinSV/ansible-flow-designer) 0.86.0
|
|
6
6
|
and released with it. Modules are imported by their path in the source tree, for example
|
|
7
7
|
`@ansible-flow/core/ai/contributions`.
|
|
8
8
|
|
package/dist/ai/handlers.js
CHANGED
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
generatedDirOf,
|
|
3
3
|
paged,
|
|
4
4
|
sourceOutline
|
|
5
|
-
} from "../chunks/chunk-
|
|
5
|
+
} from "../chunks/chunk-TPRZZHXA.js";
|
|
6
6
|
import {
|
|
7
7
|
readPlaybook
|
|
8
|
-
} from "../chunks/chunk-
|
|
8
|
+
} from "../chunks/chunk-5XGAGNH5.js";
|
|
9
9
|
import {
|
|
10
10
|
newFlowText
|
|
11
11
|
} from "../chunks/chunk-CFNYFH2A.js";
|
|
@@ -32,14 +32,14 @@ import {
|
|
|
32
32
|
scenarioProblemLines,
|
|
33
33
|
scenariosLine,
|
|
34
34
|
scenariosTool
|
|
35
|
-
} from "../chunks/chunk-
|
|
35
|
+
} from "../chunks/chunk-DHU774ES.js";
|
|
36
|
+
import "../chunks/chunk-FTJ6VOQL.js";
|
|
37
|
+
import "../chunks/chunk-2PSB652B.js";
|
|
36
38
|
import {
|
|
37
39
|
checkLive
|
|
38
40
|
} from "../chunks/chunk-CM23ODBB.js";
|
|
39
41
|
import "../chunks/chunk-7GLSXIZ3.js";
|
|
40
42
|
import "../chunks/chunk-23ISDYUS.js";
|
|
41
|
-
import "../chunks/chunk-FTJ6VOQL.js";
|
|
42
|
-
import "../chunks/chunk-2PSB652B.js";
|
|
43
43
|
import "../chunks/chunk-TH6ICGA5.js";
|
|
44
44
|
import "../chunks/chunk-KOG6OURH.js";
|
|
45
45
|
import "../chunks/chunk-CXVJWFGT.js";
|
|
@@ -50,13 +50,13 @@ import "../chunks/chunk-ZAYNYPCX.js";
|
|
|
50
50
|
import "../chunks/chunk-56VTJ6YF.js";
|
|
51
51
|
import {
|
|
52
52
|
importWorkflow
|
|
53
|
-
} from "../chunks/chunk-
|
|
53
|
+
} from "../chunks/chunk-NT62IE3Y.js";
|
|
54
54
|
import {
|
|
55
55
|
importPlaybook
|
|
56
|
-
} from "../chunks/chunk-
|
|
56
|
+
} from "../chunks/chunk-H7Z2FBRD.js";
|
|
57
|
+
import "../chunks/chunk-LNFMYJUJ.js";
|
|
57
58
|
import "../chunks/chunk-5CD65KH3.js";
|
|
58
59
|
import "../chunks/chunk-6L43PFIN.js";
|
|
59
|
-
import "../chunks/chunk-LNFMYJUJ.js";
|
|
60
60
|
import "../chunks/chunk-PDUQ3KMW.js";
|
|
61
61
|
import {
|
|
62
62
|
checksum
|
package/dist/ai/scenarios.js
CHANGED
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
scenarioProblemLines,
|
|
4
4
|
scenariosLine,
|
|
5
5
|
scenariosTool
|
|
6
|
-
} from "../chunks/chunk-
|
|
6
|
+
} from "../chunks/chunk-DHU774ES.js";
|
|
7
|
+
import "../chunks/chunk-FTJ6VOQL.js";
|
|
8
|
+
import "../chunks/chunk-2PSB652B.js";
|
|
7
9
|
import "../chunks/chunk-CM23ODBB.js";
|
|
8
10
|
import "../chunks/chunk-7GLSXIZ3.js";
|
|
9
11
|
import "../chunks/chunk-23ISDYUS.js";
|
|
10
|
-
import "../chunks/chunk-FTJ6VOQL.js";
|
|
11
|
-
import "../chunks/chunk-2PSB652B.js";
|
|
12
12
|
import "../chunks/chunk-TH6ICGA5.js";
|
|
13
13
|
import "../chunks/chunk-KOG6OURH.js";
|
|
14
14
|
import "../chunks/chunk-CXVJWFGT.js";
|
package/dist/awx/import.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
expandable,
|
|
3
3
|
importWorkflow
|
|
4
|
-
} from "../chunks/chunk-
|
|
5
|
-
import "../chunks/chunk-
|
|
4
|
+
} from "../chunks/chunk-NT62IE3Y.js";
|
|
5
|
+
import "../chunks/chunk-H7Z2FBRD.js";
|
|
6
|
+
import "../chunks/chunk-LNFMYJUJ.js";
|
|
6
7
|
import "../chunks/chunk-5CD65KH3.js";
|
|
7
8
|
import "../chunks/chunk-6L43PFIN.js";
|
|
8
|
-
import "../chunks/chunk-LNFMYJUJ.js";
|
|
9
9
|
import "../chunks/chunk-PDUQ3KMW.js";
|
|
10
10
|
import "../chunks/chunk-BAZIS67B.js";
|
|
11
11
|
import "../chunks/chunk-TC7FOG4T.js";
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
builtinFqcn,
|
|
3
|
+
shortName
|
|
4
|
+
} from "./chunk-LNFMYJUJ.js";
|
|
1
5
|
import {
|
|
2
6
|
basename,
|
|
3
7
|
dirname,
|
|
@@ -12,10 +16,6 @@ import {
|
|
|
12
16
|
parseConfig,
|
|
13
17
|
toValue
|
|
14
18
|
} from "./chunk-6L43PFIN.js";
|
|
15
|
-
import {
|
|
16
|
-
builtinFqcn,
|
|
17
|
-
shortName
|
|
18
|
-
} from "./chunk-LNFMYJUJ.js";
|
|
19
19
|
import {
|
|
20
20
|
isAllowedKeyword
|
|
21
21
|
} from "./chunk-OEQXGULI.js";
|
|
@@ -833,4 +833,4 @@ export {
|
|
|
833
833
|
readPlaybook,
|
|
834
834
|
scanFolder
|
|
835
835
|
};
|
|
836
|
-
//# sourceMappingURL=chunk-
|
|
836
|
+
//# sourceMappingURL=chunk-5XGAGNH5.js.map
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
checkLive,
|
|
3
|
-
simulatedEnds
|
|
4
|
-
} from "./chunk-CM23ODBB.js";
|
|
5
1
|
import {
|
|
6
2
|
endWords,
|
|
7
3
|
scenarioMisses
|
|
@@ -10,6 +6,10 @@ import {
|
|
|
10
6
|
ScenarioEditError,
|
|
11
7
|
planScenarioEdit
|
|
12
8
|
} from "./chunk-2PSB652B.js";
|
|
9
|
+
import {
|
|
10
|
+
checkLive,
|
|
11
|
+
simulatedEnds
|
|
12
|
+
} from "./chunk-CM23ODBB.js";
|
|
13
13
|
import {
|
|
14
14
|
blocking,
|
|
15
15
|
checkScenarios,
|
|
@@ -218,4 +218,4 @@ export {
|
|
|
218
218
|
scenarioProblemLines,
|
|
219
219
|
scenariosTool
|
|
220
220
|
};
|
|
221
|
-
//# sourceMappingURL=chunk-
|
|
221
|
+
//# sourceMappingURL=chunk-DHU774ES.js.map
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
shortName
|
|
3
|
+
} from "./chunk-LNFMYJUJ.js";
|
|
1
4
|
import {
|
|
2
5
|
fromRoot,
|
|
3
6
|
localize
|
|
@@ -6,9 +9,6 @@ import {
|
|
|
6
9
|
basename,
|
|
7
10
|
dirname
|
|
8
11
|
} from "./chunk-6L43PFIN.js";
|
|
9
|
-
import {
|
|
10
|
-
shortName
|
|
11
|
-
} from "./chunk-LNFMYJUJ.js";
|
|
12
12
|
import {
|
|
13
13
|
LevelBuilder,
|
|
14
14
|
importedFlow
|
|
@@ -989,4 +989,4 @@ export {
|
|
|
989
989
|
flatPlays,
|
|
990
990
|
importPlaybook
|
|
991
991
|
};
|
|
992
|
-
//# sourceMappingURL=chunk-
|
|
992
|
+
//# sourceMappingURL=chunk-H7Z2FBRD.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
flatPlays,
|
|
3
3
|
importPlaybook
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-H7Z2FBRD.js";
|
|
5
5
|
import {
|
|
6
6
|
LevelBuilder,
|
|
7
7
|
importedFlow
|
|
@@ -405,4 +405,4 @@ export {
|
|
|
405
405
|
expandable,
|
|
406
406
|
importWorkflow
|
|
407
407
|
};
|
|
408
|
-
//# sourceMappingURL=chunk-
|
|
408
|
+
//# sourceMappingURL=chunk-NT62IE3Y.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readPlaybook,
|
|
3
3
|
scanFolder
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5XGAGNH5.js";
|
|
5
5
|
import {
|
|
6
6
|
exportTemplates,
|
|
7
7
|
jobTemplateFromExport,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "./chunk-IYVI5MSS.js";
|
|
11
11
|
import {
|
|
12
12
|
importPlaybook
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-H7Z2FBRD.js";
|
|
14
14
|
import {
|
|
15
15
|
fromRoot,
|
|
16
16
|
localize
|
|
@@ -455,4 +455,4 @@ export {
|
|
|
455
455
|
paged,
|
|
456
456
|
sourceOutline
|
|
457
457
|
};
|
|
458
|
-
//# sourceMappingURL=chunk-
|
|
458
|
+
//# sourceMappingURL=chunk-TPRZZHXA.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FlowDocument, Ulid } from './types';
|
|
2
|
+
export type MergeSide = 'ours' | 'theirs';
|
|
3
|
+
export interface MergeConflict {
|
|
4
|
+
/** Stable across merges of the same versions: choices are given by it. */
|
|
5
|
+
id: string;
|
|
6
|
+
kind: 'value' | 'removed' | 'wire' | 'format';
|
|
7
|
+
/** Where and what, in words: "Say hello: settings.timeout". */
|
|
8
|
+
label: string;
|
|
9
|
+
node?: Ulid;
|
|
10
|
+
annotation?: Ulid;
|
|
11
|
+
edge?: Ulid;
|
|
12
|
+
/** The field, dotted (`settings.timeout`); absent for a whole step, wire or the format. */
|
|
13
|
+
field?: string;
|
|
14
|
+
base?: unknown;
|
|
15
|
+
ours?: unknown;
|
|
16
|
+
theirs?: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface FlowMerge {
|
|
19
|
+
/** The merged flow, each conflict as its choice says (ours until chosen). */
|
|
20
|
+
doc: FlowDocument;
|
|
21
|
+
conflicts: MergeConflict[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Merges what `ours` and `theirs` changed since `base`. `choices` settle conflicts by id (ours
|
|
25
|
+
* when not given). Flows without IDs are matched by the IDs their keys give (Complete IDs).
|
|
26
|
+
*/
|
|
27
|
+
export declare function mergeFlows(baseDoc: FlowDocument, oursDoc: FlowDocument, theirsDoc: FlowDocument, choices?: Record<string, MergeSide>): FlowMerge;
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonical
|
|
3
|
+
} from "../chunks/chunk-TG3C3GIL.js";
|
|
4
|
+
import {
|
|
5
|
+
withIds
|
|
6
|
+
} from "../chunks/chunk-XRGZNPPO.js";
|
|
7
|
+
import "../chunks/chunk-VG3RKETK.js";
|
|
8
|
+
import "../chunks/chunk-TS4N2EIO.js";
|
|
9
|
+
import "../chunks/chunk-BCX7P6SV.js";
|
|
10
|
+
import "../chunks/chunk-3Z2U3LN7.js";
|
|
11
|
+
import "../chunks/chunk-GHG3VINX.js";
|
|
12
|
+
import "../chunks/chunk-5TUAMVIV.js";
|
|
13
|
+
import "../chunks/chunk-45CE2RUH.js";
|
|
14
|
+
import "../chunks/chunk-E2YZJWO7.js";
|
|
15
|
+
import "../chunks/chunk-ZP5LQ4XC.js";
|
|
16
|
+
import {
|
|
17
|
+
ROOT
|
|
18
|
+
} from "../chunks/chunk-2OD7UZ7V.js";
|
|
19
|
+
import "../chunks/chunk-AGWQ3ANE.js";
|
|
20
|
+
|
|
21
|
+
// src/core/flow/merge.ts
|
|
22
|
+
function flatten(doc) {
|
|
23
|
+
const flat = {
|
|
24
|
+
doc,
|
|
25
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
26
|
+
edges: /* @__PURE__ */ new Map(),
|
|
27
|
+
annotations: /* @__PURE__ */ new Map(),
|
|
28
|
+
rootKeys: ["annotations", "layout", "decisions"].filter((k) => k in doc)
|
|
29
|
+
};
|
|
30
|
+
const visit = (level, nodes, edges, annotations, layout) => {
|
|
31
|
+
for (const node of nodes ?? []) {
|
|
32
|
+
if (!node?.id) continue;
|
|
33
|
+
const { subflow, ...own } = node;
|
|
34
|
+
flat.nodes.set(node.id, {
|
|
35
|
+
level,
|
|
36
|
+
own,
|
|
37
|
+
...subflow ? {
|
|
38
|
+
body: {
|
|
39
|
+
...subflow.boundary ? { boundary: subflow.boundary } : {},
|
|
40
|
+
keys: Object.keys(subflow)
|
|
41
|
+
}
|
|
42
|
+
} : {},
|
|
43
|
+
...layout?.[node.id] ? { position: layout[node.id] } : {}
|
|
44
|
+
});
|
|
45
|
+
if (subflow)
|
|
46
|
+
visit(node.id, subflow.nodes, subflow.edges, subflow.annotations, subflow.layout);
|
|
47
|
+
}
|
|
48
|
+
for (const edge of edges ?? []) if (edge?.id) flat.edges.set(edge.id, { level, edge });
|
|
49
|
+
for (const a of annotations ?? [])
|
|
50
|
+
if (a?.id) flat.annotations.set(a.id, { level, annotation: a });
|
|
51
|
+
};
|
|
52
|
+
visit(ROOT, doc.nodes, doc.edges, doc.annotations, doc.layout);
|
|
53
|
+
return flat;
|
|
54
|
+
}
|
|
55
|
+
var same = (a, b) => JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
|
|
56
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
57
|
+
var Merger = class {
|
|
58
|
+
constructor(choices) {
|
|
59
|
+
this.choices = choices;
|
|
60
|
+
}
|
|
61
|
+
choices;
|
|
62
|
+
conflicts = [];
|
|
63
|
+
pick(id) {
|
|
64
|
+
return this.choices[id] ?? "ours";
|
|
65
|
+
}
|
|
66
|
+
conflict(c) {
|
|
67
|
+
this.conflicts.push(c);
|
|
68
|
+
return this.pick(c.id);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One value, three ways. Records are merged key by key `depth` levels down; a value both sides
|
|
72
|
+
* changed differently is a conflict named by `id` (`<owner>:<field>`).
|
|
73
|
+
*/
|
|
74
|
+
value(base, ours, theirs, at, depth) {
|
|
75
|
+
if (same(ours, theirs)) return ours;
|
|
76
|
+
if (same(base, ours)) return theirs;
|
|
77
|
+
if (same(base, theirs)) return ours;
|
|
78
|
+
if (depth > 0 && isRecord(ours) && isRecord(theirs) && (base === void 0 || isRecord(base))) {
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
81
|
+
const v = this.value(
|
|
82
|
+
base?.[key],
|
|
83
|
+
ours[key],
|
|
84
|
+
theirs[key],
|
|
85
|
+
{
|
|
86
|
+
...at,
|
|
87
|
+
id: `${at.id}.${key}`,
|
|
88
|
+
field: at.field ? `${at.field}.${key}` : key
|
|
89
|
+
},
|
|
90
|
+
depth - 1
|
|
91
|
+
);
|
|
92
|
+
if (v !== void 0) out[key] = v;
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
const side = this.conflict({
|
|
97
|
+
...at.owner,
|
|
98
|
+
id: at.id,
|
|
99
|
+
kind: "value",
|
|
100
|
+
label: at.field ? `${at.label}: ${at.field}` : at.label,
|
|
101
|
+
...at.field ? { field: at.field } : {},
|
|
102
|
+
base,
|
|
103
|
+
ours,
|
|
104
|
+
theirs
|
|
105
|
+
});
|
|
106
|
+
return side === "ours" ? ours : theirs;
|
|
107
|
+
}
|
|
108
|
+
/** The fields of a record, each merged on its own (`depth` levels down inside each). */
|
|
109
|
+
fields(base, ours, theirs, at, depth) {
|
|
110
|
+
const out = {};
|
|
111
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
112
|
+
const v = this.value(
|
|
113
|
+
base?.[key],
|
|
114
|
+
ours[key],
|
|
115
|
+
theirs[key],
|
|
116
|
+
{ ...at, id: `${at.id}:${key}`, field: key },
|
|
117
|
+
depth
|
|
118
|
+
);
|
|
119
|
+
if (v !== void 0) out[key] = v;
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
function quiet(base, ours, theirs) {
|
|
125
|
+
return same(base, ours) ? theirs : ours;
|
|
126
|
+
}
|
|
127
|
+
function mergeFlows(baseDoc, oursDoc, theirsDoc, choices = {}) {
|
|
128
|
+
const m = new Merger(choices);
|
|
129
|
+
if (oursDoc.schema_version !== theirsDoc.schema_version) {
|
|
130
|
+
const side = m.conflict({
|
|
131
|
+
id: "format",
|
|
132
|
+
kind: "format",
|
|
133
|
+
label: `The flow's format: ${oursDoc.schema_version} here, ${theirsDoc.schema_version} in theirs (upgrade both first)`,
|
|
134
|
+
ours: oursDoc.schema_version,
|
|
135
|
+
theirs: theirsDoc.schema_version
|
|
136
|
+
});
|
|
137
|
+
return { doc: side === "ours" ? oursDoc : theirsDoc, conflicts: m.conflicts };
|
|
138
|
+
}
|
|
139
|
+
const base = flatten(withIds(baseDoc));
|
|
140
|
+
const ours = flatten(withIds(oursDoc));
|
|
141
|
+
const theirs = flatten(withIds(theirsDoc));
|
|
142
|
+
const titleOf = (id) => (ours.nodes.get(id) ?? theirs.nodes.get(id) ?? base.nodes.get(id))?.own.title ?? id;
|
|
143
|
+
const touched = (side, id, seen = /* @__PURE__ */ new Set()) => {
|
|
144
|
+
if (seen.has(id)) return false;
|
|
145
|
+
seen.add(id);
|
|
146
|
+
const now = side.nodes.get(id);
|
|
147
|
+
const was = base.nodes.get(id);
|
|
148
|
+
if (!now) return false;
|
|
149
|
+
if (!was || !same(now.own, was.own) || now.level !== was.level || !same(now.body, was.body))
|
|
150
|
+
return true;
|
|
151
|
+
for (const [eid, e] of side.edges)
|
|
152
|
+
if ((e.edge.from.node === id || e.edge.to.node === id) && !same(e, base.edges.get(eid)))
|
|
153
|
+
return true;
|
|
154
|
+
for (const [aid, a] of side.annotations)
|
|
155
|
+
if ((a.annotation.on === id || a.level === id) && !same(a, base.annotations.get(aid)))
|
|
156
|
+
return true;
|
|
157
|
+
for (const [cid, c] of side.nodes) if (c.level === id && touched(side, cid, seen)) return true;
|
|
158
|
+
return false;
|
|
159
|
+
};
|
|
160
|
+
const order = (key) => [
|
|
161
|
+
.../* @__PURE__ */ new Set([...ours[key].keys(), ...theirs[key].keys(), ...base[key].keys()])
|
|
162
|
+
];
|
|
163
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
164
|
+
for (const id of order("nodes")) {
|
|
165
|
+
const b = base.nodes.get(id);
|
|
166
|
+
const o = ours.nodes.get(id);
|
|
167
|
+
const t = theirs.nodes.get(id);
|
|
168
|
+
if (!o && !t) continue;
|
|
169
|
+
if (b && (!o || !t)) {
|
|
170
|
+
const kept = o ?? t;
|
|
171
|
+
if (!touched(o ? ours : theirs, id)) continue;
|
|
172
|
+
const side = m.conflict({
|
|
173
|
+
id: `node:${id}`,
|
|
174
|
+
kind: "removed",
|
|
175
|
+
node: id,
|
|
176
|
+
label: `${titleOf(id)}: removed ${o ? "in theirs" : "here"}, changed ${o ? "here" : "in theirs"}`,
|
|
177
|
+
ours: o ? "changed" : "removed",
|
|
178
|
+
theirs: o ? "removed" : "changed"
|
|
179
|
+
});
|
|
180
|
+
if (side === "ours" === !!o) nodes.set(id, kept);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!o || !t) {
|
|
184
|
+
nodes.set(id, o ?? t);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const owner = { node: id };
|
|
188
|
+
const label = titleOf(id);
|
|
189
|
+
const own = m.fields(
|
|
190
|
+
b?.own,
|
|
191
|
+
o.own,
|
|
192
|
+
t.own,
|
|
193
|
+
{ id: `node:${id}`, label, owner },
|
|
194
|
+
3
|
|
195
|
+
);
|
|
196
|
+
const level2 = m.value(
|
|
197
|
+
b?.level,
|
|
198
|
+
o.level,
|
|
199
|
+
t.level,
|
|
200
|
+
{ id: `node:${id}:level`, label, field: "level (moved into another subflow)", owner },
|
|
201
|
+
0
|
|
202
|
+
);
|
|
203
|
+
let body;
|
|
204
|
+
const hasBody = m.value(
|
|
205
|
+
!!b?.body,
|
|
206
|
+
!!o.body,
|
|
207
|
+
!!t.body,
|
|
208
|
+
{ id: `node:${id}:subflow`, label, field: "subflow", owner },
|
|
209
|
+
0
|
|
210
|
+
);
|
|
211
|
+
if (hasBody) {
|
|
212
|
+
const boundary = m.value(
|
|
213
|
+
b?.body?.boundary,
|
|
214
|
+
o.body?.boundary,
|
|
215
|
+
t.body?.boundary,
|
|
216
|
+
{ id: `node:${id}:boundary`, label, field: "boundary", owner },
|
|
217
|
+
2
|
|
218
|
+
);
|
|
219
|
+
body = {
|
|
220
|
+
...boundary ? { boundary } : {},
|
|
221
|
+
keys: [.../* @__PURE__ */ new Set([...o.body?.keys ?? [], ...t.body?.keys ?? []])]
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const position = quiet(b?.position, o.position, t.position);
|
|
225
|
+
nodes.set(id, {
|
|
226
|
+
level: level2,
|
|
227
|
+
own,
|
|
228
|
+
...body ? { body } : {},
|
|
229
|
+
...position ? { position } : {}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
for (let again = true; again; ) {
|
|
233
|
+
again = false;
|
|
234
|
+
for (const [id, n] of nodes)
|
|
235
|
+
if (n.level !== ROOT && !nodes.get(n.level)?.body) {
|
|
236
|
+
nodes.delete(id);
|
|
237
|
+
again = true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const edges = /* @__PURE__ */ new Map();
|
|
241
|
+
for (const id of order("edges")) {
|
|
242
|
+
const b = base.edges.get(id);
|
|
243
|
+
const o = ours.edges.get(id);
|
|
244
|
+
const t = theirs.edges.get(id);
|
|
245
|
+
if (!o && !t) continue;
|
|
246
|
+
if (b && (!o || !t)) {
|
|
247
|
+
const kept = o ?? t;
|
|
248
|
+
if (same(kept, b)) continue;
|
|
249
|
+
const side = m.conflict({
|
|
250
|
+
id: `wire:${id}`,
|
|
251
|
+
kind: "wire",
|
|
252
|
+
edge: id,
|
|
253
|
+
label: `The wire from ${titleOf(kept.edge.from.node)} (${kept.edge.from.port}): removed ${o ? "in theirs" : "here"}, changed ${o ? "here" : "in theirs"}`,
|
|
254
|
+
ours: o ? kept.edge : void 0,
|
|
255
|
+
theirs: o ? void 0 : kept.edge
|
|
256
|
+
});
|
|
257
|
+
if (side === "ours" === !!o) edges.set(id, { ...kept, from: o ? "ours" : "theirs" });
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (!o || !t) {
|
|
261
|
+
edges.set(id, { ...o ?? t, from: o ? "ours" : "theirs" });
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const merged = m.value(
|
|
265
|
+
b,
|
|
266
|
+
o,
|
|
267
|
+
t,
|
|
268
|
+
{
|
|
269
|
+
id: `wire:${id}`,
|
|
270
|
+
label: `The wire from ${titleOf(o.edge.from.node)} (${o.edge.from.port})`,
|
|
271
|
+
field: "",
|
|
272
|
+
owner: { edge: id }
|
|
273
|
+
},
|
|
274
|
+
0
|
|
275
|
+
);
|
|
276
|
+
edges.set(id, { ...merged, from: "both" });
|
|
277
|
+
}
|
|
278
|
+
const ends = /* @__PURE__ */ new Set();
|
|
279
|
+
for (const [id, e] of edges) {
|
|
280
|
+
const a = nodes.get(e.edge.from.node);
|
|
281
|
+
const z = nodes.get(e.edge.to.node);
|
|
282
|
+
const key = `${e.level}|${e.edge.from.node}.${e.edge.from.port}>${e.edge.to.node}.${e.edge.to.port}`;
|
|
283
|
+
if (!a || !z || a.level !== e.level || z.level !== e.level || ends.has(key)) edges.delete(id);
|
|
284
|
+
else ends.add(key);
|
|
285
|
+
}
|
|
286
|
+
const byPort = /* @__PURE__ */ new Map();
|
|
287
|
+
for (const [id, e] of edges) {
|
|
288
|
+
const key = `${e.edge.from.node}|${e.edge.from.port}`;
|
|
289
|
+
byPort.set(key, [...byPort.get(key) ?? [], id]);
|
|
290
|
+
}
|
|
291
|
+
for (const [key, ids] of byPort) {
|
|
292
|
+
if (ids.length < 2) continue;
|
|
293
|
+
const sides = new Set(ids.map((id) => edges.get(id)?.from));
|
|
294
|
+
if (!sides.has("ours") && !sides.has("theirs")) continue;
|
|
295
|
+
const [node = "", port = ""] = key.split("|");
|
|
296
|
+
const target = (s) => ids.filter((id) => (s === "ours" ? ours : theirs).edges.has(id)).map((id) => titleOf(edges.get(id)?.edge.to.node ?? "")).join(", ");
|
|
297
|
+
const side = m.conflict({
|
|
298
|
+
id: `port:${node}.${port}`,
|
|
299
|
+
kind: "wire",
|
|
300
|
+
node,
|
|
301
|
+
label: `${titleOf(node)}: port ${port} wired to ${target("ours") || "nothing"} here, to ${target("theirs") || "nothing"} in theirs`,
|
|
302
|
+
field: `ports.${port}`,
|
|
303
|
+
ours: target("ours"),
|
|
304
|
+
theirs: target("theirs")
|
|
305
|
+
});
|
|
306
|
+
const keep = side === "ours" ? ours : theirs;
|
|
307
|
+
for (const id of ids) if (!keep.edges.has(id)) edges.delete(id);
|
|
308
|
+
}
|
|
309
|
+
const annotations = /* @__PURE__ */ new Map();
|
|
310
|
+
const PLACE = ["x", "y", "width", "height"];
|
|
311
|
+
for (const id of order("annotations")) {
|
|
312
|
+
const b = base.annotations.get(id);
|
|
313
|
+
const o = ours.annotations.get(id);
|
|
314
|
+
const t = theirs.annotations.get(id);
|
|
315
|
+
if (!o && !t) continue;
|
|
316
|
+
if (b && (!o || !t)) {
|
|
317
|
+
const kept = o ?? t;
|
|
318
|
+
if (same(kept, b)) continue;
|
|
319
|
+
const side = m.conflict({
|
|
320
|
+
id: `note:${id}`,
|
|
321
|
+
kind: "removed",
|
|
322
|
+
annotation: id,
|
|
323
|
+
label: `The note "${kept.annotation.text.split("\n")[0]}": removed ${o ? "in theirs" : "here"}, changed ${o ? "here" : "in theirs"}`,
|
|
324
|
+
ours: o ? "changed" : "removed",
|
|
325
|
+
theirs: o ? "removed" : "changed"
|
|
326
|
+
});
|
|
327
|
+
if (side === "ours" === !!o) annotations.set(id, kept);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (!o || !t) {
|
|
331
|
+
annotations.set(id, o ?? t);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
const strip = (a) => a && Object.fromEntries(Object.entries(a).filter(([k]) => !PLACE.includes(k)));
|
|
335
|
+
const label = `The note "${o.annotation.text.split("\n")[0]}"`;
|
|
336
|
+
const fields = m.fields(
|
|
337
|
+
strip(b?.annotation),
|
|
338
|
+
strip(o.annotation),
|
|
339
|
+
strip(t.annotation),
|
|
340
|
+
{ id: `note:${id}`, label, owner: { annotation: id } },
|
|
341
|
+
1
|
|
342
|
+
);
|
|
343
|
+
const place = {};
|
|
344
|
+
for (const k of PLACE) {
|
|
345
|
+
const v = quiet(
|
|
346
|
+
b?.annotation?.[k],
|
|
347
|
+
o.annotation[k],
|
|
348
|
+
t.annotation[k]
|
|
349
|
+
);
|
|
350
|
+
if (v !== void 0) place[k] = v;
|
|
351
|
+
}
|
|
352
|
+
const level2 = quiet(b?.level, o.level, t.level) ?? o.level;
|
|
353
|
+
annotations.set(id, { level: level2, annotation: { ...fields, ...place } });
|
|
354
|
+
}
|
|
355
|
+
for (const [id, a] of annotations)
|
|
356
|
+
if (a.level !== ROOT && !nodes.get(a.level)?.body || a.annotation.on && !nodes.has(a.annotation.on))
|
|
357
|
+
annotations.delete(id);
|
|
358
|
+
const { version: vb, ...hb } = base.doc.flow;
|
|
359
|
+
const { version: vo, ...ho } = ours.doc.flow;
|
|
360
|
+
const { version: vt, ...ht } = theirs.doc.flow;
|
|
361
|
+
const header = m.fields(
|
|
362
|
+
hb,
|
|
363
|
+
ho,
|
|
364
|
+
ht,
|
|
365
|
+
{ id: "flow", label: "The flow", owner: {} },
|
|
366
|
+
2
|
|
367
|
+
);
|
|
368
|
+
const version = vo === vt ? vo : vo === vb ? vt : vt === vb ? vo : Math.max(vo, vt) + 1;
|
|
369
|
+
const ident = (d) => JSON.stringify(canonical(d));
|
|
370
|
+
const baseDecisions = new Set((base.doc.decisions ?? []).map(ident));
|
|
371
|
+
const theirsDecisions = new Set((theirs.doc.decisions ?? []).map(ident));
|
|
372
|
+
const decisions = [
|
|
373
|
+
...(ours.doc.decisions ?? []).filter(
|
|
374
|
+
(d) => !baseDecisions.has(ident(d)) || theirsDecisions.has(ident(d))
|
|
375
|
+
),
|
|
376
|
+
...(theirs.doc.decisions ?? []).filter(
|
|
377
|
+
(d) => !baseDecisions.has(ident(d)) && !(ours.doc.decisions ?? []).some((x) => ident(x) === ident(d))
|
|
378
|
+
)
|
|
379
|
+
];
|
|
380
|
+
const level = (id) => {
|
|
381
|
+
const levelNodes = [];
|
|
382
|
+
const layout = {};
|
|
383
|
+
for (const [nid, n] of nodes) {
|
|
384
|
+
if (n.level !== id) continue;
|
|
385
|
+
const node = { ...n.own };
|
|
386
|
+
if (n.body) {
|
|
387
|
+
const inner = level(nid);
|
|
388
|
+
const keys2 = new Set(n.body.keys);
|
|
389
|
+
const body = { nodes: inner.nodes, edges: inner.edges };
|
|
390
|
+
if (inner.annotations.length || keys2.has("annotations"))
|
|
391
|
+
body.annotations = inner.annotations;
|
|
392
|
+
if (Object.keys(inner.layout).length || keys2.has("layout")) body.layout = inner.layout;
|
|
393
|
+
if (n.body.boundary) body.boundary = n.body.boundary;
|
|
394
|
+
node.subflow = body;
|
|
395
|
+
}
|
|
396
|
+
levelNodes.push(node);
|
|
397
|
+
if (n.position) layout[nid] = n.position;
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
nodes: levelNodes,
|
|
401
|
+
edges: [...edges.values()].filter((e) => e.level === id).map((e) => e.edge),
|
|
402
|
+
annotations: [...annotations.values()].filter((a) => a.level === id).map((a) => a.annotation),
|
|
403
|
+
layout
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
const top = level(ROOT);
|
|
407
|
+
const keys = /* @__PURE__ */ new Set([...ours.rootKeys, ...theirs.rootKeys]);
|
|
408
|
+
const doc = {
|
|
409
|
+
schema_version: oursDoc.schema_version,
|
|
410
|
+
flow: { ...header, version },
|
|
411
|
+
nodes: top.nodes,
|
|
412
|
+
edges: top.edges,
|
|
413
|
+
...top.annotations.length || keys.has("annotations") ? { annotations: top.annotations } : {},
|
|
414
|
+
...Object.keys(top.layout).length || keys.has("layout") ? { layout: top.layout } : {},
|
|
415
|
+
...decisions.length || keys.has("decisions") ? { decisions } : {}
|
|
416
|
+
};
|
|
417
|
+
return { doc, conflicts: m.conflicts };
|
|
418
|
+
}
|
|
419
|
+
export {
|
|
420
|
+
mergeFlows
|
|
421
|
+
};
|
|
422
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/core/flow/merge.ts"],
|
|
4
|
+
"sourcesContent": ["// AFD-283: a three-way merge of a flow, by the nodes' IDs rather than the YAML's lines. Two people\n// change one flow apart (two clones, a push through the server, a live session whose branch moved\n// on); what they changed in different places comes together on its own, and what they changed in\n// the same place is a conflict with both sides named. Pure: the merge driver, the designer and\n// the server call it with the three versions and the choices made so far.\n//\n// The flow is taken apart into its nodes, wires and Notes with the level each is on, so a step\n// one side moved into a subflow is still the same step. Fields are merged one by one (records a\n// few levels deep: a step's settings, inputs, its binding's parameters); a value both sides\n// changed differently is a conflict, kept as \"ours\" until someone chooses. Positions never\n// conflict: ours wins. Rules a merge could break on its own are conflicts too: a step one side\n// removed and the other changed or wired, and two wires on one output port.\nimport { withIds } from './completeIds';\nimport { ROOT } from './model';\nimport { canonical } from './semantic';\nimport type {\n Annotation,\n Decision,\n Edge,\n FlowDocument,\n FlowHeader,\n FlowNode,\n Position,\n SubflowBody,\n Ulid,\n} from './types';\n\nexport type MergeSide = 'ours' | 'theirs';\n\nexport interface MergeConflict {\n /** Stable across merges of the same versions: choices are given by it. */\n id: string;\n kind: 'value' | 'removed' | 'wire' | 'format';\n /** Where and what, in words: \"Say hello: settings.timeout\". */\n label: string;\n node?: Ulid;\n annotation?: Ulid;\n edge?: Ulid;\n /** The field, dotted (`settings.timeout`); absent for a whole step, wire or the format. */\n field?: string;\n base?: unknown;\n ours?: unknown;\n theirs?: unknown;\n}\n\nexport interface FlowMerge {\n /** The merged flow, each conflict as its choice says (ours until chosen). */\n doc: FlowDocument;\n conflicts: MergeConflict[];\n}\n\n// --- taking a flow apart\n\ninterface FlatNode {\n level: string;\n /** The node without its inner level. */\n own: Omit<FlowNode, 'subflow'>;\n /** Present for a subflow: its boundary, and which keys its body had (kept as they were). */\n body?: { boundary?: SubflowBody['boundary']; keys: string[] };\n position?: Position;\n}\ninterface FlatEdge {\n level: string;\n edge: Edge;\n}\ninterface FlatAnnotation {\n level: string;\n annotation: Annotation;\n}\ninterface Flat {\n doc: FlowDocument;\n nodes: Map<Ulid, FlatNode>;\n edges: Map<Ulid, FlatEdge>;\n annotations: Map<Ulid, FlatAnnotation>;\n /** Which optional keys the top level had (kept as they were). */\n rootKeys: string[];\n}\n\nfunction flatten(doc: FlowDocument): Flat {\n const flat: Flat = {\n doc,\n nodes: new Map(),\n edges: new Map(),\n annotations: new Map(),\n rootKeys: ['annotations', 'layout', 'decisions'].filter((k) => k in doc),\n };\n const visit = (\n level: string,\n nodes: FlowNode[] | undefined,\n edges: Edge[] | undefined,\n annotations: Annotation[] | undefined,\n layout: Record<string, Position> | undefined,\n ) => {\n for (const node of nodes ?? []) {\n if (!node?.id) continue;\n const { subflow, ...own } = node;\n flat.nodes.set(node.id, {\n level,\n own,\n ...(subflow\n ? {\n body: {\n ...(subflow.boundary ? { boundary: subflow.boundary } : {}),\n keys: Object.keys(subflow),\n },\n }\n : {}),\n ...(layout?.[node.id] ? { position: layout[node.id] } : {}),\n });\n if (subflow)\n visit(node.id, subflow.nodes, subflow.edges, subflow.annotations, subflow.layout);\n }\n for (const edge of edges ?? []) if (edge?.id) flat.edges.set(edge.id, { level, edge });\n for (const a of annotations ?? [])\n if (a?.id) flat.annotations.set(a.id, { level, annotation: a });\n };\n visit(ROOT, doc.nodes, doc.edges, doc.annotations, doc.layout);\n return flat;\n}\n\n// --- merging values\n\nconst same = (a: unknown, b: unknown) =>\n JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\n\nclass Merger {\n readonly conflicts: MergeConflict[] = [];\n constructor(private readonly choices: Record<string, MergeSide>) {}\n\n pick(id: string): MergeSide {\n return this.choices[id] ?? 'ours';\n }\n\n conflict(c: MergeConflict): MergeSide {\n this.conflicts.push(c);\n return this.pick(c.id);\n }\n\n /**\n * One value, three ways. Records are merged key by key `depth` levels down; a value both sides\n * changed differently is a conflict named by `id` (`<owner>:<field>`).\n */\n value(\n base: unknown,\n ours: unknown,\n theirs: unknown,\n at: { id: string; label: string; field: string; owner: Partial<MergeConflict> },\n depth: number,\n ): unknown {\n if (same(ours, theirs)) return ours;\n if (same(base, ours)) return theirs;\n if (same(base, theirs)) return ours;\n if (depth > 0 && isRecord(ours) && isRecord(theirs) && (base === undefined || isRecord(base))) {\n const out: Record<string, unknown> = {};\n for (const key of new Set([...Object.keys(ours), ...Object.keys(theirs)])) {\n const v = this.value(\n (base as Record<string, unknown> | undefined)?.[key],\n ours[key],\n theirs[key],\n {\n ...at,\n id: `${at.id}.${key}`,\n field: at.field ? `${at.field}.${key}` : key,\n },\n depth - 1,\n );\n if (v !== undefined) out[key] = v;\n }\n return out;\n }\n const side = this.conflict({\n ...at.owner,\n id: at.id,\n kind: 'value',\n label: at.field ? `${at.label}: ${at.field}` : at.label,\n ...(at.field ? { field: at.field } : {}),\n base,\n ours,\n theirs,\n });\n return side === 'ours' ? ours : theirs;\n }\n\n /** The fields of a record, each merged on its own (`depth` levels down inside each). */\n fields(\n base: Record<string, unknown> | undefined,\n ours: Record<string, unknown>,\n theirs: Record<string, unknown>,\n at: { id: string; label: string; owner: Partial<MergeConflict> },\n depth: number,\n ): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const key of new Set([...Object.keys(ours), ...Object.keys(theirs)])) {\n const v = this.value(\n base?.[key],\n ours[key],\n theirs[key],\n { ...at, id: `${at.id}:${key}`, field: key },\n depth,\n );\n if (v !== undefined) out[key] = v;\n }\n return out;\n }\n}\n\n/** A position, or another value that never conflicts: the side that moved it, else ours. */\nfunction quiet<T>(base: T | undefined, ours: T | undefined, theirs: T | undefined): T | undefined {\n return same(base, ours) ? theirs : ours;\n}\n\n// --- the merge\n\n/**\n * Merges what `ours` and `theirs` changed since `base`. `choices` settle conflicts by id (ours\n * when not given). Flows without IDs are matched by the IDs their keys give (Complete IDs).\n */\nexport function mergeFlows(\n baseDoc: FlowDocument,\n oursDoc: FlowDocument,\n theirsDoc: FlowDocument,\n choices: Record<string, MergeSide> = {},\n): FlowMerge {\n const m = new Merger(choices);\n if (oursDoc.schema_version !== theirsDoc.schema_version) {\n const side = m.conflict({\n id: 'format',\n kind: 'format',\n label: `The flow's format: ${oursDoc.schema_version} here, ${theirsDoc.schema_version} in theirs (upgrade both first)`,\n ours: oursDoc.schema_version,\n theirs: theirsDoc.schema_version,\n });\n return { doc: side === 'ours' ? oursDoc : theirsDoc, conflicts: m.conflicts };\n }\n const base = flatten(withIds(baseDoc));\n const ours = flatten(withIds(oursDoc));\n const theirs = flatten(withIds(theirsDoc));\n const titleOf = (id: Ulid) =>\n (ours.nodes.get(id) ?? theirs.nodes.get(id) ?? base.nodes.get(id))?.own.title ?? id;\n\n // What a side did to a step, its wires, or anything inside it: a removal by the other side then\n // loses work and is a conflict.\n const touched = (side: Flat, id: Ulid, seen = new Set<Ulid>()): boolean => {\n if (seen.has(id)) return false;\n seen.add(id);\n const now = side.nodes.get(id);\n const was = base.nodes.get(id);\n if (!now) return false;\n if (!was || !same(now.own, was.own) || now.level !== was.level || !same(now.body, was.body))\n return true;\n for (const [eid, e] of side.edges)\n if ((e.edge.from.node === id || e.edge.to.node === id) && !same(e, base.edges.get(eid)))\n return true;\n for (const [aid, a] of side.annotations)\n if ((a.annotation.on === id || a.level === id) && !same(a, base.annotations.get(aid)))\n return true;\n for (const [cid, c] of side.nodes) if (c.level === id && touched(side, cid, seen)) return true;\n return false;\n };\n\n // Steps.\n const order = (key: 'nodes' | 'edges' | 'annotations') => [\n ...new Set([...ours[key].keys(), ...theirs[key].keys(), ...base[key].keys()]),\n ];\n const nodes = new Map<Ulid, FlatNode>();\n for (const id of order('nodes')) {\n const b = base.nodes.get(id);\n const o = ours.nodes.get(id);\n const t = theirs.nodes.get(id);\n if (!o && !t) continue;\n if (b && (!o || !t)) {\n // One side removed it: gone, unless the other side changed it meanwhile.\n const kept = (o ?? t) as FlatNode;\n if (!touched(o ? ours : theirs, id)) continue;\n const side = m.conflict({\n id: `node:${id}`,\n kind: 'removed',\n node: id,\n label: `${titleOf(id)}: removed ${o ? 'in theirs' : 'here'}, changed ${o ? 'here' : 'in theirs'}`,\n ours: o ? 'changed' : 'removed',\n theirs: o ? 'removed' : 'changed',\n });\n if ((side === 'ours') === !!o) nodes.set(id, kept);\n continue;\n }\n if (!o || !t) {\n nodes.set(id, (o ?? t) as FlatNode);\n continue;\n }\n const owner = { node: id };\n const label = titleOf(id);\n const own = m.fields(\n b?.own as Record<string, unknown> | undefined,\n o.own as Record<string, unknown>,\n t.own as Record<string, unknown>,\n { id: `node:${id}`, label, owner },\n 3,\n ) as FlatNode['own'];\n const level = m.value(\n b?.level,\n o.level,\n t.level,\n { id: `node:${id}:level`, label, field: 'level (moved into another subflow)', owner },\n 0,\n ) as string;\n let body: FlatNode['body'];\n const hasBody = m.value(\n !!b?.body,\n !!o.body,\n !!t.body,\n { id: `node:${id}:subflow`, label, field: 'subflow', owner },\n 0,\n );\n if (hasBody) {\n const boundary = m.value(\n b?.body?.boundary,\n o.body?.boundary,\n t.body?.boundary,\n { id: `node:${id}:boundary`, label, field: 'boundary', owner },\n 2,\n ) as SubflowBody['boundary'];\n body = {\n ...(boundary ? { boundary } : {}),\n keys: [...new Set([...(o.body?.keys ?? []), ...(t.body?.keys ?? [])])],\n };\n }\n const position = quiet(b?.position, o.position, t.position);\n nodes.set(id, {\n level,\n own,\n ...(body ? { body } : {}),\n ...(position ? { position } : {}),\n });\n }\n // A step whose subflow is gone goes with it.\n for (let again = true; again; ) {\n again = false;\n for (const [id, n] of nodes)\n if (n.level !== ROOT && !nodes.get(n.level)?.body) {\n nodes.delete(id);\n again = true;\n }\n }\n\n // Wires.\n const edges = new Map<Ulid, FlatEdge & { from: MergeSide | 'both' }>();\n for (const id of order('edges')) {\n const b = base.edges.get(id);\n const o = ours.edges.get(id);\n const t = theirs.edges.get(id);\n if (!o && !t) continue;\n if (b && (!o || !t)) {\n const kept = (o ?? t) as FlatEdge;\n if (same(kept, b)) continue;\n // Removed on one side, rewired on the other.\n const side = m.conflict({\n id: `wire:${id}`,\n kind: 'wire',\n edge: id,\n label: `The wire from ${titleOf(kept.edge.from.node)} (${kept.edge.from.port}): removed ${o ? 'in theirs' : 'here'}, changed ${o ? 'here' : 'in theirs'}`,\n ours: o ? kept.edge : undefined,\n theirs: o ? undefined : kept.edge,\n });\n if ((side === 'ours') === !!o) edges.set(id, { ...kept, from: o ? 'ours' : 'theirs' });\n continue;\n }\n if (!o || !t) {\n edges.set(id, { ...((o ?? t) as FlatEdge), from: o ? 'ours' : 'theirs' });\n continue;\n }\n const merged = m.value(\n b,\n o,\n t,\n {\n id: `wire:${id}`,\n label: `The wire from ${titleOf(o.edge.from.node)} (${o.edge.from.port})`,\n field: '',\n owner: { edge: id },\n },\n 0,\n ) as FlatEdge;\n edges.set(id, { ...merged, from: 'both' });\n }\n // Wires whose ends are gone or now on another level go; the same wire added twice is one.\n const ends = new Set<string>();\n for (const [id, e] of edges) {\n const a = nodes.get(e.edge.from.node);\n const z = nodes.get(e.edge.to.node);\n const key = `${e.level}|${e.edge.from.node}.${e.edge.from.port}>${e.edge.to.node}.${e.edge.to.port}`;\n if (!a || !z || a.level !== e.level || z.level !== e.level || ends.has(key)) edges.delete(id);\n else ends.add(key);\n }\n // One wire per output port: two from different sides are a conflict.\n const byPort = new Map<string, Ulid[]>();\n for (const [id, e] of edges) {\n const key = `${e.edge.from.node}|${e.edge.from.port}`;\n byPort.set(key, [...(byPort.get(key) ?? []), id]);\n }\n for (const [key, ids] of byPort) {\n if (ids.length < 2) continue;\n const sides = new Set(ids.map((id) => edges.get(id)?.from));\n if (!sides.has('ours') && !sides.has('theirs')) continue;\n const [node = '', port = ''] = key.split('|');\n const target = (s: MergeSide) =>\n ids\n .filter((id) => (s === 'ours' ? ours : theirs).edges.has(id))\n .map((id) => titleOf(edges.get(id)?.edge.to.node ?? ''))\n .join(', ');\n const side = m.conflict({\n id: `port:${node}.${port}`,\n kind: 'wire',\n node,\n label: `${titleOf(node)}: port ${port} wired to ${target('ours') || 'nothing'} here, to ${target('theirs') || 'nothing'} in theirs`,\n field: `ports.${port}`,\n ours: target('ours'),\n theirs: target('theirs'),\n });\n const keep = side === 'ours' ? ours : theirs;\n for (const id of ids) if (!keep.edges.has(id)) edges.delete(id);\n }\n\n // Notes, Sections and Text.\n const annotations = new Map<Ulid, FlatAnnotation>();\n const PLACE = ['x', 'y', 'width', 'height'];\n for (const id of order('annotations')) {\n const b = base.annotations.get(id);\n const o = ours.annotations.get(id);\n const t = theirs.annotations.get(id);\n if (!o && !t) continue;\n if (b && (!o || !t)) {\n const kept = (o ?? t) as FlatAnnotation;\n if (same(kept, b)) continue;\n const side = m.conflict({\n id: `note:${id}`,\n kind: 'removed',\n annotation: id,\n label: `The note \"${kept.annotation.text.split('\\n')[0]}\": removed ${o ? 'in theirs' : 'here'}, changed ${o ? 'here' : 'in theirs'}`,\n ours: o ? 'changed' : 'removed',\n theirs: o ? 'removed' : 'changed',\n });\n if ((side === 'ours') === !!o) annotations.set(id, kept);\n continue;\n }\n if (!o || !t) {\n annotations.set(id, (o ?? t) as FlatAnnotation);\n continue;\n }\n const strip = (a?: Annotation) =>\n a && Object.fromEntries(Object.entries(a).filter(([k]) => !PLACE.includes(k)));\n const label = `The note \"${o.annotation.text.split('\\n')[0]}\"`;\n const fields = m.fields(\n strip(b?.annotation),\n strip(o.annotation) as Record<string, unknown>,\n strip(t.annotation) as Record<string, unknown>,\n { id: `note:${id}`, label, owner: { annotation: id } },\n 1,\n );\n const place: Record<string, unknown> = {};\n for (const k of PLACE) {\n const v = quiet(\n (b?.annotation as unknown as Record<string, unknown> | undefined)?.[k],\n (o.annotation as unknown as Record<string, unknown>)[k],\n (t.annotation as unknown as Record<string, unknown>)[k],\n );\n if (v !== undefined) place[k] = v;\n }\n const level = quiet(b?.level, o.level, t.level) ?? o.level;\n annotations.set(id, { level, annotation: { ...fields, ...place } as unknown as Annotation });\n }\n for (const [id, a] of annotations)\n if (\n (a.level !== ROOT && !nodes.get(a.level)?.body) ||\n (a.annotation.on && !nodes.has(a.annotation.on))\n )\n annotations.delete(id);\n\n // The flow itself: its fields; its version moves on when both sides moved it.\n const { version: vb, ...hb } = base.doc.flow;\n const { version: vo, ...ho } = ours.doc.flow;\n const { version: vt, ...ht } = theirs.doc.flow;\n const header = m.fields(\n hb as Record<string, unknown>,\n ho as Record<string, unknown>,\n ht as Record<string, unknown>,\n { id: 'flow', label: 'The flow', owner: {} },\n 2,\n ) as Omit<FlowHeader, 'version'>;\n const version = vo === vt ? vo : vo === vb ? vt : vt === vb ? vo : Math.max(vo, vt) + 1;\n\n // Decisions: a set; what a side added is in, what a side took out is out.\n const ident = (d: Decision) => JSON.stringify(canonical(d));\n const baseDecisions = new Set((base.doc.decisions ?? []).map(ident));\n const theirsDecisions = new Set((theirs.doc.decisions ?? []).map(ident));\n const decisions = [\n ...(ours.doc.decisions ?? []).filter(\n (d) => !baseDecisions.has(ident(d)) || theirsDecisions.has(ident(d)),\n ),\n ...(theirs.doc.decisions ?? []).filter(\n (d) =>\n !baseDecisions.has(ident(d)) &&\n !(ours.doc.decisions ?? []).some((x) => ident(x) === ident(d)),\n ),\n ];\n\n // Put it back together, level by level, in ours's order with theirs's new parts after.\n const level = (id: string) => {\n const levelNodes: FlowNode[] = [];\n const layout: Record<string, Position> = {};\n for (const [nid, n] of nodes) {\n if (n.level !== id) continue;\n const node = { ...n.own } as FlowNode;\n if (n.body) {\n const inner = level(nid);\n const keys = new Set(n.body.keys);\n const body: SubflowBody = { nodes: inner.nodes, edges: inner.edges };\n if (inner.annotations.length || keys.has('annotations'))\n body.annotations = inner.annotations;\n if (Object.keys(inner.layout).length || keys.has('layout')) body.layout = inner.layout;\n if (n.body.boundary) body.boundary = n.body.boundary;\n node.subflow = body;\n }\n levelNodes.push(node);\n if (n.position) layout[nid] = n.position;\n }\n return {\n nodes: levelNodes,\n edges: [...edges.values()].filter((e) => e.level === id).map((e) => e.edge),\n annotations: [...annotations.values()].filter((a) => a.level === id).map((a) => a.annotation),\n layout,\n };\n };\n const top = level(ROOT);\n const keys = new Set([...ours.rootKeys, ...theirs.rootKeys]);\n const doc: FlowDocument = {\n schema_version: oursDoc.schema_version,\n flow: { ...header, version } as FlowHeader,\n nodes: top.nodes,\n edges: top.edges,\n ...(top.annotations.length || keys.has('annotations') ? { annotations: top.annotations } : {}),\n ...(Object.keys(top.layout).length || keys.has('layout') ? { layout: top.layout } : {}),\n ...(decisions.length || keys.has('decisions') ? { decisions } : {}),\n };\n return { doc, conflicts: m.conflicts };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["level", "keys"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonical
|
|
3
|
+
} from "../chunks/chunk-TG3C3GIL.js";
|
|
4
|
+
import "../chunks/chunk-VG3RKETK.js";
|
|
5
|
+
import {
|
|
6
|
+
YamlTransaction
|
|
7
|
+
} from "../chunks/chunk-3Z2U3LN7.js";
|
|
8
|
+
import "../chunks/chunk-GHG3VINX.js";
|
|
9
|
+
import {
|
|
10
|
+
deleteKey,
|
|
11
|
+
insertItem,
|
|
12
|
+
removeItem,
|
|
13
|
+
setIn
|
|
14
|
+
} from "../chunks/chunk-5TUAMVIV.js";
|
|
15
|
+
import {
|
|
16
|
+
parseYaml
|
|
17
|
+
} from "../chunks/chunk-45CE2RUH.js";
|
|
18
|
+
import "../chunks/chunk-E2YZJWO7.js";
|
|
19
|
+
import "../chunks/chunk-AGWQ3ANE.js";
|
|
20
|
+
|
|
21
|
+
// src/core/flow/mergeText.ts
|
|
22
|
+
var same = (a, b) => JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
|
|
23
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
24
|
+
var idOf = (v) => isRecord(v) && typeof v.id === "string" ? v.id : void 0;
|
|
25
|
+
var byId = (list) => list.length > 0 && list.every((v) => idOf(v) !== void 0);
|
|
26
|
+
function writeMergedFlow(text, doc) {
|
|
27
|
+
const tx = new YamlTransaction(text);
|
|
28
|
+
const current = parseYaml(text).doc.toJS();
|
|
29
|
+
sync(tx, [], current, doc);
|
|
30
|
+
return tx.text;
|
|
31
|
+
}
|
|
32
|
+
function sync(tx, path, was, now) {
|
|
33
|
+
if (same(was, now)) return;
|
|
34
|
+
if (isRecord(was) && isRecord(now)) {
|
|
35
|
+
for (const key of Object.keys(was))
|
|
36
|
+
if (!(key in now) || now[key] === void 0) tx.apply((f) => deleteKey(f, path, key));
|
|
37
|
+
for (const [key, value] of Object.entries(now)) {
|
|
38
|
+
if (value === void 0) continue;
|
|
39
|
+
if (key in was) sync(tx, [...path, key], was[key], value);
|
|
40
|
+
else tx.apply((f) => setIn(f, [...path, key], value));
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(was) && Array.isArray(now) && byId(was) && byId(now)) {
|
|
45
|
+
const wanted = new Set(now.map(idOf));
|
|
46
|
+
const list = [...was];
|
|
47
|
+
for (let i = list.length - 1; i >= 0; i--)
|
|
48
|
+
if (!wanted.has(idOf(list[i]))) {
|
|
49
|
+
tx.apply((f) => removeItem(f, path, i));
|
|
50
|
+
list.splice(i, 1);
|
|
51
|
+
}
|
|
52
|
+
now.forEach((item, i) => {
|
|
53
|
+
const at = list.findIndex((x) => idOf(x) === idOf(item));
|
|
54
|
+
if (at >= 0) {
|
|
55
|
+
sync(tx, [...path, at], list[at], item);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const index = Math.min(i, list.length);
|
|
59
|
+
tx.apply(
|
|
60
|
+
(f) => insertItem(f, path, index === list.length ? "end" : index, item)
|
|
61
|
+
);
|
|
62
|
+
list.splice(index, 0, item);
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
tx.apply((f) => setIn(f, path, now));
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
writeMergedFlow
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=mergeText.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/core/flow/mergeText.ts"],
|
|
4
|
+
"sourcesContent": ["// AFD-283: a merged flow written over \"ours\" text with the fewest edits, so its comments, quoting\n// and order stay where they were and the file's diff shows only what the merge brought in. Lists\n// of things with IDs (steps, wires, notes) are matched by ID; anything else changed is replaced.\n\nimport { deleteKey, insertItem, removeItem, setIn } from '../yaml/ops';\nimport { type Path, parseYaml } from '../yaml/parse';\nimport type { YamlValue } from '../yaml/render';\nimport { YamlTransaction } from '../yaml/transaction';\nimport { canonical } from './semantic';\nimport type { FlowDocument } from './types';\n\nconst same = (a: unknown, b: unknown) =>\n JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\nconst idOf = (v: unknown) => (isRecord(v) && typeof v.id === 'string' ? v.id : undefined);\nconst byId = (list: unknown[]) => list.length > 0 && list.every((v) => idOf(v) !== undefined);\n\n/** `text` (ours) rewritten to say `doc`. */\nexport function writeMergedFlow(text: string, doc: FlowDocument): string {\n const tx = new YamlTransaction(text);\n const current = parseYaml(text).doc.toJS() as unknown;\n sync(tx, [], current, doc);\n return tx.text;\n}\n\nfunction sync(tx: YamlTransaction, path: Path, was: unknown, now: unknown): void {\n if (same(was, now)) return;\n if (isRecord(was) && isRecord(now)) {\n for (const key of Object.keys(was))\n if (!(key in now) || now[key] === undefined) tx.apply((f) => deleteKey(f, path, key));\n for (const [key, value] of Object.entries(now)) {\n if (value === undefined) continue;\n if (key in was) sync(tx, [...path, key], was[key], value);\n else tx.apply((f) => setIn(f, [...path, key], value as YamlValue));\n }\n return;\n }\n if (Array.isArray(was) && Array.isArray(now) && byId(was) && byId(now)) {\n const wanted = new Set(now.map(idOf));\n // Removals first, from the end, so the earlier indexes hold.\n const list = [...was];\n for (let i = list.length - 1; i >= 0; i--)\n if (!wanted.has(idOf(list[i]))) {\n tx.apply((f) => removeItem(f, path, i));\n list.splice(i, 1);\n }\n now.forEach((item, i) => {\n const at = list.findIndex((x) => idOf(x) === idOf(item));\n if (at >= 0) {\n sync(tx, [...path, at], list[at], item);\n return;\n }\n const index = Math.min(i, list.length);\n tx.apply((f) =>\n insertItem(f, path, index === list.length ? 'end' : index, item as YamlValue),\n );\n list.splice(index, 0, item);\n });\n return;\n }\n tx.apply((f) => setIn(f, path, now as YamlValue));\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;AAWA,IAAM,OAAO,CAAC,GAAY,MACxB,KAAK,UAAU,UAAU,CAAC,CAAC,MAAM,KAAK,UAAU,UAAU,CAAC,CAAC;AAC9D,IAAM,WAAW,CAAC,MAChB,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AACzD,IAAM,OAAO,CAAC,MAAgB,SAAS,CAAC,KAAK,OAAO,EAAE,OAAO,WAAW,EAAE,KAAK;AAC/E,IAAM,OAAO,CAAC,SAAoB,KAAK,SAAS,KAAK,KAAK,MAAM,CAAC,MAAM,KAAK,CAAC,MAAM,MAAS;AAGrF,SAAS,gBAAgB,MAAc,KAA2B;AACvE,QAAM,KAAK,IAAI,gBAAgB,IAAI;AACnC,QAAM,UAAU,UAAU,IAAI,EAAE,IAAI,KAAK;AACzC,OAAK,IAAI,CAAC,GAAG,SAAS,GAAG;AACzB,SAAO,GAAG;AACZ;AAEA,SAAS,KAAK,IAAqB,MAAY,KAAc,KAAoB;AAC/E,MAAI,KAAK,KAAK,GAAG,EAAG;AACpB,MAAI,SAAS,GAAG,KAAK,SAAS,GAAG,GAAG;AAClC,eAAW,OAAO,OAAO,KAAK,GAAG;AAC/B,UAAI,EAAE,OAAO,QAAQ,IAAI,GAAG,MAAM,OAAW,IAAG,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC;AACtF,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,UAAI,UAAU,OAAW;AACzB,UAAI,OAAO,IAAK,MAAK,IAAI,CAAC,GAAG,MAAM,GAAG,GAAG,IAAI,GAAG,GAAG,KAAK;AAAA,UACnD,IAAG,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,GAAG,KAAkB,CAAC;AAAA,IACnE;AACA;AAAA,EACF;AACA,MAAI,MAAM,QAAQ,GAAG,KAAK,MAAM,QAAQ,GAAG,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,GAAG;AACtE,UAAM,SAAS,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;AAEpC,UAAM,OAAO,CAAC,GAAG,GAAG;AACpB,aAAS,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG;AACpC,UAAI,CAAC,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG;AAC9B,WAAG,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC;AACtC,aAAK,OAAO,GAAG,CAAC;AAAA,MAClB;AACF,QAAI,QAAQ,CAAC,MAAM,MAAM;AACvB,YAAM,KAAK,KAAK,UAAU,CAAC,MAAM,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC;AACvD,UAAI,MAAM,GAAG;AACX,aAAK,IAAI,CAAC,GAAG,MAAM,EAAE,GAAG,KAAK,EAAE,GAAG,IAAI;AACtC;AAAA,MACF;AACA,YAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM;AACrC,SAAG;AAAA,QAAM,CAAC,MACR,WAAW,GAAG,MAAM,UAAU,KAAK,SAAS,QAAQ,OAAO,IAAiB;AAAA,MAC9E;AACA,WAAK,OAAO,OAAO,GAAG,IAAI;AAAA,IAC5B,CAAC;AACD;AAAA,EACF;AACA,KAAG,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,GAAgB,CAAC;AAClD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** What a typed line means: a list or map in YAML flow syntax, else its text. */
|
|
2
|
+
export declare function valueOfText(text: string): unknown;
|
|
3
|
+
/** The same value, whatever the text: a field keeps what was typed while it means this. */
|
|
4
|
+
export declare function sameValue(a: unknown, b: unknown): boolean;
|
|
5
|
+
/** The line that means `value` again in `valueOfText`, or undefined when it needs several lines. */
|
|
6
|
+
export declare function textOfValue(value: unknown): string | undefined;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/core/flow/valueText.ts
|
|
2
|
+
import { parse, stringify } from "yaml";
|
|
3
|
+
var TEMPLATE = /^\s*\{[{%]/;
|
|
4
|
+
var QUOTED = /^(".*"|'.*')$/s;
|
|
5
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
6
|
+
function yaml(text) {
|
|
7
|
+
try {
|
|
8
|
+
return parse(text);
|
|
9
|
+
} catch {
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function valueOfText(text) {
|
|
14
|
+
const t = text.trim();
|
|
15
|
+
if (TEMPLATE.test(t)) return text;
|
|
16
|
+
if (t.startsWith("[") || t.startsWith("{")) {
|
|
17
|
+
const v = yaml(t);
|
|
18
|
+
if (Array.isArray(v) || isRecord(v)) return v;
|
|
19
|
+
return text;
|
|
20
|
+
}
|
|
21
|
+
if (QUOTED.test(t)) {
|
|
22
|
+
const v = yaml(t);
|
|
23
|
+
if (typeof v === "string" && needsQuotes(v.trim())) return v;
|
|
24
|
+
}
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
27
|
+
function needsQuotes(t) {
|
|
28
|
+
if (QUOTED.test(t)) return /^.\s*[[{]/.test(t);
|
|
29
|
+
if (TEMPLATE.test(t) || !/^[[{]/.test(t)) return false;
|
|
30
|
+
const v = yaml(t);
|
|
31
|
+
return Array.isArray(v) || isRecord(v);
|
|
32
|
+
}
|
|
33
|
+
function sameValue(a, b) {
|
|
34
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
35
|
+
}
|
|
36
|
+
function textOfValue(value) {
|
|
37
|
+
if (value === void 0 || value === null) return "";
|
|
38
|
+
if (typeof value === "string") {
|
|
39
|
+
return needsQuotes(value.trim()) ? JSON.stringify(value) : value;
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(value) || isRecord(value)) {
|
|
42
|
+
const line = stringify(value, {
|
|
43
|
+
collectionStyle: "flow",
|
|
44
|
+
flowCollectionPadding: false,
|
|
45
|
+
lineWidth: 0
|
|
46
|
+
}).trim();
|
|
47
|
+
return line.includes("\n") ? void 0 : line;
|
|
48
|
+
}
|
|
49
|
+
return String(value);
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
sameValue,
|
|
53
|
+
textOfValue,
|
|
54
|
+
valueOfText
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=valueText.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/core/flow/valueText.ts"],
|
|
4
|
+
"sourcesContent": ["// AFD-286: a value typed in a one-line field, read the way YAML (and so Ansible) reads it, and\n// shown back so it reads the same again. Unquoted `[a, b]` is a list and `{k: v}` a map; a quoted\n// value is the text inside the quotes; `{{ ... }}` and `{% ... %}` stay templates. Anything else\n// is text, as the field always kept it (numbers too: Ansible converts where it needs).\nimport { parse, stringify } from 'yaml';\n\nconst TEMPLATE = /^\\s*\\{[{%]/;\nconst QUOTED = /^(\".*\"|'.*')$/s;\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\n\nfunction yaml(text: string): unknown {\n try {\n return parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** What a typed line means: a list or map in YAML flow syntax, else its text. */\nexport function valueOfText(text: string): unknown {\n const t = text.trim();\n if (TEMPLATE.test(t)) return text;\n if (t.startsWith('[') || t.startsWith('{')) {\n const v = yaml(t);\n if (Array.isArray(v) || isRecord(v)) return v;\n return text;\n }\n // A quoted list or map is the text inside the quotes: the way to keep \"[a, b]\" as text. Other\n // quotes stay as typed (they always did).\n if (QUOTED.test(t)) {\n const v = yaml(t);\n if (typeof v === 'string' && needsQuotes(v.trim())) return v;\n }\n return text;\n}\n\n/** Text that `valueOfText` would not give back as it is: quoted so it reads as text again. */\nfunction needsQuotes(t: string): boolean {\n if (QUOTED.test(t)) return /^.\\s*[[{]/.test(t);\n if (TEMPLATE.test(t) || !/^[[{]/.test(t)) return false;\n const v = yaml(t);\n return Array.isArray(v) || isRecord(v);\n}\n\n/** The same value, whatever the text: a field keeps what was typed while it means this. */\nexport function sameValue(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n/** The line that means `value` again in `valueOfText`, or undefined when it needs several lines. */\nexport function textOfValue(value: unknown): string | undefined {\n if (value === undefined || value === null) return '';\n if (typeof value === 'string') {\n return needsQuotes(value.trim()) ? JSON.stringify(value) : value;\n }\n if (Array.isArray(value) || isRecord(value)) {\n const line = stringify(value, {\n collectionStyle: 'flow',\n flowCollectionPadding: false,\n lineWidth: 0,\n }).trim();\n return line.includes('\\n') ? undefined : line;\n }\n return String(value);\n}\n"],
|
|
5
|
+
"mappings": ";AAIA,SAAS,OAAO,iBAAiB;AAEjC,IAAM,WAAW;AACjB,IAAM,SAAS;AACf,IAAM,WAAW,CAAC,MAChB,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAEzD,SAAS,KAAK,MAAuB;AACnC,MAAI;AACF,WAAO,MAAM,IAAI;AAAA,EACnB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,YAAY,MAAuB;AACjD,QAAM,IAAI,KAAK,KAAK;AACpB,MAAI,SAAS,KAAK,CAAC,EAAG,QAAO;AAC7B,MAAI,EAAE,WAAW,GAAG,KAAK,EAAE,WAAW,GAAG,GAAG;AAC1C,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,MAAM,QAAQ,CAAC,KAAK,SAAS,CAAC,EAAG,QAAO;AAC5C,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,KAAK,CAAC,GAAG;AAClB,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,OAAO,MAAM,YAAY,YAAY,EAAE,KAAK,CAAC,EAAG,QAAO;AAAA,EAC7D;AACA,SAAO;AACT;AAGA,SAAS,YAAY,GAAoB;AACvC,MAAI,OAAO,KAAK,CAAC,EAAG,QAAO,YAAY,KAAK,CAAC;AAC7C,MAAI,SAAS,KAAK,CAAC,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAG,QAAO;AACjD,QAAM,IAAI,KAAK,CAAC;AAChB,SAAO,MAAM,QAAQ,CAAC,KAAK,SAAS,CAAC;AACvC;AAGO,SAAS,UAAU,GAAY,GAAqB;AACzD,SAAO,KAAK,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC;AAC/C;AAGO,SAAS,YAAY,OAAoC;AAC9D,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,YAAY,MAAM,KAAK,CAAC,IAAI,KAAK,UAAU,KAAK,IAAI;AAAA,EAC7D;AACA,MAAI,MAAM,QAAQ,KAAK,KAAK,SAAS,KAAK,GAAG;AAC3C,UAAM,OAAO,UAAU,OAAO;AAAA,MAC5B,iBAAiB;AAAA,MACjB,uBAAuB;AAAA,MACvB,WAAW;AAAA,IACb,CAAC,EAAE,KAAK;AACR,WAAO,KAAK,SAAS,IAAI,IAAI,SAAY;AAAA,EAC3C;AACA,SAAO,OAAO,KAAK;AACrB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/import/outline.js
CHANGED
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
paged,
|
|
5
5
|
sourceOutline,
|
|
6
6
|
taskBinding
|
|
7
|
-
} from "../chunks/chunk-
|
|
8
|
-
import "../chunks/chunk-
|
|
7
|
+
} from "../chunks/chunk-TPRZZHXA.js";
|
|
8
|
+
import "../chunks/chunk-5XGAGNH5.js";
|
|
9
9
|
import "../chunks/chunk-IYVI5MSS.js";
|
|
10
|
-
import "../chunks/chunk-
|
|
10
|
+
import "../chunks/chunk-H7Z2FBRD.js";
|
|
11
|
+
import "../chunks/chunk-LNFMYJUJ.js";
|
|
11
12
|
import "../chunks/chunk-5CD65KH3.js";
|
|
12
13
|
import "../chunks/chunk-6L43PFIN.js";
|
|
13
|
-
import "../chunks/chunk-LNFMYJUJ.js";
|
|
14
14
|
import "../chunks/chunk-PDUQ3KMW.js";
|
|
15
15
|
import "../chunks/chunk-BAZIS67B.js";
|
|
16
16
|
import "../chunks/chunk-TC7FOG4T.js";
|
package/dist/import/playbook.js
CHANGED
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
flatPlays,
|
|
4
4
|
importPlaybook,
|
|
5
5
|
mainPlay
|
|
6
|
-
} from "../chunks/chunk-
|
|
6
|
+
} from "../chunks/chunk-H7Z2FBRD.js";
|
|
7
|
+
import "../chunks/chunk-LNFMYJUJ.js";
|
|
7
8
|
import "../chunks/chunk-5CD65KH3.js";
|
|
8
9
|
import "../chunks/chunk-6L43PFIN.js";
|
|
9
|
-
import "../chunks/chunk-LNFMYJUJ.js";
|
|
10
10
|
import "../chunks/chunk-PDUQ3KMW.js";
|
|
11
11
|
import "../chunks/chunk-BAZIS67B.js";
|
|
12
12
|
import "../chunks/chunk-TC7FOG4T.js";
|
package/dist/import/source.js
CHANGED
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
parseKv,
|
|
3
3
|
readPlaybook,
|
|
4
4
|
scanFolder
|
|
5
|
-
} from "../chunks/chunk-
|
|
6
|
-
import "../chunks/chunk-6L43PFIN.js";
|
|
5
|
+
} from "../chunks/chunk-5XGAGNH5.js";
|
|
7
6
|
import "../chunks/chunk-LNFMYJUJ.js";
|
|
7
|
+
import "../chunks/chunk-6L43PFIN.js";
|
|
8
8
|
import "../chunks/chunk-TC7FOG4T.js";
|
|
9
9
|
import "../chunks/chunk-OEQXGULI.js";
|
|
10
10
|
import "../chunks/chunk-VHCA4A7K.js";
|
package/dist/lens/index.js
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
dataLens
|
|
3
|
-
} from "../chunks/chunk-BXHI5EQM.js";
|
|
4
1
|
import {
|
|
5
2
|
timingLens
|
|
6
3
|
} from "../chunks/chunk-VEYR53BH.js";
|
|
7
|
-
import {
|
|
8
|
-
accessLens
|
|
9
|
-
} from "../chunks/chunk-WS52COFG.js";
|
|
10
|
-
import {
|
|
11
|
-
declaredTarget,
|
|
12
|
-
inventoryLens
|
|
13
|
-
} from "../chunks/chunk-KWEFZFLC.js";
|
|
14
4
|
import {
|
|
15
5
|
LENSES
|
|
16
6
|
} from "../chunks/chunk-XOPGOWNQ.js";
|
|
@@ -20,6 +10,16 @@ import {
|
|
|
20
10
|
import {
|
|
21
11
|
codeLens
|
|
22
12
|
} from "../chunks/chunk-4N3AN5CJ.js";
|
|
13
|
+
import {
|
|
14
|
+
dataLens
|
|
15
|
+
} from "../chunks/chunk-BXHI5EQM.js";
|
|
16
|
+
import {
|
|
17
|
+
accessLens
|
|
18
|
+
} from "../chunks/chunk-WS52COFG.js";
|
|
19
|
+
import {
|
|
20
|
+
declaredTarget,
|
|
21
|
+
inventoryLens
|
|
22
|
+
} from "../chunks/chunk-KWEFZFLC.js";
|
|
23
23
|
import {
|
|
24
24
|
incoming,
|
|
25
25
|
title
|
package/dist/messageCheck.js
CHANGED
|
@@ -35,6 +35,8 @@ function messageProblem(m) {
|
|
|
35
35
|
return isMap(m.request) ? void 0 : "scenarioEdit: bad request";
|
|
36
36
|
case "runScenarios":
|
|
37
37
|
return strList(m.names) ? void 0 : "runScenarios: bad names";
|
|
38
|
+
case "mergeChoose":
|
|
39
|
+
return str(m.id, 500) && (m.side === "ours" || m.side === "theirs") ? void 0 : "mergeChoose: bad choice";
|
|
38
40
|
case "lens":
|
|
39
41
|
return m.lens === null || str(m.lens, 64) ? void 0 : "lens: bad lens";
|
|
40
42
|
default:
|
package/dist/messageCheck.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/core/messageCheck.ts"],
|
|
4
|
-
"sourcesContent": ["// AFD-165: the host checks what a designer webview sends before acting on it. The webview runs\n// under a strict CSP with no HTML sinks, so a compromised page is unlikely; these checks keep one\n// from turning a message into a git option, a file outside the workspace or a probe argument.\n// Shape only: what a value means (a known ref, a path inside the folder) is checked where it is\n// used.\n\nconst isMap = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === 'object' && !Array.isArray(v);\n\nconst str = (v: unknown, max = 4096): v is string => typeof v === 'string' && v.length <= max;\nconst optNum = (v: unknown) => v === undefined || (typeof v === 'number' && Number.isFinite(v));\nconst strList = (v: unknown) =>\n v === undefined || (Array.isArray(v) && v.every((x) => str(x, 200)));\n\n/** A plugin name the probe looks up: dotted words, no option or path syntax. */\nexport const PLUGIN_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.]{0,199}$/;\n\n/** A git revision a designer may ask to compare with: never an option (`-\u2026`), no spaces. */\nexport const GIT_REF = /^[A-Za-z0-9_][A-Za-z0-9_./@{}^~-]{0,199}$/;\n\nconst CATALOG_OPS = new Set(['search', 'doc', 'roles', 'playbooks', 'rebuild']);\n\nfunction catalogProblem(r: unknown): string | undefined {\n if (!isMap(r) || !CATALOG_OPS.has(String(r.op))) return 'catalog: unknown request';\n if (r.op === 'search' && (!str(r.query, 500) || !strList(r.types))) return 'catalog: bad search';\n if (r.op === 'doc' && (!str(r.fqcn) || !PLUGIN_NAME.test(r.fqcn)))\n return 'catalog: not a plugin name';\n return undefined;\n}\n\n/** Why a message from the webview is refused, or undefined when its shape is right. */\nexport function messageProblem(m: unknown): string | undefined {\n if (!isMap(m) || !str(m.type, 64)) return 'not a designer message';\n if ('reqId' in m && !optNum(m.reqId)) return `${m.type}: bad reqId`;\n switch (m.type) {\n case 'openCode':\n return str(m.path) && m.path !== '' && optNum(m.line) ? undefined : 'openCode: bad path';\n case 'openText':\n return m.path === undefined ||\n (Array.isArray(m.path) &&\n m.path.every((p) => str(p, 1000) || (typeof p === 'number' && Number.isInteger(p))))\n ? undefined\n : 'openText: bad path';\n case 'compareWith':\n return str(m.ref, 200) && GIT_REF.test(m.ref) ? undefined : 'compareWith: bad ref';\n case 'catalog':\n return catalogProblem(m.request);\n case 'openLink':\n return str(m.url) ? undefined : 'openLink: bad url';\n case 'copyText':\n return str(m.text, 10_000_000) && optNum(m.count) ? undefined : 'copyText: bad text';\n case 'command':\n return isMap(m.command) && str(m.command.kind, 64) ? undefined : 'command: bad command';\n case 'scenarioEdit':\n return isMap(m.request) ? undefined : 'scenarioEdit: bad request';\n case 'runScenarios':\n return strList(m.names) ? undefined : 'runScenarios: bad names';\n case 'lens':\n return m.lens === null || str(m.lens, 64) ? undefined : 'lens: bad lens';\n default:\n return undefined;\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAMA,IAAM,QAAQ,CAAC,MACb,CAAC,CAAC,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AAElD,IAAM,MAAM,CAAC,GAAY,MAAM,SAAsB,OAAO,MAAM,YAAY,EAAE,UAAU;AAC1F,IAAM,SAAS,CAAC,MAAe,MAAM,UAAc,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AAC7F,IAAM,UAAU,CAAC,MACf,MAAM,UAAc,MAAM,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,CAAC;AAG7D,IAAM,cAAc;AAGpB,IAAM,UAAU;AAEvB,IAAM,cAAc,oBAAI,IAAI,CAAC,UAAU,OAAO,SAAS,aAAa,SAAS,CAAC;AAE9E,SAAS,eAAe,GAAgC;AACtD,MAAI,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,OAAO,EAAE,EAAE,CAAC,EAAG,QAAO;AACxD,MAAI,EAAE,OAAO,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC,QAAQ,EAAE,KAAK,GAAI,QAAO;AAC3E,MAAI,EAAE,OAAO,UAAU,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,YAAY,KAAK,EAAE,IAAI;AAC7D,WAAO;AACT,SAAO;AACT;AAGO,SAAS,eAAe,GAAgC;AAC7D,MAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAG,QAAO;AAC1C,MAAI,WAAW,KAAK,CAAC,OAAO,EAAE,KAAK,EAAG,QAAO,GAAG,EAAE,IAAI;AACtD,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AACH,aAAO,IAAI,EAAE,IAAI,KAAK,EAAE,SAAS,MAAM,OAAO,EAAE,IAAI,IAAI,SAAY;AAAA,IACtE,KAAK;AACH,aAAO,EAAE,SAAS,UACf,MAAM,QAAQ,EAAE,IAAI,KACnB,EAAE,KAAK,MAAM,CAAC,MAAM,IAAI,GAAG,GAAI,KAAM,OAAO,MAAM,YAAY,OAAO,UAAU,CAAC,CAAE,IAClF,SACA;AAAA,IACN,KAAK;AACH,aAAO,IAAI,EAAE,KAAK,GAAG,KAAK,QAAQ,KAAK,EAAE,GAAG,IAAI,SAAY;AAAA,IAC9D,KAAK;AACH,aAAO,eAAe,EAAE,OAAO;AAAA,IACjC,KAAK;AACH,aAAO,IAAI,EAAE,GAAG,IAAI,SAAY;AAAA,IAClC,KAAK;AACH,aAAO,IAAI,EAAE,MAAM,GAAU,KAAK,OAAO,EAAE,KAAK,IAAI,SAAY;AAAA,IAClE,KAAK;AACH,aAAO,MAAM,EAAE,OAAO,KAAK,IAAI,EAAE,QAAQ,MAAM,EAAE,IAAI,SAAY;AAAA,IACnE,KAAK;AACH,aAAO,MAAM,EAAE,OAAO,IAAI,SAAY;AAAA,IACxC,KAAK;AACH,aAAO,QAAQ,EAAE,KAAK,IAAI,SAAY;AAAA,IACxC,KAAK;AACH,aAAO,EAAE,SAAS,QAAQ,IAAI,EAAE,MAAM,EAAE,IAAI,SAAY;AAAA,IAC1D;AACE,aAAO;AAAA,EACX;AACF;",
|
|
4
|
+
"sourcesContent": ["// AFD-165: the host checks what a designer webview sends before acting on it. The webview runs\n// under a strict CSP with no HTML sinks, so a compromised page is unlikely; these checks keep one\n// from turning a message into a git option, a file outside the workspace or a probe argument.\n// Shape only: what a value means (a known ref, a path inside the folder) is checked where it is\n// used.\n\nconst isMap = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === 'object' && !Array.isArray(v);\n\nconst str = (v: unknown, max = 4096): v is string => typeof v === 'string' && v.length <= max;\nconst optNum = (v: unknown) => v === undefined || (typeof v === 'number' && Number.isFinite(v));\nconst strList = (v: unknown) =>\n v === undefined || (Array.isArray(v) && v.every((x) => str(x, 200)));\n\n/** A plugin name the probe looks up: dotted words, no option or path syntax. */\nexport const PLUGIN_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.]{0,199}$/;\n\n/** A git revision a designer may ask to compare with: never an option (`-\u2026`), no spaces. */\nexport const GIT_REF = /^[A-Za-z0-9_][A-Za-z0-9_./@{}^~-]{0,199}$/;\n\nconst CATALOG_OPS = new Set(['search', 'doc', 'roles', 'playbooks', 'rebuild']);\n\nfunction catalogProblem(r: unknown): string | undefined {\n if (!isMap(r) || !CATALOG_OPS.has(String(r.op))) return 'catalog: unknown request';\n if (r.op === 'search' && (!str(r.query, 500) || !strList(r.types))) return 'catalog: bad search';\n if (r.op === 'doc' && (!str(r.fqcn) || !PLUGIN_NAME.test(r.fqcn)))\n return 'catalog: not a plugin name';\n return undefined;\n}\n\n/** Why a message from the webview is refused, or undefined when its shape is right. */\nexport function messageProblem(m: unknown): string | undefined {\n if (!isMap(m) || !str(m.type, 64)) return 'not a designer message';\n if ('reqId' in m && !optNum(m.reqId)) return `${m.type}: bad reqId`;\n switch (m.type) {\n case 'openCode':\n return str(m.path) && m.path !== '' && optNum(m.line) ? undefined : 'openCode: bad path';\n case 'openText':\n return m.path === undefined ||\n (Array.isArray(m.path) &&\n m.path.every((p) => str(p, 1000) || (typeof p === 'number' && Number.isInteger(p))))\n ? undefined\n : 'openText: bad path';\n case 'compareWith':\n return str(m.ref, 200) && GIT_REF.test(m.ref) ? undefined : 'compareWith: bad ref';\n case 'catalog':\n return catalogProblem(m.request);\n case 'openLink':\n return str(m.url) ? undefined : 'openLink: bad url';\n case 'copyText':\n return str(m.text, 10_000_000) && optNum(m.count) ? undefined : 'copyText: bad text';\n case 'command':\n return isMap(m.command) && str(m.command.kind, 64) ? undefined : 'command: bad command';\n case 'scenarioEdit':\n return isMap(m.request) ? undefined : 'scenarioEdit: bad request';\n case 'runScenarios':\n return strList(m.names) ? undefined : 'runScenarios: bad names';\n case 'mergeChoose':\n return str(m.id, 500) && (m.side === 'ours' || m.side === 'theirs')\n ? undefined\n : 'mergeChoose: bad choice';\n case 'lens':\n return m.lens === null || str(m.lens, 64) ? undefined : 'lens: bad lens';\n default:\n return undefined;\n }\n}\n"],
|
|
5
|
+
"mappings": ";AAMA,IAAM,QAAQ,CAAC,MACb,CAAC,CAAC,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AAElD,IAAM,MAAM,CAAC,GAAY,MAAM,SAAsB,OAAO,MAAM,YAAY,EAAE,UAAU;AAC1F,IAAM,SAAS,CAAC,MAAe,MAAM,UAAc,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AAC7F,IAAM,UAAU,CAAC,MACf,MAAM,UAAc,MAAM,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,CAAC;AAG7D,IAAM,cAAc;AAGpB,IAAM,UAAU;AAEvB,IAAM,cAAc,oBAAI,IAAI,CAAC,UAAU,OAAO,SAAS,aAAa,SAAS,CAAC;AAE9E,SAAS,eAAe,GAAgC;AACtD,MAAI,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,OAAO,EAAE,EAAE,CAAC,EAAG,QAAO;AACxD,MAAI,EAAE,OAAO,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC,QAAQ,EAAE,KAAK,GAAI,QAAO;AAC3E,MAAI,EAAE,OAAO,UAAU,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,YAAY,KAAK,EAAE,IAAI;AAC7D,WAAO;AACT,SAAO;AACT;AAGO,SAAS,eAAe,GAAgC;AAC7D,MAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAG,QAAO;AAC1C,MAAI,WAAW,KAAK,CAAC,OAAO,EAAE,KAAK,EAAG,QAAO,GAAG,EAAE,IAAI;AACtD,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AACH,aAAO,IAAI,EAAE,IAAI,KAAK,EAAE,SAAS,MAAM,OAAO,EAAE,IAAI,IAAI,SAAY;AAAA,IACtE,KAAK;AACH,aAAO,EAAE,SAAS,UACf,MAAM,QAAQ,EAAE,IAAI,KACnB,EAAE,KAAK,MAAM,CAAC,MAAM,IAAI,GAAG,GAAI,KAAM,OAAO,MAAM,YAAY,OAAO,UAAU,CAAC,CAAE,IAClF,SACA;AAAA,IACN,KAAK;AACH,aAAO,IAAI,EAAE,KAAK,GAAG,KAAK,QAAQ,KAAK,EAAE,GAAG,IAAI,SAAY;AAAA,IAC9D,KAAK;AACH,aAAO,eAAe,EAAE,OAAO;AAAA,IACjC,KAAK;AACH,aAAO,IAAI,EAAE,GAAG,IAAI,SAAY;AAAA,IAClC,KAAK;AACH,aAAO,IAAI,EAAE,MAAM,GAAU,KAAK,OAAO,EAAE,KAAK,IAAI,SAAY;AAAA,IAClE,KAAK;AACH,aAAO,MAAM,EAAE,OAAO,KAAK,IAAI,EAAE,QAAQ,MAAM,EAAE,IAAI,SAAY;AAAA,IACnE,KAAK;AACH,aAAO,MAAM,EAAE,OAAO,IAAI,SAAY;AAAA,IACxC,KAAK;AACH,aAAO,QAAQ,EAAE,KAAK,IAAI,SAAY;AAAA,IACxC,KAAK;AACH,aAAO,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,SAAS,UAAU,EAAE,SAAS,YACtD,SACA;AAAA,IACN,KAAK;AACH,aAAO,EAAE,SAAS,QAAQ,IAAI,EAAE,MAAM,EAAE,IAAI,SAAY;AAAA,IAC1D;AACE,aAAO;AAAA,EACX;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CatalogOption, CatalogPlugin, PluginType, SearchHit } from './catalog/types';
|
|
2
2
|
import type { CodeScan, DriftAction } from './drift/types';
|
|
3
3
|
import type { FlowCommand } from './flow/commands';
|
|
4
|
+
import type { MergeConflict, MergeSide } from './flow/merge';
|
|
4
5
|
import type { OpenPort } from './flow/openPorts';
|
|
5
6
|
import type { FlowDocument, Position, Ulid } from './flow/types';
|
|
6
7
|
import type { Finding } from './flow/validate';
|
|
@@ -347,6 +348,11 @@ export type HostMessage = {
|
|
|
347
348
|
type: 'outsideMarks';
|
|
348
349
|
marks: OutsideMarks | null;
|
|
349
350
|
}
|
|
351
|
+
/** AFD-283: a merge of this flow waiting for choices (git left it to the designer); null: none. */
|
|
352
|
+
| {
|
|
353
|
+
type: 'mergeConflicts';
|
|
354
|
+
merge: MergeView | null;
|
|
355
|
+
}
|
|
350
356
|
/** AFD-93: the workspace's flows at a glance, for the flow panel's line. */
|
|
351
357
|
| {
|
|
352
358
|
type: 'repoState';
|
|
@@ -489,6 +495,16 @@ export type WebviewMessage = {
|
|
|
489
495
|
| {
|
|
490
496
|
type: 'clearOutside';
|
|
491
497
|
}
|
|
498
|
+
/** AFD-283: a side chosen for one conflict of the waiting merge. */
|
|
499
|
+
| {
|
|
500
|
+
type: 'mergeChoose';
|
|
501
|
+
id: string;
|
|
502
|
+
side: MergeSide;
|
|
503
|
+
}
|
|
504
|
+
/** AFD-283: the waiting merge is done: the file is taken as merged. */
|
|
505
|
+
| {
|
|
506
|
+
type: 'mergeFinish';
|
|
507
|
+
}
|
|
492
508
|
/** AFD-65: a link in an annotation; the host opens web links this flow's annotations hold. */
|
|
493
509
|
| {
|
|
494
510
|
type: 'openLink';
|
|
@@ -646,6 +662,8 @@ export interface DomSummary {
|
|
|
646
662
|
banner: number | null;
|
|
647
663
|
/** AFD-19: items the written-by-hand banner says need IDs (null: no banner). */
|
|
648
664
|
idGaps: number | null;
|
|
665
|
+
/** AFD-283: conflicts the Merge note lists (null: no note). */
|
|
666
|
+
merge?: number | null;
|
|
649
667
|
/** Inside a subflow: frame ports shown (enabled ones) and boundary wires drawn. */
|
|
650
668
|
frame: {
|
|
651
669
|
ports: string[];
|
|
@@ -666,3 +684,13 @@ export interface DomSummary {
|
|
|
666
684
|
card: string[];
|
|
667
685
|
};
|
|
668
686
|
}
|
|
687
|
+
/** AFD-283: a merge git left to the designer: its conflicts with what is chosen for each. */
|
|
688
|
+
export interface MergeView {
|
|
689
|
+
/** The file's path in the repository. */
|
|
690
|
+
file: string;
|
|
691
|
+
conflicts: (MergeConflict & {
|
|
692
|
+
chosen: MergeSide;
|
|
693
|
+
})[];
|
|
694
|
+
/** Rules the merged flow breaks that neither side broke. */
|
|
695
|
+
broken: string[];
|
|
696
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ansible-flow/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.0",
|
|
4
4
|
"description": "Ansible Flow Designer core: the flow model, commands, validation and playbook generation, for any JavaScript host.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/ChistokhinSV/ansible-flow-designer/blob/main/docs/packages.md",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|