@ansible-flow/node 0.86.0 → 0.87.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Ansible Flow Designer for Node hosts: layout, the probe client, runs, the AWX client, the module catalog and SSH.
4
4
 
5
- Built from [Ansible Flow Designer](https://github.com/ChistokhinSV/ansible-flow-designer) 0.86.0
5
+ Built from [Ansible Flow Designer](https://github.com/ChistokhinSV/ansible-flow-designer) 0.87.0
6
6
  and released with it. Modules are imported by their path in the source tree, for example
7
7
  `@ansible-flow/node/ai/agents`.
8
8
 
@@ -98,6 +98,27 @@ export interface LaunchRequest {
98
98
  export declare const RUNNER_API = "v1";
99
99
  /** Why this client and the server do not fit for a flow in `format`, or undefined. */
100
100
  export declare function incompatible(meta: RunnerMeta, format: number): string | undefined;
101
+ /** AFD-284: what happened to which flows between two commits, and who did it. */
102
+ export interface RunnerChanges {
103
+ from: string;
104
+ to: string;
105
+ flows: {
106
+ file: string;
107
+ change: 'added' | 'changed' | 'removed';
108
+ authors: string[];
109
+ at?: string;
110
+ commit?: string;
111
+ }[];
112
+ }
113
+ /** AFD-284: where the project's branch is (`commit`), and each time it moves (`moved`). */
114
+ export type ProjectEvent = {
115
+ type: 'commit';
116
+ commit: string | null;
117
+ } | {
118
+ type: 'moved';
119
+ from?: string;
120
+ to: string;
121
+ };
101
122
  export interface RunnerOptions {
102
123
  verifyTls?: boolean;
103
124
  /** Send the token over plain http to another machine (a lab only). */
@@ -112,6 +133,14 @@ export declare class RunnerClient {
112
133
  constructor(url: string, token: string, options?: RunnerOptions);
113
134
  /** A run's page on the server. */
114
135
  runUrl(id: string): string;
136
+ /** Refuses a URL the token must not go to: another origin, or plain http without consent. */
137
+ private guard;
138
+ /**
139
+ * AFD-284: follows a project's event stream (server-sent events) until `stop` is called, going
140
+ * back after a dropped connection (5 s, then up to a minute between tries). `commit` comes at
141
+ * once and after each reconnect, so a move missed while away is noticed.
142
+ */
143
+ follow(project: string, onEvent: (e: ProjectEvent) => void): () => void;
115
144
  /** The answer's body as text, and its status. */
116
145
  private send;
117
146
  request<T>(method: string, path: string, body?: unknown, raw?: {
@@ -142,6 +171,16 @@ export declare class RunnerClient {
142
171
  commit: string;
143
172
  expiresAt: string;
144
173
  }>;
174
+ /** AFD-284: the flows changed between `from` and the project's commit (or `to`). */
175
+ changes: (project: string, from: string, to?: string) => Promise<RunnerChanges>;
176
+ /** AFD-284: one flow as the server reads it at a commit (its document in `snapshot.doc`). */
177
+ flowAt: (project: string, path: string, commit: string) => Promise<{
178
+ commit: string;
179
+ file: string;
180
+ snapshot: {
181
+ doc?: unknown;
182
+ };
183
+ }>;
145
184
  /** What one host did in one step of the run. */
146
185
  hostOutput: (id: string, node: string, host: string) => Promise<{
147
186
  entries?: unknown[];
@@ -47,17 +47,86 @@ var RunnerClient = class {
47
47
  runUrl(id) {
48
48
  return `${this.base}/runs/${enc(id)}`;
49
49
  }
50
- /** The answer's body as text, and its status. */
51
- send(method, path, body, raw) {
52
- const url = new URL(`${this.base}${path}`);
50
+ /** Refuses a URL the token must not go to: another origin, or plain http without consent. */
51
+ guard(url) {
53
52
  if (url.origin !== this.origin)
54
- return Promise.reject(new RunnerError(`Not sending the token to ${url.origin}`));
53
+ return new RunnerError(`Not sending the token to ${url.origin}`);
55
54
  if (cleartextTo(url) && !this.options.allowHttp)
56
- return Promise.reject(
57
- new RunnerError(
58
- `Not sending the token unencrypted to ${url.origin}: use an https address, or turn on afd.runner.allowHttp in your user settings (a lab server only)`
59
- )
55
+ return new RunnerError(
56
+ `Not sending the token unencrypted to ${url.origin}: use an https address, or turn on afd.runner.allowHttp in your user settings (a lab server only)`
60
57
  );
58
+ return void 0;
59
+ }
60
+ /**
61
+ * AFD-284: follows a project's event stream (server-sent events) until `stop` is called, going
62
+ * back after a dropped connection (5 s, then up to a minute between tries). `commit` comes at
63
+ * once and after each reconnect, so a move missed while away is noticed.
64
+ */
65
+ follow(project, onEvent) {
66
+ let stopped = false;
67
+ let req;
68
+ let retry;
69
+ let wait = 5e3;
70
+ const url = new URL(`${this.base}/api/v1/projects/${enc(project)}/events`);
71
+ const problem = this.guard(url);
72
+ const again = () => {
73
+ if (stopped) return;
74
+ retry = setTimeout(connect, wait);
75
+ wait = Math.min(wait * 2, 6e4);
76
+ };
77
+ const connect = () => {
78
+ if (stopped || problem) return;
79
+ const lib = url.protocol === "https:" ? https : http;
80
+ req = lib.request(
81
+ url,
82
+ {
83
+ method: "GET",
84
+ headers: { Authorization: `Bearer ${this.token}`, Accept: "text/event-stream" },
85
+ ...url.protocol === "https:" ? { rejectUnauthorized: this.options.verifyTls !== false } : {}
86
+ },
87
+ (res) => {
88
+ if (res.statusCode !== 200) {
89
+ res.resume();
90
+ if (res.statusCode === 401 || res.statusCode === 403 || res.statusCode === 404) return;
91
+ again();
92
+ return;
93
+ }
94
+ wait = 5e3;
95
+ res.setEncoding("utf8");
96
+ let buffer = "";
97
+ res.on("data", (chunk) => {
98
+ buffer += chunk;
99
+ for (let at = buffer.indexOf("\n\n"); at >= 0; at = buffer.indexOf("\n\n")) {
100
+ const block = buffer.slice(0, at);
101
+ buffer = buffer.slice(at + 2);
102
+ const type = /^event: (.*)$/m.exec(block)?.[1];
103
+ const data = /^data: (.*)$/m.exec(block)?.[1];
104
+ if (!data || type !== "commit" && type !== "moved") continue;
105
+ try {
106
+ onEvent({ type, ...JSON.parse(data) });
107
+ } catch {
108
+ }
109
+ }
110
+ });
111
+ res.on("end", again);
112
+ res.on("error", again);
113
+ }
114
+ );
115
+ req.on("error", again);
116
+ req.end();
117
+ };
118
+ connect();
119
+ return () => {
120
+ stopped = true;
121
+ clearTimeout(retry);
122
+ req?.destroy();
123
+ };
124
+ }
125
+ /** The answer's body as text, and its status. */
126
+ send(method, path, body, raw) {
127
+ const url = new URL(`${this.base}${path}`);
128
+ const refused = this.guard(url);
129
+ if (refused) return Promise.reject(refused);
61
130
  const lib = url.protocol === "https:" ? https : http;
62
131
  const data = raw?.bytes ?? (body === void 0 ? void 0 : JSON.stringify(body));
63
132
  const type = raw?.type ?? "application/json";
@@ -153,6 +222,16 @@ var RunnerClient = class {
153
222
  void 0,
154
223
  { bytes, type: "application/x-git-bundle" }
155
224
  );
225
+ /** AFD-284: the flows changed between `from` and the project's commit (or `to`). */
226
+ changes = (project, from, to) => this.request(
227
+ "GET",
228
+ `/api/v1/projects/${enc(project)}/changes?from=${enc(from)}${to ? `&to=${enc(to)}` : ""}`
229
+ );
230
+ /** AFD-284: one flow as the server reads it at a commit (its document in `snapshot.doc`). */
231
+ flowAt = (project, path, commit) => this.request(
232
+ "GET",
233
+ `/api/v1/projects/${enc(project)}/flows/view?path=${enc(path)}&commit=${enc(commit)}`
234
+ );
156
235
  /** What one host did in one step of the run. */
157
236
  hostOutput = (id, node, host) => this.request(
158
237
  "GET",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/node/runner/client.ts"],
4
- "sourcesContent": ["// A client of Ansible Flow Server (AFD-276): the routes the extension's server target uses, as\n// the designer's contract with the server names them (contract/server-api.json, AFD-280).\n// node:http(s) rather than fetch so TLS verification can be turned off for a lab server, as the\n// AWX client does; the token travels only in the Authorization header, to the configured server.\nimport * as http from 'node:http';\nimport * as https from 'node:https';\nimport { cleartextTo } from '../awx/client';\nimport { TIMEOUTS } from '../timeouts';\n\nexport class RunnerError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n /** The server's error code (`not_found`, `read_only_token`, \u2026). */\n readonly code?: string,\n /** A launch's problems, by field. */\n readonly problems?: { field?: string; message: string }[],\n ) {\n super(message);\n this.name = 'RunnerError';\n }\n}\n\nexport interface RunnerMeta {\n name: string;\n version: string;\n api: string;\n designer: { version: string; format: number; formats?: number[]; bundle?: string };\n features?: string[];\n}\n\nexport interface RunnerProject {\n id: string;\n key: string;\n name: string;\n gitPath: string;\n gitRef: string;\n commit?: string;\n}\n\nexport interface RunnerConfig {\n id: string;\n name: string;\n flowFile: string;\n mode: 'check' | 'real';\n inventory?: string;\n gitRef?: string;\n commit?: string;\n}\n\n/** What a launch would run (the server's PreparedLaunch, without its inputs). */\nexport interface RunnerLaunch {\n commit: string;\n ref?: string;\n flow: { file: string; key: string; name: string; version: number };\n mode: 'check' | 'real';\n inventory?: string;\n targets: string[];\n}\n\nexport interface RunnerRun {\n id: string;\n number: number;\n project: string;\n commit: string;\n flow: { file: string; key: string; name: string };\n mode: 'check' | 'real';\n state: 'queued' | 'running' | 'waiting' | 'succeeded' | 'failed' | 'cancelled' | 'lost';\n error?: string;\n endedAt?: string;\n /** AFD-282: a check run of someone's unpushed work. */\n dev?: true;\n}\n\nexport interface RunnerGate {\n node: { id: string; key: string; title: string };\n status: string;\n mayDecide: boolean;\n why?: string;\n}\n\nexport interface LaunchRequest {\n mode?: 'check' | 'real';\n /** AFD-282: a dev bundle's id: the server runs its commit, in check mode. */\n dev?: string;\n limit?: string;\n /** The flow's inputs as typed text, by name. */\n inputs?: Record<string, string>;\n}\n\n/** The REST API this client speaks. */\nexport const RUNNER_API = 'v1';\n\n/** Why this client and the server do not fit for a flow in `format`, or undefined. */\nexport function incompatible(meta: RunnerMeta, format: number): string | undefined {\n if (meta.name !== 'Ansible Flow Server') return 'The address is not an Ansible Flow Server';\n if (meta.api !== RUNNER_API)\n return `The server speaks API ${meta.api}; this extension speaks ${RUNNER_API}: update the one that is older`;\n const formats = meta.designer.formats ?? [meta.designer.format];\n if (!formats.includes(format))\n return `The flow is in format ${format}; the server (${meta.version}) reads formats ${formats.join(', ')}: upgrade the server`;\n return undefined;\n}\n\nexport interface RunnerOptions {\n verifyTls?: boolean;\n /** Send the token over plain http to another machine (a lab only). */\n allowHttp?: boolean;\n timeoutMs?: number;\n}\n\nconst MAX_BODY = 32 * 1024 * 1024;\nconst enc = encodeURIComponent;\n\nexport class RunnerClient {\n readonly base: string;\n private readonly origin: string;\n\n constructor(\n url: string,\n private readonly token: string,\n private readonly options: RunnerOptions = {},\n ) {\n this.base = url.replace(/\\/+$/, '');\n this.origin = new URL(this.base).origin;\n }\n\n /** A run's page on the server. */\n runUrl(id: string): string {\n return `${this.base}/runs/${enc(id)}`;\n }\n\n /** The answer's body as text, and its status. */\n private send(\n method: string,\n path: string,\n body?: unknown,\n /** A body sent as it is, with its content type (a git bundle). */\n raw?: { bytes: Buffer; type: string },\n ): Promise<{ status: number; text: string }> {\n const url = new URL(`${this.base}${path}`);\n if (url.origin !== this.origin)\n return Promise.reject(new RunnerError(`Not sending the token to ${url.origin}`));\n if (cleartextTo(url) && !this.options.allowHttp)\n return Promise.reject(\n new RunnerError(\n `Not sending the token unencrypted to ${url.origin}: use an https address, or turn on afd.runner.allowHttp in your user settings (a lab server only)`,\n ),\n );\n const lib = url.protocol === 'https:' ? https : http;\n const data = raw?.bytes ?? (body === undefined ? undefined : JSON.stringify(body));\n const type = raw?.type ?? 'application/json';\n return new Promise((resolve, reject) => {\n const req = lib.request(\n url,\n {\n method,\n headers: {\n Authorization: `Bearer ${this.token}`,\n Accept: 'application/json',\n ...(data ? { 'Content-Type': type, 'Content-Length': Buffer.byteLength(data) } : {}),\n },\n timeout: this.options.timeoutMs ?? TIMEOUTS.runnerRequest,\n ...(url.protocol === 'https:'\n ? { rejectUnauthorized: this.options.verifyTls !== false }\n : {}),\n },\n (res) => {\n const chunks: Buffer[] = [];\n let size = 0;\n res.on('data', (c: Buffer) => {\n size += c.length;\n if (size > MAX_BODY) {\n req.destroy(new Error('the answer is too large'));\n return;\n }\n chunks.push(c);\n });\n res.on('end', () =>\n resolve({ status: res.statusCode ?? 0, text: Buffer.concat(chunks).toString('utf8') }),\n );\n res.on('error', reject);\n },\n );\n req.on('timeout', () => req.destroy(new Error('no answer in time')));\n req.on('error', (e) =>\n reject(new RunnerError(`The server at ${this.base} does not answer: ${e.message}`)),\n );\n req.end(data);\n });\n }\n\n async request<T>(\n method: string,\n path: string,\n body?: unknown,\n raw?: { bytes: Buffer; type: string },\n ): Promise<T> {\n const { status, text } = await this.send(method, path, body, raw);\n let json: unknown;\n try {\n json = text ? JSON.parse(text) : undefined;\n } catch {\n json = undefined;\n }\n if (status >= 200 && status < 300) return json as T;\n const e = (json ?? {}) as {\n error?: string;\n message?: string;\n problems?: { field?: string; message: string }[];\n };\n const why =\n status === 401\n ? 'The server does not take the token: set it again (it may have expired or been revoked)'\n : status === 413\n ? 'The work is larger than the server takes (AFS_DEV_BUNDLE_MAX_MB)'\n : (e.message ?? `The server answered ${status}`);\n throw new RunnerError(why, status, e.error, e.problems);\n }\n\n /** The output of a request whose answer is plain text. */\n async text(path: string): Promise<string> {\n const { status, text } = await this.send('GET', path);\n if (status >= 200 && status < 300) return text;\n throw new RunnerError(`The server answered ${status}`, status);\n }\n\n meta = () => this.request<RunnerMeta>('GET', '/api/v1/meta');\n me = () => this.request<{ login: string; name: string }>('GET', '/api/v1/session');\n projects = (workspace: string) =>\n this.request<RunnerProject[]>('GET', `/api/v1/workspaces/${enc(workspace)}/projects`);\n project = (id: string) => this.request<RunnerProject>('GET', `/api/v1/projects/${enc(id)}`);\n configs = (project: string) =>\n this.request<RunnerConfig[]>('GET', `/api/v1/projects/${enc(project)}/launch-configs`);\n check = async (config: string, req: LaunchRequest) =>\n (\n await this.request<{ launch: RunnerLaunch }>(\n 'POST',\n `/api/v1/launch-configs/${enc(config)}/check`,\n req,\n )\n ).launch;\n launch = (config: string, req: LaunchRequest) =>\n this.request<RunnerRun>('POST', `/api/v1/launch-configs/${enc(config)}/launch`, req);\n run = (id: string) => this.request<RunnerRun>('GET', `/api/v1/runs/${enc(id)}`);\n /** How the run went, as the designer shows it (the server's RunView); 404 until it has one. */\n view = async (id: string) => {\n try {\n return await this.request<Record<string, unknown>>('GET', `/api/v1/runs/${enc(id)}/view`);\n } catch (e) {\n if (e instanceof RunnerError && e.status === 404) return undefined;\n throw e;\n }\n };\n gates = (id: string) => this.request<RunnerGate[]>('GET', `/api/v1/runs/${enc(id)}/approvals`);\n cancel = (id: string) => this.request<RunnerRun>('POST', `/api/v1/runs/${enc(id)}/cancel`);\n decide = (id: string, node: string, decision: 'approved' | 'rejected', comment?: string) =>\n this.request<unknown>('POST', `/api/v1/runs/${enc(id)}/approvals/${enc(node)}`, {\n decision,\n ...(comment?.trim() ? { comment: comment.trim() } : {}),\n });\n /** AFD-282: unpushed work for a dev run; resolves with the bundle's id and commit. */\n devBundle = (project: string, bytes: Buffer) =>\n this.request<{ id: string; commit: string; expiresAt: string }>(\n 'POST',\n `/api/v1/projects/${enc(project)}/dev-bundles`,\n undefined,\n { bytes, type: 'application/x-git-bundle' },\n );\n /** What one host did in one step of the run. */\n hostOutput = (id: string, node: string, host: string) =>\n this.request<{ entries?: unknown[] }>(\n 'GET',\n `/api/v1/runs/${enc(id)}/hosts/${enc(node)}/${enc(host)}`,\n );\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// A client of Ansible Flow Server (AFD-276): the routes the extension's server target uses, as\n// the designer's contract with the server names them (contract/server-api.json, AFD-280).\n// node:http(s) rather than fetch so TLS verification can be turned off for a lab server, as the\n// AWX client does; the token travels only in the Authorization header, to the configured server.\nimport * as http from 'node:http';\nimport * as https from 'node:https';\nimport { cleartextTo } from '../awx/client';\nimport { TIMEOUTS } from '../timeouts';\n\nexport class RunnerError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n /** The server's error code (`not_found`, `read_only_token`, \u2026). */\n readonly code?: string,\n /** A launch's problems, by field. */\n readonly problems?: { field?: string; message: string }[],\n ) {\n super(message);\n this.name = 'RunnerError';\n }\n}\n\nexport interface RunnerMeta {\n name: string;\n version: string;\n api: string;\n designer: { version: string; format: number; formats?: number[]; bundle?: string };\n features?: string[];\n}\n\nexport interface RunnerProject {\n id: string;\n key: string;\n name: string;\n gitPath: string;\n gitRef: string;\n commit?: string;\n}\n\nexport interface RunnerConfig {\n id: string;\n name: string;\n flowFile: string;\n mode: 'check' | 'real';\n inventory?: string;\n gitRef?: string;\n commit?: string;\n}\n\n/** What a launch would run (the server's PreparedLaunch, without its inputs). */\nexport interface RunnerLaunch {\n commit: string;\n ref?: string;\n flow: { file: string; key: string; name: string; version: number };\n mode: 'check' | 'real';\n inventory?: string;\n targets: string[];\n}\n\nexport interface RunnerRun {\n id: string;\n number: number;\n project: string;\n commit: string;\n flow: { file: string; key: string; name: string };\n mode: 'check' | 'real';\n state: 'queued' | 'running' | 'waiting' | 'succeeded' | 'failed' | 'cancelled' | 'lost';\n error?: string;\n endedAt?: string;\n /** AFD-282: a check run of someone's unpushed work. */\n dev?: true;\n}\n\nexport interface RunnerGate {\n node: { id: string; key: string; title: string };\n status: string;\n mayDecide: boolean;\n why?: string;\n}\n\nexport interface LaunchRequest {\n mode?: 'check' | 'real';\n /** AFD-282: a dev bundle's id: the server runs its commit, in check mode. */\n dev?: string;\n limit?: string;\n /** The flow's inputs as typed text, by name. */\n inputs?: Record<string, string>;\n}\n\n/** The REST API this client speaks. */\nexport const RUNNER_API = 'v1';\n\n/** Why this client and the server do not fit for a flow in `format`, or undefined. */\nexport function incompatible(meta: RunnerMeta, format: number): string | undefined {\n if (meta.name !== 'Ansible Flow Server') return 'The address is not an Ansible Flow Server';\n if (meta.api !== RUNNER_API)\n return `The server speaks API ${meta.api}; this extension speaks ${RUNNER_API}: update the one that is older`;\n const formats = meta.designer.formats ?? [meta.designer.format];\n if (!formats.includes(format))\n return `The flow is in format ${format}; the server (${meta.version}) reads formats ${formats.join(', ')}: upgrade the server`;\n return undefined;\n}\n\n/** AFD-284: what happened to which flows between two commits, and who did it. */\nexport interface RunnerChanges {\n from: string;\n to: string;\n flows: {\n file: string;\n change: 'added' | 'changed' | 'removed';\n authors: string[];\n at?: string;\n commit?: string;\n }[];\n}\n\n/** AFD-284: where the project's branch is (`commit`), and each time it moves (`moved`). */\nexport type ProjectEvent =\n | { type: 'commit'; commit: string | null }\n | { type: 'moved'; from?: string; to: string };\n\nexport interface RunnerOptions {\n verifyTls?: boolean;\n /** Send the token over plain http to another machine (a lab only). */\n allowHttp?: boolean;\n timeoutMs?: number;\n}\n\nconst MAX_BODY = 32 * 1024 * 1024;\nconst enc = encodeURIComponent;\n\nexport class RunnerClient {\n readonly base: string;\n private readonly origin: string;\n\n constructor(\n url: string,\n private readonly token: string,\n private readonly options: RunnerOptions = {},\n ) {\n this.base = url.replace(/\\/+$/, '');\n this.origin = new URL(this.base).origin;\n }\n\n /** A run's page on the server. */\n runUrl(id: string): string {\n return `${this.base}/runs/${enc(id)}`;\n }\n\n /** Refuses a URL the token must not go to: another origin, or plain http without consent. */\n private guard(url: URL): RunnerError | undefined {\n if (url.origin !== this.origin)\n return new RunnerError(`Not sending the token to ${url.origin}`);\n if (cleartextTo(url) && !this.options.allowHttp)\n return new RunnerError(\n `Not sending the token unencrypted to ${url.origin}: use an https address, or turn on afd.runner.allowHttp in your user settings (a lab server only)`,\n );\n return undefined;\n }\n\n /**\n * AFD-284: follows a project's event stream (server-sent events) until `stop` is called, going\n * back after a dropped connection (5 s, then up to a minute between tries). `commit` comes at\n * once and after each reconnect, so a move missed while away is noticed.\n */\n follow(project: string, onEvent: (e: ProjectEvent) => void): () => void {\n let stopped = false;\n let req: http.ClientRequest | undefined;\n let retry: NodeJS.Timeout | undefined;\n let wait = 5_000;\n const url = new URL(`${this.base}/api/v1/projects/${enc(project)}/events`);\n const problem = this.guard(url);\n const again = () => {\n if (stopped) return;\n retry = setTimeout(connect, wait);\n wait = Math.min(wait * 2, 60_000);\n };\n const connect = () => {\n if (stopped || problem) return;\n const lib = url.protocol === 'https:' ? https : http;\n req = lib.request(\n url,\n {\n method: 'GET',\n headers: { Authorization: `Bearer ${this.token}`, Accept: 'text/event-stream' },\n ...(url.protocol === 'https:'\n ? { rejectUnauthorized: this.options.verifyTls !== false }\n : {}),\n },\n (res) => {\n if (res.statusCode !== 200) {\n res.resume();\n // A token the server refuses, or a project gone: trying again changes nothing.\n if (res.statusCode === 401 || res.statusCode === 403 || res.statusCode === 404) return;\n again();\n return;\n }\n wait = 5_000;\n res.setEncoding('utf8');\n let buffer = '';\n res.on('data', (chunk: string) => {\n buffer += chunk;\n for (let at = buffer.indexOf('\\n\\n'); at >= 0; at = buffer.indexOf('\\n\\n')) {\n const block = buffer.slice(0, at);\n buffer = buffer.slice(at + 2);\n const type = /^event: (.*)$/m.exec(block)?.[1];\n const data = /^data: (.*)$/m.exec(block)?.[1];\n if (!data || (type !== 'commit' && type !== 'moved')) continue;\n try {\n onEvent({ type, ...JSON.parse(data) } as ProjectEvent);\n } catch {\n // A line it cannot read: the next event says where the branch is.\n }\n }\n });\n res.on('end', again);\n res.on('error', again);\n },\n );\n req.on('error', again);\n req.end();\n };\n connect();\n return () => {\n stopped = true;\n clearTimeout(retry);\n req?.destroy();\n };\n }\n\n /** The answer's body as text, and its status. */\n private send(\n method: string,\n path: string,\n body?: unknown,\n /** A body sent as it is, with its content type (a git bundle). */\n raw?: { bytes: Buffer; type: string },\n ): Promise<{ status: number; text: string }> {\n const url = new URL(`${this.base}${path}`);\n const refused = this.guard(url);\n if (refused) return Promise.reject(refused);\n const lib = url.protocol === 'https:' ? https : http;\n const data = raw?.bytes ?? (body === undefined ? undefined : JSON.stringify(body));\n const type = raw?.type ?? 'application/json';\n return new Promise((resolve, reject) => {\n const req = lib.request(\n url,\n {\n method,\n headers: {\n Authorization: `Bearer ${this.token}`,\n Accept: 'application/json',\n ...(data ? { 'Content-Type': type, 'Content-Length': Buffer.byteLength(data) } : {}),\n },\n timeout: this.options.timeoutMs ?? TIMEOUTS.runnerRequest,\n ...(url.protocol === 'https:'\n ? { rejectUnauthorized: this.options.verifyTls !== false }\n : {}),\n },\n (res) => {\n const chunks: Buffer[] = [];\n let size = 0;\n res.on('data', (c: Buffer) => {\n size += c.length;\n if (size > MAX_BODY) {\n req.destroy(new Error('the answer is too large'));\n return;\n }\n chunks.push(c);\n });\n res.on('end', () =>\n resolve({ status: res.statusCode ?? 0, text: Buffer.concat(chunks).toString('utf8') }),\n );\n res.on('error', reject);\n },\n );\n req.on('timeout', () => req.destroy(new Error('no answer in time')));\n req.on('error', (e) =>\n reject(new RunnerError(`The server at ${this.base} does not answer: ${e.message}`)),\n );\n req.end(data);\n });\n }\n\n async request<T>(\n method: string,\n path: string,\n body?: unknown,\n raw?: { bytes: Buffer; type: string },\n ): Promise<T> {\n const { status, text } = await this.send(method, path, body, raw);\n let json: unknown;\n try {\n json = text ? JSON.parse(text) : undefined;\n } catch {\n json = undefined;\n }\n if (status >= 200 && status < 300) return json as T;\n const e = (json ?? {}) as {\n error?: string;\n message?: string;\n problems?: { field?: string; message: string }[];\n };\n const why =\n status === 401\n ? 'The server does not take the token: set it again (it may have expired or been revoked)'\n : status === 413\n ? 'The work is larger than the server takes (AFS_DEV_BUNDLE_MAX_MB)'\n : (e.message ?? `The server answered ${status}`);\n throw new RunnerError(why, status, e.error, e.problems);\n }\n\n /** The output of a request whose answer is plain text. */\n async text(path: string): Promise<string> {\n const { status, text } = await this.send('GET', path);\n if (status >= 200 && status < 300) return text;\n throw new RunnerError(`The server answered ${status}`, status);\n }\n\n meta = () => this.request<RunnerMeta>('GET', '/api/v1/meta');\n me = () => this.request<{ login: string; name: string }>('GET', '/api/v1/session');\n projects = (workspace: string) =>\n this.request<RunnerProject[]>('GET', `/api/v1/workspaces/${enc(workspace)}/projects`);\n project = (id: string) => this.request<RunnerProject>('GET', `/api/v1/projects/${enc(id)}`);\n configs = (project: string) =>\n this.request<RunnerConfig[]>('GET', `/api/v1/projects/${enc(project)}/launch-configs`);\n check = async (config: string, req: LaunchRequest) =>\n (\n await this.request<{ launch: RunnerLaunch }>(\n 'POST',\n `/api/v1/launch-configs/${enc(config)}/check`,\n req,\n )\n ).launch;\n launch = (config: string, req: LaunchRequest) =>\n this.request<RunnerRun>('POST', `/api/v1/launch-configs/${enc(config)}/launch`, req);\n run = (id: string) => this.request<RunnerRun>('GET', `/api/v1/runs/${enc(id)}`);\n /** How the run went, as the designer shows it (the server's RunView); 404 until it has one. */\n view = async (id: string) => {\n try {\n return await this.request<Record<string, unknown>>('GET', `/api/v1/runs/${enc(id)}/view`);\n } catch (e) {\n if (e instanceof RunnerError && e.status === 404) return undefined;\n throw e;\n }\n };\n gates = (id: string) => this.request<RunnerGate[]>('GET', `/api/v1/runs/${enc(id)}/approvals`);\n cancel = (id: string) => this.request<RunnerRun>('POST', `/api/v1/runs/${enc(id)}/cancel`);\n decide = (id: string, node: string, decision: 'approved' | 'rejected', comment?: string) =>\n this.request<unknown>('POST', `/api/v1/runs/${enc(id)}/approvals/${enc(node)}`, {\n decision,\n ...(comment?.trim() ? { comment: comment.trim() } : {}),\n });\n /** AFD-282: unpushed work for a dev run; resolves with the bundle's id and commit. */\n devBundle = (project: string, bytes: Buffer) =>\n this.request<{ id: string; commit: string; expiresAt: string }>(\n 'POST',\n `/api/v1/projects/${enc(project)}/dev-bundles`,\n undefined,\n { bytes, type: 'application/x-git-bundle' },\n );\n /** AFD-284: the flows changed between `from` and the project's commit (or `to`). */\n changes = (project: string, from: string, to?: string) =>\n this.request<RunnerChanges>(\n 'GET',\n `/api/v1/projects/${enc(project)}/changes?from=${enc(from)}${to ? `&to=${enc(to)}` : ''}`,\n );\n /** AFD-284: one flow as the server reads it at a commit (its document in `snapshot.doc`). */\n flowAt = (project: string, path: string, commit: string) =>\n this.request<{ commit: string; file: string; snapshot: { doc?: unknown } }>(\n 'GET',\n `/api/v1/projects/${enc(project)}/flows/view?path=${enc(path)}&commit=${enc(commit)}`,\n );\n /** What one host did in one step of the run. */\n hostOutput = (id: string, node: string, host: string) =>\n this.request<{ entries?: unknown[] }>(\n 'GET',\n `/api/v1/runs/${enc(id)}/hosts/${enc(node)}/${enc(host)}`,\n );\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ansible-flow/node",
3
- "version": "0.86.0",
3
+ "version": "0.87.0",
4
4
  "description": "Ansible Flow Designer for Node hosts: layout, the probe client, runs, the AWX client, the module catalog and SSH.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/ChistokhinSV/ansible-flow-designer/blob/main/docs/packages.md",
@@ -26,7 +26,7 @@
26
26
  "node": ">=22.13"
27
27
  },
28
28
  "dependencies": {
29
- "@ansible-flow/core": "0.86.0",
29
+ "@ansible-flow/core": "0.87.0",
30
30
  "elkjs": "0.12.0",
31
31
  "jsonc-parser": "3.3.1",
32
32
  "yaml": "2.9.1"