@ansible-flow/node 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +9 -2
- package/dist/ai/agents.d.ts +34 -0
- package/dist/ai/agents.js +16 -0
- package/dist/ai/agents.js.map +7 -0
- package/dist/ai/hostConfig.d.ts +44 -0
- package/dist/ai/hostConfig.js +120 -0
- package/dist/ai/hostConfig.js.map +7 -0
- package/dist/ai/hosts.d.ts +28 -0
- package/dist/ai/hosts.js +9 -0
- package/dist/ai/hosts.js.map +7 -0
- package/dist/ai/writers.d.ts +124 -0
- package/dist/ai/writers.js +75 -0
- package/dist/ai/writers.js.map +7 -0
- package/dist/autoLayout.d.ts +38 -0
- package/dist/autoLayout.js +145 -0
- package/dist/autoLayout.js.map +7 -0
- package/dist/awx/awxWorkflow.d.ts +47 -0
- package/dist/awx/awxWorkflow.js +17 -0
- package/dist/awx/awxWorkflow.js.map +7 -0
- package/dist/awx/client.d.ts +77 -0
- package/dist/awx/client.js +22 -0
- package/dist/awx/client.js.map +7 -0
- package/dist/catalog/bindingCheck.d.ts +4 -0
- package/dist/catalog/bindingCheck.js +69 -0
- package/dist/catalog/bindingCheck.js.map +7 -0
- package/dist/catalog/catalogIndex.d.ts +52 -0
- package/dist/catalog/catalogIndex.js +13 -0
- package/dist/catalog/catalogIndex.js.map +7 -0
- package/dist/catalog/catalogWorker.d.ts +17 -0
- package/dist/catalog/catalogWorker.js +38 -0
- package/dist/catalog/catalogWorker.js.map +7 -0
- package/dist/catalog/dumpFiles.d.ts +3 -0
- package/dist/catalog/dumpFiles.js +19 -0
- package/dist/catalog/dumpFiles.js.map +7 -0
- package/dist/chunks/chunk-2MZQHIXP.js +614 -0
- package/dist/chunks/chunk-2MZQHIXP.js.map +7 -0
- package/dist/chunks/chunk-4FHPTGI4.js +52 -0
- package/dist/chunks/chunk-4FHPTGI4.js.map +7 -0
- package/dist/chunks/chunk-A2NMQGQE.js +189 -0
- package/dist/chunks/chunk-A2NMQGQE.js.map +7 -0
- package/dist/chunks/chunk-EPJKJBNW.js +181 -0
- package/dist/chunks/chunk-EPJKJBNW.js.map +7 -0
- package/dist/chunks/chunk-FG53MLUX.js +136 -0
- package/dist/chunks/chunk-FG53MLUX.js.map +7 -0
- package/dist/chunks/chunk-GAL5LW6Y.js +269 -0
- package/dist/chunks/chunk-GAL5LW6Y.js.map +7 -0
- package/dist/chunks/chunk-HPVT6I36.js +371 -0
- package/dist/chunks/chunk-HPVT6I36.js.map +7 -0
- package/dist/chunks/chunk-I6FIOP7G.js +48 -0
- package/dist/chunks/chunk-I6FIOP7G.js.map +7 -0
- package/dist/chunks/chunk-IIFP6UQR.js +44 -0
- package/dist/chunks/chunk-IIFP6UQR.js.map +7 -0
- package/dist/chunks/chunk-JDPN3LTT.js +18 -0
- package/dist/chunks/chunk-JDPN3LTT.js.map +7 -0
- package/dist/chunks/chunk-LGUG2Q7Z.js +191 -0
- package/dist/chunks/chunk-LGUG2Q7Z.js.map +7 -0
- package/dist/chunks/chunk-NTZRNEZM.js +25 -0
- package/dist/chunks/chunk-NTZRNEZM.js.map +7 -0
- package/dist/chunks/chunk-RETJPSKL.js +123 -0
- package/dist/chunks/chunk-RETJPSKL.js.map +7 -0
- package/dist/chunks/chunk-SSZNBJZQ.js +52 -0
- package/dist/chunks/chunk-SSZNBJZQ.js.map +7 -0
- package/dist/chunks/chunk-SUV3VHKR.js +52 -0
- package/dist/chunks/chunk-SUV3VHKR.js.map +7 -0
- package/dist/chunks/chunk-THNSGAKN.js +133 -0
- package/dist/chunks/chunk-THNSGAKN.js.map +7 -0
- package/dist/chunks/chunk-UNCR5JOZ.js +85 -0
- package/dist/chunks/chunk-UNCR5JOZ.js.map +7 -0
- package/dist/env/probeClient.d.ts +77 -0
- package/dist/env/probeClient.js +244 -0
- package/dist/env/probeClient.js.map +7 -0
- package/dist/env/userSettings.d.ts +33 -0
- package/dist/env/userSettings.js +101 -0
- package/dist/env/userSettings.js.map +7 -0
- package/dist/flowMerge.d.ts +35 -0
- package/dist/flowMerge.js +74 -0
- package/dist/flowMerge.js.map +7 -0
- package/dist/run/audit.d.ts +2 -0
- package/dist/run/audit.js +10 -0
- package/dist/run/audit.js.map +7 -0
- package/dist/run/exec.d.ts +14 -0
- package/dist/run/exec.js +7 -0
- package/dist/run/exec.js.map +7 -0
- package/dist/run/facts.d.ts +39 -0
- package/dist/run/facts.js +96 -0
- package/dist/run/facts.js.map +7 -0
- package/dist/run/ids.d.ts +2 -0
- package/dist/run/ids.js +8 -0
- package/dist/run/ids.js.map +7 -0
- package/dist/run/pastRuns.d.ts +9 -0
- package/dist/run/pastRuns.js +13 -0
- package/dist/run/pastRuns.js.map +7 -0
- package/dist/run/runFlow.d.ts +78 -0
- package/dist/run/runFlow.js +20 -0
- package/dist/run/runFlow.js.map +7 -0
- package/dist/run/segments.d.ts +43 -0
- package/dist/run/segments.js +157 -0
- package/dist/run/segments.js.map +7 -0
- package/dist/run/stop.d.ts +3 -0
- package/dist/run/stop.js +7 -0
- package/dist/run/stop.js.map +7 -0
- package/dist/run/storage.d.ts +16 -0
- package/dist/run/storage.js +15 -0
- package/dist/run/storage.js.map +7 -0
- package/dist/run/tail.d.ts +5 -0
- package/dist/run/tail.js +7 -0
- package/dist/run/tail.js.map +7 -0
- package/dist/runner/client.d.ts +149 -0
- package/dist/runner/client.js +168 -0
- package/dist/runner/client.js.map +7 -0
- package/dist/runner/devBundle.d.ts +14 -0
- package/dist/runner/devBundle.js +78 -0
- package/dist/runner/devBundle.js.map +7 -0
- package/dist/scenario/cli.d.ts +39 -0
- package/dist/scenario/cli.js +125 -0
- package/dist/scenario/cli.js.map +7 -0
- package/dist/scenario/runner.d.ts +50 -0
- package/dist/scenario/runner.js +185 -0
- package/dist/scenario/runner.js.map +7 -0
- package/dist/sharedRefs.d.ts +22 -0
- package/dist/sharedRefs.js +114 -0
- package/dist/sharedRefs.js.map +7 -0
- package/dist/ssh/exec.d.ts +39 -0
- package/dist/ssh/exec.js +13 -0
- package/dist/ssh/exec.js.map +7 -0
- package/dist/ssh/remote.d.ts +40 -0
- package/dist/ssh/remote.js +25 -0
- package/dist/ssh/remote.js.map +7 -0
- package/dist/ssh/sshRun.d.ts +1 -0
- package/dist/ssh/sshRun.js +24 -0
- package/dist/ssh/sshRun.js.map +7 -0
- package/dist/ssh/sync.d.ts +22 -0
- package/dist/ssh/sync.js +12 -0
- package/dist/ssh/sync.js.map +7 -0
- package/dist/timeouts.d.ts +38 -0
- package/dist/timeouts.js +7 -0
- package/dist/timeouts.js.map +7 -0
- package/package.json +35 -4
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// src/node/autoLayout.ts
|
|
2
|
+
import { nodeSize } from "@ansible-flow/core/flow/geometry";
|
|
3
|
+
import { portOffsets } from "@ansible-flow/core/flow/levelView";
|
|
4
|
+
import { buildModel } from "@ansible-flow/core/flow/model";
|
|
5
|
+
var engine;
|
|
6
|
+
function elk() {
|
|
7
|
+
engine ??= import("elkjs/lib/elk.bundled.js").then((m) => new m.default());
|
|
8
|
+
return engine;
|
|
9
|
+
}
|
|
10
|
+
var OPTIONS = {
|
|
11
|
+
"elk.algorithm": "layered",
|
|
12
|
+
"elk.layered.spacing.nodeNodeBetweenLayers": "56",
|
|
13
|
+
"elk.spacing.nodeNode": "48",
|
|
14
|
+
// AFD-192: room between a wire and a node it passes (ELK's default is 10). A step fed from a
|
|
15
|
+
// right side port lines up beside the wire of the column it leaves; at 10 the wire's label
|
|
16
|
+
// (`failed_hosts`, 80 px) then lay on the column's nodes.
|
|
17
|
+
"elk.spacing.edgeNode": "40",
|
|
18
|
+
"elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
|
|
19
|
+
"elk.edgeRouting": "ORTHOGONAL"
|
|
20
|
+
};
|
|
21
|
+
var SIDE = {
|
|
22
|
+
top: "NORTH",
|
|
23
|
+
bottom: "SOUTH",
|
|
24
|
+
right: "EAST",
|
|
25
|
+
left: "WEST"
|
|
26
|
+
};
|
|
27
|
+
var portId = (node, port) => `${node}:${port}`;
|
|
28
|
+
function elkNode(n) {
|
|
29
|
+
const size = nodeSize(n.type);
|
|
30
|
+
const ports = portOffsets(n).map((p) => {
|
|
31
|
+
const across = p.side === "top" || p.side === "bottom";
|
|
32
|
+
const along = Math.round((across ? size.width : size.height) * p.offset);
|
|
33
|
+
return {
|
|
34
|
+
id: portId(n.id, p.name),
|
|
35
|
+
x: across ? along : p.side === "right" ? size.width : 0,
|
|
36
|
+
y: across ? p.side === "bottom" ? size.height : 0 : along,
|
|
37
|
+
width: 0,
|
|
38
|
+
height: 0,
|
|
39
|
+
layoutOptions: { "elk.port.side": SIDE[p.side] }
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
return { id: n.id, ...size, ports, layoutOptions: { "elk.portConstraints": "FIXED_POS" } };
|
|
43
|
+
}
|
|
44
|
+
async function layoutLevel(level, direction = "TB") {
|
|
45
|
+
const nodes = level.nodes.filter((n) => n?.id);
|
|
46
|
+
const children = nodes.map(elkNode);
|
|
47
|
+
const ports = new Set(children.flatMap((c) => c.ports.map((p) => p.id)));
|
|
48
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
49
|
+
const end = (r) => ports.has(portId(r.node, r.port)) ? portId(r.node, r.port) : r.node;
|
|
50
|
+
const graph = await (await elk()).layout({
|
|
51
|
+
id: level.id,
|
|
52
|
+
layoutOptions: { ...OPTIONS, "elk.direction": direction === "LR" ? "RIGHT" : "DOWN" },
|
|
53
|
+
children,
|
|
54
|
+
edges: level.edges.filter((e) => e && ids.has(e.from?.node) && ids.has(e.to?.node)).map((e) => ({ id: e.id, sources: [end(e.from)], targets: [end(e.to)] }))
|
|
55
|
+
});
|
|
56
|
+
const out = {};
|
|
57
|
+
for (const c of graph.children ?? [])
|
|
58
|
+
out[c.id] = { x: Math.round(c.x ?? 0), y: Math.round(c.y ?? 0) };
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
async function layoutAll(doc) {
|
|
62
|
+
const layout = {};
|
|
63
|
+
for (const level of buildModel(doc).levels.values())
|
|
64
|
+
Object.assign(layout, await layoutLevel(level));
|
|
65
|
+
doc.layout = layout;
|
|
66
|
+
}
|
|
67
|
+
async function autoPositions(level, layout, direction = "TB") {
|
|
68
|
+
const placed = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const n of level.nodes) {
|
|
70
|
+
const p = n && layout?.[n.id];
|
|
71
|
+
if (p && typeof p.x === "number" && typeof p.y === "number") placed.set(n.id, p);
|
|
72
|
+
}
|
|
73
|
+
const missing = level.nodes.filter((n) => n?.id && !placed.has(n.id));
|
|
74
|
+
if (missing.length === 0) return {};
|
|
75
|
+
const elkAt = await layoutLevel(level, direction);
|
|
76
|
+
if (placed.size === 0) return elkAt;
|
|
77
|
+
const out = {};
|
|
78
|
+
const known = (id) => placed.get(id) ?? out[id];
|
|
79
|
+
const median = (xs) => [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)] ?? 0;
|
|
80
|
+
const shifts = [...placed].flatMap(([id, p]) => {
|
|
81
|
+
const e = elkAt[id];
|
|
82
|
+
return e ? [{ x: p.x - e.x, y: p.y - e.y }] : [];
|
|
83
|
+
});
|
|
84
|
+
const shift = { x: median(shifts.map((s) => s.x)), y: median(shifts.map((s) => s.y)) };
|
|
85
|
+
const type = new Map(level.nodes.filter((n) => n?.id).map((n) => [n.id, n.type]));
|
|
86
|
+
const boxes = [...placed].map(([id, p]) => ({ ...p, ...nodeSize(type.get(id) ?? "task") }));
|
|
87
|
+
const overlaps = (p, size) => boxes.some(
|
|
88
|
+
(b) => p.x < b.x + b.width + GAP && p.x + size.width + GAP > b.x && p.y < b.y + b.height + GAP && p.y + size.height + GAP > b.y
|
|
89
|
+
);
|
|
90
|
+
const order = [...missing].sort(
|
|
91
|
+
(a, b) => (elkAt[a.id]?.y ?? 0) - (elkAt[b.id]?.y ?? 0) || (elkAt[a.id]?.x ?? 0) - (elkAt[b.id]?.x ?? 0)
|
|
92
|
+
);
|
|
93
|
+
for (const n of order) {
|
|
94
|
+
const e = elkAt[n.id] ?? { x: 0, y: 0 };
|
|
95
|
+
const anchor = level.edges.find((w) => w?.to?.node === n.id && known(w.from?.node))?.from.node ?? level.edges.find((w) => w?.from?.node === n.id && known(w.to?.node))?.to.node;
|
|
96
|
+
const a = anchor ? known(anchor) : void 0;
|
|
97
|
+
const ea = anchor ? elkAt[anchor] : void 0;
|
|
98
|
+
const size = nodeSize(n.type);
|
|
99
|
+
const p = a && ea ? { x: a.x + e.x - ea.x, y: a.y + e.y - ea.y } : { x: e.x + shift.x, y: e.y + shift.y };
|
|
100
|
+
for (let i = 0; i < 40 && overlaps(p, size); i++) p.x += size.width + GAP * 2;
|
|
101
|
+
const at = { x: Math.round(p.x), y: Math.round(p.y) };
|
|
102
|
+
out[n.id] = at;
|
|
103
|
+
boxes.push({ ...at, ...size });
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
var GAP = 24;
|
|
108
|
+
var TEMPLATE = {
|
|
109
|
+
start: { x: 0, y: 0 },
|
|
110
|
+
end: { x: 36, y: 160 }
|
|
111
|
+
};
|
|
112
|
+
function shownLayout(model) {
|
|
113
|
+
const layout = model.doc.layout;
|
|
114
|
+
if (!layout) return layout;
|
|
115
|
+
let out;
|
|
116
|
+
for (const level of model.levels.values()) {
|
|
117
|
+
const nodes = level.nodes.filter((n) => n?.id);
|
|
118
|
+
const placed = nodes.filter((n) => layout[n.id]);
|
|
119
|
+
if (nodes.length <= 2 || placed.length !== 2) continue;
|
|
120
|
+
const template = placed.every((n) => {
|
|
121
|
+
const t = TEMPLATE[n.type];
|
|
122
|
+
const p = layout[n.id];
|
|
123
|
+
return !!t && p?.x === t.x && p?.y === t.y;
|
|
124
|
+
});
|
|
125
|
+
if (!template || placed[0]?.type === placed[1]?.type) continue;
|
|
126
|
+
out ??= { ...layout };
|
|
127
|
+
for (const n of placed) delete out[n.id];
|
|
128
|
+
}
|
|
129
|
+
return out ?? layout;
|
|
130
|
+
}
|
|
131
|
+
async function shownPositions(model, direction = "TB") {
|
|
132
|
+
const stored = shownLayout(model) ?? {};
|
|
133
|
+
const auto = {};
|
|
134
|
+
for (const level of model.levels.values())
|
|
135
|
+
Object.assign(auto, await autoPositions(level, stored, direction));
|
|
136
|
+
return { stored, auto };
|
|
137
|
+
}
|
|
138
|
+
export {
|
|
139
|
+
autoPositions,
|
|
140
|
+
layoutAll,
|
|
141
|
+
layoutLevel,
|
|
142
|
+
shownLayout,
|
|
143
|
+
shownPositions
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=autoLayout.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/node/autoLayout.ts"],
|
|
4
|
+
"sourcesContent": ["// Top-down layered layout with ELK, run in the host (5\u201315 nodes per level: milliseconds, no worker).\nimport type ELK from 'elkjs/lib/elk.bundled.js';\nimport { nodeSize } from '../core/flow/geometry';\nimport { portOffsets } from '../core/flow/levelView';\nimport { buildModel, type FlowModel, type Level } from '../core/flow/model';\nimport type { PortSide } from '../core/flow/ports';\nimport type { FlowDocument, FlowNode, Layout, Position, Ulid } from '../core/flow/types';\n\n// ELK is three quarters of the host code (about 160 ms to load). It loads at the first layout, not\n// at activation; the build ships it as dist/elk.js next to the extension (scripts/build.mjs).\nlet engine: Promise<InstanceType<typeof ELK>> | undefined;\nfunction elk(): Promise<InstanceType<typeof ELK>> {\n engine ??= import('elkjs/lib/elk.bundled.js').then((m) => new m.default());\n return engine;\n}\n\n/** Top to bottom (the default) or left to right: the user setting `afd.canvas.direction` (AFD-42). */\nexport type LayoutDirection = 'TB' | 'LR';\n\nconst OPTIONS = {\n 'elk.algorithm': 'layered',\n 'elk.layered.spacing.nodeNodeBetweenLayers': '56',\n 'elk.spacing.nodeNode': '48',\n // AFD-192: room between a wire and a node it passes (ELK's default is 10). A step fed from a\n // right side port lines up beside the wire of the column it leaves; at 10 the wire's label\n // (`failed_hosts`, 80 px) then lay on the column's nodes.\n 'elk.spacing.edgeNode': '40',\n 'elk.layered.nodePlacement.strategy': 'BRANDES_KOEPF',\n 'elk.edgeRouting': 'ORTHOGONAL',\n};\n\nconst SIDE: Record<PortSide, string> = {\n top: 'NORTH',\n bottom: 'SOUTH',\n right: 'EAST',\n left: 'WEST',\n};\n\nconst portId = (node: Ulid, port: string) => `${node}:${port}`;\n\n/**\n * A node for ELK with its ports where the canvas draws them (AFD-192). Without ports ELK invented\n * one bottom port per wire, in wire order: a decision's `true` target could land under the canvas's\n * `false` handle (the two labels then shared one gap), and a step fed by `failed_hosts` (a right\n * side port) could land left of the column it handles, its wires across that column.\n */\nfunction elkNode(n: FlowNode) {\n const size = nodeSize(n.type);\n const ports = portOffsets(n).map((p) => {\n const across = p.side === 'top' || p.side === 'bottom';\n const along = Math.round((across ? size.width : size.height) * p.offset);\n return {\n id: portId(n.id, p.name),\n x: across ? along : p.side === 'right' ? size.width : 0,\n y: across ? (p.side === 'bottom' ? size.height : 0) : along,\n width: 0,\n height: 0,\n layoutOptions: { 'elk.port.side': SIDE[p.side] },\n };\n });\n return { id: n.id, ...size, ports, layoutOptions: { 'elk.portConstraints': 'FIXED_POS' } };\n}\n\n/**\n * Positions for every node of a level. Wires run port to port. Model order\n * (`considerModelOrder`) and straightness priority on main outputs were tried for AFD-192: both\n * gave more crossings on the fixtures, so ELK orders the layers by crossings alone.\n */\nexport async function layoutLevel(\n level: Level,\n direction: LayoutDirection = 'TB',\n): Promise<Record<Ulid, Position>> {\n const nodes = level.nodes.filter((n) => n?.id);\n const children = nodes.map(elkNode);\n const ports = new Set(children.flatMap((c) => c.ports.map((p) => p.id)));\n const ids = new Set(nodes.map((n) => n.id));\n // A wire on a port the node does not have (the validator reports it) ends at the node itself.\n const end = (r: { node: Ulid; port: string }) =>\n ports.has(portId(r.node, r.port)) ? portId(r.node, r.port) : r.node;\n const graph = await (await elk()).layout({\n id: level.id,\n layoutOptions: { ...OPTIONS, 'elk.direction': direction === 'LR' ? 'RIGHT' : 'DOWN' },\n children,\n edges: level.edges\n .filter((e) => e && ids.has(e.from?.node) && ids.has(e.to?.node))\n .map((e) => ({ id: e.id, sources: [end(e.from)], targets: [end(e.to)] })),\n });\n const out: Record<Ulid, Position> = {};\n for (const c of graph.children ?? [])\n out[c.id] = { x: Math.round(c.x ?? 0), y: Math.round(c.y ?? 0) };\n return out;\n}\n\n/** ELK positions for every level of a new document, in the one flat layout map (ADR-0002). */\nexport async function layoutAll(doc: FlowDocument): Promise<void> {\n const layout: Layout = {};\n for (const level of buildModel(doc).levels.values())\n Object.assign(layout, await layoutLevel(level));\n doc.layout = layout;\n}\n\n/**\n * In-memory positions for nodes the layout block does not place (ADR-0002). With no stored\n * positions at all the level gets a full ELK layout. Otherwise (AFD-23) the missing nodes keep\n * ELK's shape around the placed ones: each goes where ELK puts it relative to a placed neighbour\n * (its predecessor first), else by the placed nodes' median shift, and moves right while it would\n * overlap a node. Before, each missing node went straight below its predecessor, so a flow with\n * only Start and Done placed (the New Flow template) and everything else added by an AI agent\n * stood in one column, siblings on top of each other.\n */\nexport async function autoPositions(\n level: Level,\n layout: Layout | undefined,\n direction: LayoutDirection = 'TB',\n): Promise<Record<Ulid, Position>> {\n const placed = new Map<Ulid, Position>();\n for (const n of level.nodes) {\n const p = n && layout?.[n.id];\n if (p && typeof p.x === 'number' && typeof p.y === 'number') placed.set(n.id, p);\n }\n const missing = level.nodes.filter((n) => n?.id && !placed.has(n.id));\n if (missing.length === 0) return {};\n const elkAt = await layoutLevel(level, direction);\n if (placed.size === 0) return elkAt;\n\n const out: Record<Ulid, Position> = {};\n const known = (id: Ulid) => placed.get(id) ?? out[id];\n const median = (xs: number[]) => [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)] ?? 0;\n const shifts = [...placed].flatMap(([id, p]) => {\n const e = elkAt[id];\n return e ? [{ x: p.x - e.x, y: p.y - e.y }] : [];\n });\n const shift = { x: median(shifts.map((s) => s.x)), y: median(shifts.map((s) => s.y)) };\n const type = new Map(level.nodes.filter((n) => n?.id).map((n) => [n.id, n.type]));\n const boxes = [...placed].map(([id, p]) => ({ ...p, ...nodeSize(type.get(id) ?? 'task') }));\n const overlaps = (p: Position, size: { width: number; height: number }) =>\n boxes.some(\n (b) =>\n p.x < b.x + b.width + GAP &&\n p.x + size.width + GAP > b.x &&\n p.y < b.y + b.height + GAP &&\n p.y + size.height + GAP > b.y,\n );\n // Top-down in ELK's order, so a predecessor is placed before the nodes below it.\n const order = [...missing].sort(\n (a, b) =>\n (elkAt[a.id]?.y ?? 0) - (elkAt[b.id]?.y ?? 0) ||\n (elkAt[a.id]?.x ?? 0) - (elkAt[b.id]?.x ?? 0),\n );\n for (const n of order) {\n const e = elkAt[n.id] ?? { x: 0, y: 0 };\n const anchor =\n level.edges.find((w) => w?.to?.node === n.id && known(w.from?.node))?.from.node ??\n level.edges.find((w) => w?.from?.node === n.id && known(w.to?.node))?.to.node;\n const a = anchor ? known(anchor) : undefined;\n const ea = anchor ? elkAt[anchor] : undefined;\n const size = nodeSize(n.type);\n const p =\n a && ea\n ? { x: a.x + e.x - ea.x, y: a.y + e.y - ea.y }\n : { x: e.x + shift.x, y: e.y + shift.y };\n for (let i = 0; i < 40 && overlaps(p, size); i++) p.x += size.width + GAP * 2;\n const at = { x: Math.round(p.x), y: Math.round(p.y) };\n out[n.id] = at;\n boxes.push({ ...at, ...size });\n }\n return out;\n}\n\n/** Room kept between an auto-placed node and its neighbours. */\nconst GAP = 24;\n\n/** Where the New Flow template put Start and Done until AFD-23. */\nconst TEMPLATE: Partial<Record<string, Position>> = {\n start: { x: 0, y: 0 },\n end: { x: 36, y: 160 },\n};\n\n/**\n * The layout the canvas shows (AFD-23): the stored one, except that a level whose only positions\n * are the New Flow template's (Start at 0,0 and Done at 36,160), while it has other nodes, is shown\n * with a full auto-layout. Flows made before AFD-23 and then filled by an AI agent look like that.\n * The shown positions are written on the first move or Tidy up, like any auto position.\n */\nexport function shownLayout(model: FlowModel): Layout | undefined {\n const layout = model.doc.layout;\n if (!layout) return layout;\n let out: Layout | undefined;\n for (const level of model.levels.values()) {\n const nodes = level.nodes.filter((n) => n?.id);\n const placed = nodes.filter((n) => layout[n.id]);\n if (nodes.length <= 2 || placed.length !== 2) continue;\n const template = placed.every((n) => {\n const t = TEMPLATE[n.type];\n const p = layout[n.id];\n return !!t && p?.x === t.x && p?.y === t.y;\n });\n if (!template || placed[0]?.type === placed[1]?.type) continue;\n out ??= { ...layout };\n for (const n of placed) delete out[n.id];\n }\n return out ?? layout;\n}\n\n/**\n * Where the canvas shows every node (AFD-24), for the AI tools: `stored` positions (the old\n * template's dropped, as in the view) and `auto` positions for the rest, on every level. Pass the\n * canvas direction (`afd.canvas.direction`) so the layout check sees what the canvas shows (AFD-192).\n */\nexport async function shownPositions(\n model: FlowModel,\n direction: LayoutDirection = 'TB',\n): Promise<{ stored: Layout; auto: Record<Ulid, Position> }> {\n const stored = shownLayout(model) ?? {};\n const auto: Record<Ulid, Position> = {};\n for (const level of model.levels.values())\n Object.assign(auto, await autoPositions(level, stored, direction));\n return { stored, auto };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type AwxNamed, type AwxWorkflowJob } from '@ansible-flow/core/awx/types';
|
|
2
|
+
import type { Route, SegmentFile } from '@ansible-flow/core/engine/types';
|
|
3
|
+
import type { FlowDocument } from '@ansible-flow/core/flow/types';
|
|
4
|
+
import type { AwxRunInfo } from '@ansible-flow/core/protocol';
|
|
5
|
+
import { type RunSummary } from '@ansible-flow/core/run/events';
|
|
6
|
+
import { type AwxClient } from './client';
|
|
7
|
+
export declare const workflowName: (doc: FlowDocument) => string;
|
|
8
|
+
export declare const segmentTemplateName: (doc: FlowDocument, segment: string) => string;
|
|
9
|
+
export interface WorkflowPicks {
|
|
10
|
+
project: AwxNamed;
|
|
11
|
+
inventory: AwxNamed;
|
|
12
|
+
environment?: AwxNamed;
|
|
13
|
+
credentials: AwxNamed[];
|
|
14
|
+
/** Repository paths of the segment playbooks, in segment order. */
|
|
15
|
+
playbooks: string[];
|
|
16
|
+
}
|
|
17
|
+
/** Creates or updates the segment job templates and the workflow; returns the workflow name. */
|
|
18
|
+
export declare function setupWorkflow(awx: AwxClient, doc: FlowDocument, segments: SegmentFile[], picks: WorkflowPicks): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* The segment plays that restore and carry host sets run on localhost outside any node; the empty-
|
|
21
|
+
* gate stop is one of them. Neither is a host of the flow or a failure of one.
|
|
22
|
+
*/
|
|
23
|
+
export declare function withoutBookkeeping(summary: RunSummary): RunSummary;
|
|
24
|
+
export interface WorkflowWatch {
|
|
25
|
+
awx: AwxClient;
|
|
26
|
+
doc: FlowDocument;
|
|
27
|
+
segments: SegmentFile[];
|
|
28
|
+
routes: Record<string, Route>;
|
|
29
|
+
/** Called with every change while the workflow runs. */
|
|
30
|
+
update(state: {
|
|
31
|
+
info: AwxRunInfo;
|
|
32
|
+
summary: RunSummary;
|
|
33
|
+
workflow: AwxWorkflowJob;
|
|
34
|
+
}): void;
|
|
35
|
+
/** Local HEAD, for the revision check. */
|
|
36
|
+
sha?: string;
|
|
37
|
+
/** Asked before each poll: a reason to stop following (the workflow keeps running in AWX). */
|
|
38
|
+
stop?: () => 'cancelled' | 'timeout' | undefined;
|
|
39
|
+
/** The workflow job started (AFD-39: Stop Run cancels it). */
|
|
40
|
+
launched?: (workflowJobId: number) => void;
|
|
41
|
+
}
|
|
42
|
+
/** Launches the workflow with the inputs and follows it until it and its jobs' events are done. */
|
|
43
|
+
export declare function runWorkflow(watch: WorkflowWatch, templateId: number, inputs: Record<string, unknown>): Promise<{
|
|
44
|
+
info: AwxRunInfo;
|
|
45
|
+
summary: RunSummary;
|
|
46
|
+
workflow: AwxWorkflowJob;
|
|
47
|
+
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runWorkflow,
|
|
3
|
+
segmentTemplateName,
|
|
4
|
+
setupWorkflow,
|
|
5
|
+
withoutBookkeeping,
|
|
6
|
+
workflowName
|
|
7
|
+
} from "../chunks/chunk-A2NMQGQE.js";
|
|
8
|
+
import "../chunks/chunk-GAL5LW6Y.js";
|
|
9
|
+
import "../chunks/chunk-IIFP6UQR.js";
|
|
10
|
+
export {
|
|
11
|
+
runWorkflow,
|
|
12
|
+
segmentTemplateName,
|
|
13
|
+
setupWorkflow,
|
|
14
|
+
withoutBookkeeping,
|
|
15
|
+
workflowName
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=awxWorkflow.js.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AwxSurveySpec } from '@ansible-flow/core/awx/survey';
|
|
2
|
+
import type { AwxJob, AwxJobEvent, AwxJobTemplate, AwxNamed, TemplateSpec } from '@ansible-flow/core/awx/types';
|
|
3
|
+
export declare class AwxError extends Error {
|
|
4
|
+
readonly status?: number | undefined;
|
|
5
|
+
/** The socket error code when AWX could not be reached (ECONNRESET, …). */
|
|
6
|
+
readonly code?: string | undefined;
|
|
7
|
+
constructor(message: string, status?: number | undefined,
|
|
8
|
+
/** The socket error code when AWX could not be reached (ECONNRESET, …). */
|
|
9
|
+
code?: string | undefined);
|
|
10
|
+
}
|
|
11
|
+
export interface AwxOptions {
|
|
12
|
+
verifyTls?: boolean;
|
|
13
|
+
/** Idle time without data before a request fails. */
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
/** AFD-161: the whole request, however slowly the data comes. */
|
|
16
|
+
deadlineMs?: number;
|
|
17
|
+
/**
|
|
18
|
+
* AFD-161: send the token over plain http. Refused by default, except to this machine
|
|
19
|
+
* (localhost, 127.0.0.1, ::1), where nothing crosses a network.
|
|
20
|
+
*/
|
|
21
|
+
allowHttp?: boolean;
|
|
22
|
+
/** The largest answer read (default MAX_BODY_BYTES). */
|
|
23
|
+
maxBodyBytes?: number;
|
|
24
|
+
}
|
|
25
|
+
/** AFD-161: no AWX answer is larger than this; a bigger one is a misbehaving proxy or server. */
|
|
26
|
+
export declare const MAX_BODY_BYTES: number;
|
|
27
|
+
/** AFD-161: 500 pages of 200 is 100,000 items, more than any AWX list Ansible Flow reads. */
|
|
28
|
+
export declare const MAX_PAGES = 500;
|
|
29
|
+
/** Whether a URL would carry the token over plain http to another machine. */
|
|
30
|
+
export declare const cleartextTo: (url: URL) => boolean;
|
|
31
|
+
export declare class AwxClient {
|
|
32
|
+
private readonly token;
|
|
33
|
+
private readonly options;
|
|
34
|
+
readonly base: string;
|
|
35
|
+
private readonly origin;
|
|
36
|
+
constructor(url: string, token: string, options?: AwxOptions);
|
|
37
|
+
/** The AWX UI page of a job's output. */
|
|
38
|
+
jobUrl(id: number): string;
|
|
39
|
+
request<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
40
|
+
/** A GET is safe to repeat, so a stale kept-alive socket gets one more try. */
|
|
41
|
+
get<T>(path: string): Promise<T>;
|
|
42
|
+
post<T>(path: string, body: unknown): Promise<T>;
|
|
43
|
+
patch<T>(path: string, body: unknown): Promise<T>;
|
|
44
|
+
/** Every page of a list endpoint. */
|
|
45
|
+
all<T>(path: string): Promise<T[]>;
|
|
46
|
+
ping(): Promise<{
|
|
47
|
+
version: string;
|
|
48
|
+
}>;
|
|
49
|
+
named<T extends AwxNamed>(kind: string, name: string): Promise<T | undefined>;
|
|
50
|
+
list<T extends AwxNamed>(kind: string): Promise<T[]>;
|
|
51
|
+
/** Creates the job template or updates the one of the same name; credentials are set to the list. */
|
|
52
|
+
upsertTemplate(spec: TemplateSpec): Promise<AwxJobTemplate>;
|
|
53
|
+
/** A template's survey (AFD-59); `kind` is job_templates or workflow_job_templates. */
|
|
54
|
+
survey(kind: string, id: number): Promise<Partial<AwxSurveySpec> | undefined>;
|
|
55
|
+
/** Writes the survey and turns it on: launches then ask its questions. */
|
|
56
|
+
setSurvey(kind: string, id: number, spec: AwxSurveySpec): Promise<void>;
|
|
57
|
+
launch(templateId: number, extraVars: Record<string, unknown>): Promise<AwxJob>;
|
|
58
|
+
job(id: number): Promise<AwxJob>;
|
|
59
|
+
/** Events after `counter`, in order (incremental polling while a job runs). */
|
|
60
|
+
events(jobId: number, counter: number): Promise<AwxJobEvent[]>;
|
|
61
|
+
cancel(jobId: number): Promise<void>;
|
|
62
|
+
cancelWorkflow(workflowJobId: number): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Following an AWX job stopped before it finished (AFD-29): the user cancelled the progress, or it
|
|
66
|
+
* ran past the follow limit. The job itself keeps running in AWX.
|
|
67
|
+
*/
|
|
68
|
+
export declare class StoppedFollowing extends Error {
|
|
69
|
+
readonly kind: 'job' | 'workflow';
|
|
70
|
+
readonly id: number;
|
|
71
|
+
readonly url: string;
|
|
72
|
+
constructor(kind: 'job' | 'workflow', id: number, url: string, timedOut: boolean);
|
|
73
|
+
}
|
|
74
|
+
/** How long a launch follows a job or workflow before it stops polling (it can be stopped sooner). */
|
|
75
|
+
export declare const FOLLOW_HOURS = 12;
|
|
76
|
+
/** A follow loop's stop check: cancelled by the user, or past the follow limit. */
|
|
77
|
+
export declare function followLimit(cancelled: () => boolean, start?: number): () => 'cancelled' | 'timeout' | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AwxClient,
|
|
3
|
+
AwxError,
|
|
4
|
+
FOLLOW_HOURS,
|
|
5
|
+
MAX_BODY_BYTES,
|
|
6
|
+
MAX_PAGES,
|
|
7
|
+
StoppedFollowing,
|
|
8
|
+
cleartextTo,
|
|
9
|
+
followLimit
|
|
10
|
+
} from "../chunks/chunk-GAL5LW6Y.js";
|
|
11
|
+
import "../chunks/chunk-IIFP6UQR.js";
|
|
12
|
+
export {
|
|
13
|
+
AwxClient,
|
|
14
|
+
AwxError,
|
|
15
|
+
FOLLOW_HOURS,
|
|
16
|
+
MAX_BODY_BYTES,
|
|
17
|
+
MAX_PAGES,
|
|
18
|
+
StoppedFollowing,
|
|
19
|
+
cleartextTo,
|
|
20
|
+
followLimit
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FlowDocument } from '@ansible-flow/core/flow/types';
|
|
2
|
+
import type { Finding } from '@ansible-flow/core/flow/validate';
|
|
3
|
+
import type { CatalogIndex } from './catalogIndex';
|
|
4
|
+
export declare function checkBindings(doc: FlowDocument, index: CatalogIndex, environment: string): Finding[];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// src/node/catalog/bindingCheck.ts
|
|
2
|
+
import { documentedOptions, takesFreeKeys } from "@ansible-flow/core/catalog/freeKeys";
|
|
3
|
+
import { buildModel } from "@ansible-flow/core/flow/model";
|
|
4
|
+
function checkBindings(doc, index, environment) {
|
|
5
|
+
const out = [];
|
|
6
|
+
for (const { node, path } of buildModel(doc).nodes.values()) {
|
|
7
|
+
const impl = node.impl;
|
|
8
|
+
if (impl?.kind !== "module" || !impl.ref) continue;
|
|
9
|
+
const at = [...path, "impl", "ref"];
|
|
10
|
+
const who = `"${node.title}"`;
|
|
11
|
+
const add = (code, severity, message, where = at) => out.push({ code, severity, message, path: where, node: node.id });
|
|
12
|
+
const r = index.resolve(impl.ref);
|
|
13
|
+
if (!r.found) {
|
|
14
|
+
if (r.removed)
|
|
15
|
+
add("binding-removed", "error", `${who}: ${impl.ref} was removed: ${r.removed}`);
|
|
16
|
+
else
|
|
17
|
+
add(
|
|
18
|
+
"binding-not-in-ee",
|
|
19
|
+
"warning",
|
|
20
|
+
`${who}: ${impl.ref} is not in the environment (${environment})`
|
|
21
|
+
);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const plugin = index.plugin(r.fqcn ?? impl.ref);
|
|
25
|
+
if (!plugin) continue;
|
|
26
|
+
if (r.fqcn !== impl.ref)
|
|
27
|
+
add("binding-redirect", "info", `${who}: ${impl.ref} runs as ${r.fqcn}`);
|
|
28
|
+
const deprecated = r.deprecated ?? (plugin.deprecated ? plugin.deprecated.why || "deprecated" : void 0);
|
|
29
|
+
if (deprecated)
|
|
30
|
+
add("binding-deprecated", "warning", `${who}: ${r.fqcn} is deprecated: ${deprecated}`);
|
|
31
|
+
if (plugin.attributes?.check_mode?.support === "none")
|
|
32
|
+
add(
|
|
33
|
+
"binding-no-check-mode",
|
|
34
|
+
"info",
|
|
35
|
+
`${who}: ${r.fqcn} does not support check mode, so skeleton runs skip it`
|
|
36
|
+
);
|
|
37
|
+
const options = documentedOptions(impl.ref, plugin.options) ?? {};
|
|
38
|
+
const names = Object.keys(options);
|
|
39
|
+
if (names.length === 0) continue;
|
|
40
|
+
const params = Object.keys(impl.params ?? {});
|
|
41
|
+
const known = /* @__PURE__ */ new Map();
|
|
42
|
+
for (const [name, o] of Object.entries(options)) {
|
|
43
|
+
known.set(name, name);
|
|
44
|
+
for (const a of o.aliases ?? []) known.set(a, name);
|
|
45
|
+
}
|
|
46
|
+
const given = new Set(params.map((p) => known.get(p) ?? p));
|
|
47
|
+
for (const [name, o] of Object.entries(options))
|
|
48
|
+
if (o.required && o.default === void 0 && !given.has(name))
|
|
49
|
+
add("binding-missing-param", "warning", `${who}: ${r.fqcn} needs "${name}"`, [
|
|
50
|
+
...path,
|
|
51
|
+
"impl"
|
|
52
|
+
]);
|
|
53
|
+
if (!names.includes("free_form") && !takesFreeKeys(impl.ref)) {
|
|
54
|
+
for (const p of params)
|
|
55
|
+
if (!known.has(p))
|
|
56
|
+
add("binding-unknown-param", "warning", `${who}: ${r.fqcn} has no parameter "${p}"`, [
|
|
57
|
+
...path,
|
|
58
|
+
"impl",
|
|
59
|
+
"params",
|
|
60
|
+
p
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
checkBindings
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=bindingCheck.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/node/catalog/bindingCheck.ts"],
|
|
4
|
+
"sourcesContent": ["// Catalog-backed checks of module bindings (M5, ADR-0011): what the environment says about the\n// bound module. Runs only with a loaded index; the pure checks live in core/flow/validate.\nimport { documentedOptions, takesFreeKeys } from '../../core/catalog/freeKeys';\nimport { buildModel } from '../../core/flow/model';\nimport type { FlowDocument } from '../../core/flow/types';\nimport type { Finding } from '../../core/flow/validate';\nimport type { CatalogIndex } from './catalogIndex';\n\nexport function checkBindings(\n doc: FlowDocument,\n index: CatalogIndex,\n environment: string,\n): Finding[] {\n const out: Finding[] = [];\n for (const { node, path } of buildModel(doc).nodes.values()) {\n const impl = node.impl;\n if (impl?.kind !== 'module' || !impl.ref) continue;\n const at = [...path, 'impl', 'ref'];\n const who = `\"${node.title}\"`;\n const add = (code: string, severity: Finding['severity'], message: string, where = at) =>\n out.push({ code, severity, message, path: where, node: node.id });\n const r = index.resolve(impl.ref);\n if (!r.found) {\n if (r.removed)\n add('binding-removed', 'error', `${who}: ${impl.ref} was removed: ${r.removed}`);\n else\n add(\n 'binding-not-in-ee',\n 'warning',\n `${who}: ${impl.ref} is not in the environment (${environment})`,\n );\n continue;\n }\n const plugin = index.plugin(r.fqcn ?? impl.ref);\n if (!plugin) continue;\n if (r.fqcn !== impl.ref)\n add('binding-redirect', 'info', `${who}: ${impl.ref} runs as ${r.fqcn}`);\n const deprecated =\n r.deprecated ?? (plugin.deprecated ? plugin.deprecated.why || 'deprecated' : undefined);\n if (deprecated)\n add('binding-deprecated', 'warning', `${who}: ${r.fqcn} is deprecated: ${deprecated}`);\n if (plugin.attributes?.check_mode?.support === 'none')\n add(\n 'binding-no-check-mode',\n 'info',\n `${who}: ${r.fqcn} does not support check mode, so skeleton runs skip it`,\n );\n const options = documentedOptions(impl.ref, plugin.options) ?? {};\n const names = Object.keys(options);\n if (names.length === 0) continue;\n const params = Object.keys(impl.params ?? {});\n const known = new Map<string, string>();\n for (const [name, o] of Object.entries(options)) {\n known.set(name, name);\n for (const a of o.aliases ?? []) known.set(a, name);\n }\n const given = new Set(params.map((p) => known.get(p) ?? p));\n for (const [name, o] of Object.entries(options))\n if (o.required && o.default === undefined && !given.has(name))\n add('binding-missing-param', 'warning', `${who}: ${r.fqcn} needs \"${name}\"`, [\n ...path,\n 'impl',\n ]);\n // Free-form modules (command, shell) take `_raw_params` and `cmd`; anything goes there, as do\n // set_fact's facts (AFD-222).\n if (!names.includes('free_form') && !takesFreeKeys(impl.ref))\n for (const p of params)\n if (!known.has(p))\n add('binding-unknown-param', 'warning', `${who}: ${r.fqcn} has no parameter \"${p}\"`, [\n ...path,\n 'impl',\n 'params',\n p,\n ]);\n }\n return out;\n}\n"],
|
|
5
|
+
"mappings": ";AAEA,SAAS,mBAAmB,qBAAqB;AACjD,SAAS,kBAAkB;AAKpB,SAAS,cACd,KACA,OACA,aACW;AACX,QAAM,MAAiB,CAAC;AACxB,aAAW,EAAE,MAAM,KAAK,KAAK,WAAW,GAAG,EAAE,MAAM,OAAO,GAAG;AAC3D,UAAM,OAAO,KAAK;AAClB,QAAI,MAAM,SAAS,YAAY,CAAC,KAAK,IAAK;AAC1C,UAAM,KAAK,CAAC,GAAG,MAAM,QAAQ,KAAK;AAClC,UAAM,MAAM,IAAI,KAAK,KAAK;AAC1B,UAAM,MAAM,CAAC,MAAc,UAA+B,SAAiB,QAAQ,OACjF,IAAI,KAAK,EAAE,MAAM,UAAU,SAAS,MAAM,OAAO,MAAM,KAAK,GAAG,CAAC;AAClE,UAAM,IAAI,MAAM,QAAQ,KAAK,GAAG;AAChC,QAAI,CAAC,EAAE,OAAO;AACZ,UAAI,EAAE;AACJ,YAAI,mBAAmB,SAAS,GAAG,GAAG,KAAK,KAAK,GAAG,iBAAiB,EAAE,OAAO,EAAE;AAAA;AAE/E;AAAA,UACE;AAAA,UACA;AAAA,UACA,GAAG,GAAG,KAAK,KAAK,GAAG,+BAA+B,WAAW;AAAA,QAC/D;AACF;AAAA,IACF;AACA,UAAM,SAAS,MAAM,OAAO,EAAE,QAAQ,KAAK,GAAG;AAC9C,QAAI,CAAC,OAAQ;AACb,QAAI,EAAE,SAAS,KAAK;AAClB,UAAI,oBAAoB,QAAQ,GAAG,GAAG,KAAK,KAAK,GAAG,YAAY,EAAE,IAAI,EAAE;AACzE,UAAM,aACJ,EAAE,eAAe,OAAO,aAAa,OAAO,WAAW,OAAO,eAAe;AAC/E,QAAI;AACF,UAAI,sBAAsB,WAAW,GAAG,GAAG,KAAK,EAAE,IAAI,mBAAmB,UAAU,EAAE;AACvF,QAAI,OAAO,YAAY,YAAY,YAAY;AAC7C;AAAA,QACE;AAAA,QACA;AAAA,QACA,GAAG,GAAG,KAAK,EAAE,IAAI;AAAA,MACnB;AACF,UAAM,UAAU,kBAAkB,KAAK,KAAK,OAAO,OAAO,KAAK,CAAC;AAChE,UAAM,QAAQ,OAAO,KAAK,OAAO;AACjC,QAAI,MAAM,WAAW,EAAG;AACxB,UAAM,SAAS,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC;AAC5C,UAAM,QAAQ,oBAAI,IAAoB;AACtC,eAAW,CAAC,MAAM,CAAC,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC/C,YAAM,IAAI,MAAM,IAAI;AACpB,iBAAW,KAAK,EAAE,WAAW,CAAC,EAAG,OAAM,IAAI,GAAG,IAAI;AAAA,IACpD;AACA,UAAM,QAAQ,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;AAC1D,eAAW,CAAC,MAAM,CAAC,KAAK,OAAO,QAAQ,OAAO;AAC5C,UAAI,EAAE,YAAY,EAAE,YAAY,UAAa,CAAC,MAAM,IAAI,IAAI;AAC1D,YAAI,yBAAyB,WAAW,GAAG,GAAG,KAAK,EAAE,IAAI,WAAW,IAAI,KAAK;AAAA,UAC3E,GAAG;AAAA,UACH;AAAA,QACF,CAAC;AAGL,QAAI,CAAC,MAAM,SAAS,WAAW,KAAK,CAAC,cAAc,KAAK,GAAG;AACzD,iBAAW,KAAK;AACd,YAAI,CAAC,MAAM,IAAI,CAAC;AACd,cAAI,yBAAyB,WAAW,GAAG,GAAG,KAAK,EAAE,IAAI,sBAAsB,CAAC,KAAK;AAAA,YACnF,GAAG;AAAA,YACH;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAAA;AAAA,EACT;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CatalogPlugin, CollectionFile, PluginType, Resolution, SearchContext, SearchHit } from '@ansible-flow/core/catalog/types';
|
|
2
|
+
import type { PluginName } from '@ansible-flow/core/view/jinjaComplete';
|
|
3
|
+
export declare const INDEX_SCHEMA = 8;
|
|
4
|
+
export declare function sqliteAvailable(): boolean;
|
|
5
|
+
/** Why the catalog is off in this editor, for the status line and errors. */
|
|
6
|
+
export declare function sqliteMissing(): string;
|
|
7
|
+
export declare class CatalogIndex {
|
|
8
|
+
private readonly db;
|
|
9
|
+
private readonly total;
|
|
10
|
+
private readonly df;
|
|
11
|
+
private readonly named;
|
|
12
|
+
private readonly docOf;
|
|
13
|
+
private readonly routeOf;
|
|
14
|
+
private readonly textOf;
|
|
15
|
+
private readonly searches;
|
|
16
|
+
/** A scratch FTS table whose vocabulary gives the Porter stems the index uses. */
|
|
17
|
+
private readonly stemmer;
|
|
18
|
+
private readonly stemSql;
|
|
19
|
+
private constructor();
|
|
20
|
+
/** Builds a fresh index at `path` (':memory:' in tests) from the probe's collection files. */
|
|
21
|
+
static build(path: string, files: CollectionFile[]): CatalogIndex;
|
|
22
|
+
/** Opens an index built before, or undefined when it is missing or of an older schema. */
|
|
23
|
+
static open(path: string): CatalogIndex | undefined;
|
|
24
|
+
close(): void;
|
|
25
|
+
stats(): {
|
|
26
|
+
plugins: number;
|
|
27
|
+
modules: number;
|
|
28
|
+
collections: number;
|
|
29
|
+
};
|
|
30
|
+
private versions?;
|
|
31
|
+
/** Every indexed collection and its version. */
|
|
32
|
+
collections(): Record<string, string>;
|
|
33
|
+
/** The indexed version of a collection (`ansible.builtin` is ansible-core's). */
|
|
34
|
+
collectionVersion(name: string): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* AFD-225: the filters, tests or lookups the environment has, for the Jinja completion list:
|
|
37
|
+
* ansible.builtin's by short name, a collection's by FQCN (ansible.legacy repeats builtin).
|
|
38
|
+
*/
|
|
39
|
+
names(type: 'filter' | 'test' | 'lookup'): PluginName[];
|
|
40
|
+
plugin(fqcn: string, type?: PluginType): CatalogPlugin | undefined;
|
|
41
|
+
/** The index's own Porter stems of query words (create → creat, query → queri). */
|
|
42
|
+
private porter;
|
|
43
|
+
/** Parameters in text order: (match | names), stems for in_strong, stems for in_text, types. */
|
|
44
|
+
private statement;
|
|
45
|
+
search(query: string, ctx?: SearchContext, limit?: number): SearchHit[];
|
|
46
|
+
private route;
|
|
47
|
+
/**
|
|
48
|
+
* What Ansible runs for a module name (07 §2): the play's collections, then ansible.builtin, then
|
|
49
|
+
* redirects (at most five hops); tombstones mean removed.
|
|
50
|
+
*/
|
|
51
|
+
resolve(name: string, collections?: string[], type?: PluginType): Resolution;
|
|
52
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface BuildJob {
|
|
2
|
+
/** Collection files on this machine. */
|
|
3
|
+
files: string[];
|
|
4
|
+
/** The index to write; built beside it and renamed into place. */
|
|
5
|
+
out: string;
|
|
6
|
+
}
|
|
7
|
+
export type BuildResult = {
|
|
8
|
+
ok: true;
|
|
9
|
+
plugins: number;
|
|
10
|
+
modules: number;
|
|
11
|
+
collections: number;
|
|
12
|
+
ms: number;
|
|
13
|
+
skipped: string[];
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
error: string;
|
|
17
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CatalogIndex
|
|
3
|
+
} from "../chunks/chunk-HPVT6I36.js";
|
|
4
|
+
|
|
5
|
+
// src/node/catalog/catalogWorker.ts
|
|
6
|
+
import { readFileSync, renameSync, rmSync } from "node:fs";
|
|
7
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
8
|
+
import { collectionFile } from "@ansible-flow/core/catalog/shape";
|
|
9
|
+
function build(job) {
|
|
10
|
+
const started = performance.now();
|
|
11
|
+
const tmp = `${job.out}.tmp`;
|
|
12
|
+
rmSync(tmp, { force: true });
|
|
13
|
+
const files = [];
|
|
14
|
+
const skipped = [];
|
|
15
|
+
for (const path of job.files) {
|
|
16
|
+
try {
|
|
17
|
+
const file = collectionFile(JSON.parse(readFileSync(path, "utf8")));
|
|
18
|
+
if (file) files.push(file);
|
|
19
|
+
else skipped.push(path);
|
|
20
|
+
} catch {
|
|
21
|
+
skipped.push(path);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const index = CatalogIndex.build(tmp, files);
|
|
25
|
+
const stats = index.stats();
|
|
26
|
+
index.close();
|
|
27
|
+
renameSync(tmp, job.out);
|
|
28
|
+
return { ok: true, ...stats, ms: Math.round(performance.now() - started), skipped };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
parentPort?.postMessage(build(workerData));
|
|
32
|
+
} catch (e) {
|
|
33
|
+
parentPort?.postMessage({
|
|
34
|
+
ok: false,
|
|
35
|
+
error: e instanceof Error ? e.message : String(e)
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=catalogWorker.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/node/catalog/catalogWorker.ts"],
|
|
4
|
+
"sourcesContent": ["// Builds a catalog index off the extension host thread (node:sqlite is synchronous and a full\n// environment takes seconds). Bundled as dist/catalogWorker.js; one build per worker.\nimport { readFileSync, renameSync, rmSync } from 'node:fs';\nimport { parentPort, workerData } from 'node:worker_threads';\nimport { collectionFile } from '../../core/catalog/shape';\nimport type { CollectionFile } from '../../core/catalog/types';\nimport { CatalogIndex } from './catalogIndex';\n\nexport interface BuildJob {\n /** Collection files on this machine. */\n files: string[];\n /** The index to write; built beside it and renamed into place. */\n out: string;\n}\n\nexport type BuildResult =\n | {\n ok: true;\n plugins: number;\n modules: number;\n collections: number;\n ms: number;\n skipped: string[];\n }\n | { ok: false; error: string };\n\nfunction build(job: BuildJob): BuildResult {\n const started = performance.now();\n const tmp = `${job.out}.tmp`;\n rmSync(tmp, { force: true });\n const files: CollectionFile[] = [];\n const skipped: string[] = [];\n for (const path of job.files) {\n try {\n // AFD-163: JSON that is not a collection dump is skipped too; it threw in build.\n const file = collectionFile(JSON.parse(readFileSync(path, 'utf8')));\n if (file) files.push(file);\n else skipped.push(path);\n } catch {\n // A truncated or foreign file costs one collection, not the catalog.\n skipped.push(path);\n }\n }\n const index = CatalogIndex.build(tmp, files);\n const stats = index.stats();\n index.close();\n renameSync(tmp, job.out);\n return { ok: true, ...stats, ms: Math.round(performance.now() - started), skipped };\n}\n\ntry {\n parentPort?.postMessage(build(workerData as BuildJob));\n} catch (e) {\n parentPort?.postMessage({\n ok: false,\n error: e instanceof Error ? e.message : String(e),\n } satisfies BuildResult);\n}\n"],
|
|
5
|
+
"mappings": ";;;;;AAEA,SAAS,cAAc,YAAY,cAAc;AACjD,SAAS,YAAY,kBAAkB;AACvC,SAAS,sBAAsB;AAsB/B,SAAS,MAAM,KAA4B;AACzC,QAAM,UAAU,YAAY,IAAI;AAChC,QAAM,MAAM,GAAG,IAAI,GAAG;AACtB,SAAO,KAAK,EAAE,OAAO,KAAK,CAAC;AAC3B,QAAM,QAA0B,CAAC;AACjC,QAAM,UAAoB,CAAC;AAC3B,aAAW,QAAQ,IAAI,OAAO;AAC5B,QAAI;AAEF,YAAM,OAAO,eAAe,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC,CAAC;AAClE,UAAI,KAAM,OAAM,KAAK,IAAI;AAAA,UACpB,SAAQ,KAAK,IAAI;AAAA,IACxB,QAAQ;AAEN,cAAQ,KAAK,IAAI;AAAA,IACnB;AAAA,EACF;AACA,QAAM,QAAQ,aAAa,MAAM,KAAK,KAAK;AAC3C,QAAM,QAAQ,MAAM,MAAM;AAC1B,QAAM,MAAM;AACZ,aAAW,KAAK,IAAI,GAAG;AACvB,SAAO,EAAE,IAAI,MAAM,GAAG,OAAO,IAAI,KAAK,MAAM,YAAY,IAAI,IAAI,OAAO,GAAG,QAAQ;AACpF;AAEA,IAAI;AACF,cAAY,YAAY,MAAM,UAAsB,CAAC;AACvD,SAAS,GAAG;AACV,cAAY,YAAY;AAAA,IACtB,IAAI;AAAA,IACJ,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,EAClD,CAAuB;AACzB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/node/catalog/dumpFiles.ts
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
var DUMP_FILE = /^\d+\.\d+\/[\w-]+\.[\w-]+@[\w.+*-]+\.json$/;
|
|
4
|
+
function checkedManifest(manifest, cache) {
|
|
5
|
+
const root = resolve(cache);
|
|
6
|
+
return {
|
|
7
|
+
...manifest,
|
|
8
|
+
collections: manifest.collections.map((c) => {
|
|
9
|
+
const file = typeof c.file === "string" ? c.file : "";
|
|
10
|
+
const rel = relative(root, resolve(root, file));
|
|
11
|
+
const inside = rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
12
|
+
return DUMP_FILE.test(file) && inside ? c : { ...c, file: "", error: c.error ?? `not a dump name the catalog accepts: ${file}` };
|
|
13
|
+
})
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
checkedManifest
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=dumpFiles.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/node/catalog/dumpFiles.ts"],
|
|
4
|
+
"sourcesContent": ["// Catalog dump files on this machine (AFD-134): the probe names them after the collections\n// installed in the environment, and the extension reads and deletes them in its cache.\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport type { Manifest } from '../../core/catalog/types';\n\n/**\n * A dump's name as the probe makes it: `<core minor>/<namespace>.<name>@<version>.json` (AFD-134).\n * Names and versions come from the collections installed in the environment, which a repository\n * or an image chooses, and the extension deletes and reads these files on this machine.\n */\nconst DUMP_FILE = /^\\d+\\.\\d+\\/[\\w-]+\\.[\\w-]+@[\\w.+*-]+\\.json$/;\n\n/** The manifest with every collection whose file would not name a dump inside `cache` failed. */\nexport function checkedManifest(manifest: Manifest, cache: string): Manifest {\n const root = resolve(cache);\n return {\n ...manifest,\n collections: manifest.collections.map((c) => {\n const file = typeof c.file === 'string' ? c.file : '';\n const rel = relative(root, resolve(root, file));\n const inside = rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);\n return DUMP_FILE.test(file) && inside\n ? c\n : { ...c, file: '', error: c.error ?? `not a dump name the catalog accepts: ${file}` };\n }),\n };\n}\n"],
|
|
5
|
+
"mappings": ";AAEA,SAAS,YAAY,UAAU,eAAe;AAQ9C,IAAM,YAAY;AAGX,SAAS,gBAAgB,UAAoB,OAAyB;AAC3E,QAAM,OAAO,QAAQ,KAAK;AAC1B,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa,SAAS,YAAY,IAAI,CAAC,MAAM;AAC3C,YAAM,OAAO,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AACnD,YAAM,MAAM,SAAS,MAAM,QAAQ,MAAM,IAAI,CAAC;AAC9C,YAAM,SAAS,QAAQ,MAAM,CAAC,IAAI,WAAW,IAAI,KAAK,CAAC,WAAW,GAAG;AACrE,aAAO,UAAU,KAAK,IAAI,KAAK,SAC3B,IACA,EAAE,GAAG,GAAG,MAAM,IAAI,OAAO,EAAE,SAAS,wCAAwC,IAAI,GAAG;AAAA,IACzF,CAAC;AAAA,EACH;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|