@zhuixin/engine-client 0.2.2 → 0.2.4

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/dist/index.cjs CHANGED
@@ -37,9 +37,11 @@ var import_node_child_process = require("child_process");
37
37
  var import_node_readline = __toESM(require("readline"), 1);
38
38
  var import_protocol = require("@zhuixin/protocol");
39
39
  var EngineClient = class {
40
- constructor(executable, childEnv = {}) {
40
+ constructor(executable, childEnv = {}, onChild, timeoutMs = 20 * 6e4) {
41
41
  this.executable = executable;
42
42
  this.childEnv = childEnv;
43
+ this.onChild = onChild;
44
+ this.timeoutMs = timeoutMs;
43
45
  }
44
46
  child = null;
45
47
  sequence = 0;
@@ -55,14 +57,35 @@ var EngineClient = class {
55
57
  APPTRACE_WORKSPACE_ROOT: workspacePath
56
58
  }
57
59
  });
60
+ this.onChild?.(this.child);
61
+ this.child.on("error", (error) => this.failAll(error.message));
62
+ this.child.on("exit", (code) => {
63
+ this.failAll(`Engine exited${code === null ? "" : ` with code ${code}`}`);
64
+ });
58
65
  const lines = import_node_readline.default.createInterface({ input: this.child.stdout });
59
66
  lines.on("line", (line) => {
60
67
  const parsed = import_protocol.engineResponseSchema.safeParse(JSON.parse(line));
61
68
  if (!parsed.success) return;
62
- this.pending.get(parsed.data.id)?.(parsed.data);
69
+ if (parsed.data.id === "unknown" && !parsed.data.ok) {
70
+ this.failAll(
71
+ parsed.data.error?.message ?? "Engine rejected the request"
72
+ );
73
+ return;
74
+ }
75
+ const entry = this.pending.get(parsed.data.id);
76
+ if (!entry) return;
77
+ clearTimeout(entry.timer);
63
78
  this.pending.delete(parsed.data.id);
79
+ entry.resolve(parsed.data);
64
80
  });
65
81
  }
82
+ failAll(message) {
83
+ for (const entry of this.pending.values()) {
84
+ clearTimeout(entry.timer);
85
+ entry.reject(new Error(message));
86
+ }
87
+ this.pending.clear();
88
+ }
66
89
  async execute(workspacePath, capability, input = {}) {
67
90
  await this.start(workspacePath);
68
91
  const id = `req-${++this.sequence}`;
@@ -84,12 +107,20 @@ var EngineClient = class {
84
107
  };
85
108
  return new Promise((resolve, reject) => {
86
109
  if (!this.child) return reject(new Error("Engine is not running"));
87
- this.pending.set(id, (response) => {
88
- if (!response.ok || !response.result) {
89
- reject(new Error(response.error?.message ?? "Engine request failed"));
90
- return;
110
+ const timer = setTimeout(() => {
111
+ this.pending.delete(id);
112
+ reject(new Error(`Engine request timed out after ${this.timeoutMs}ms`));
113
+ }, this.timeoutMs);
114
+ this.pending.set(id, {
115
+ timer,
116
+ reject,
117
+ resolve: (response) => {
118
+ if (!response.ok || !response.result) {
119
+ reject(new Error(response.error?.message ?? "Engine request failed"));
120
+ return;
121
+ }
122
+ resolve(response.result);
91
123
  }
92
- resolve(response.result);
93
124
  });
94
125
  this.child.stdin.write(`${JSON.stringify(request)}
95
126
  `);
@@ -98,7 +129,7 @@ var EngineClient = class {
98
129
  close() {
99
130
  this.child?.kill("SIGTERM");
100
131
  this.child = null;
101
- this.pending.clear();
132
+ this.failAll("Engine client closed");
102
133
  }
103
134
  };
104
135
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport readline from \"node:readline\";\nimport {\n PROTOCOL_VERSION,\n engineResponseSchema,\n type EngineCapability,\n type EngineRequest,\n type EngineResponse\n} from \"@zhuixin/protocol\";\n\nexport class EngineClient {\n private child: ChildProcessWithoutNullStreams | null = null;\n private sequence = 0;\n private readonly pending = new Map<string, (value: EngineResponse) => void>();\n\n constructor(\n private readonly executable: string,\n private readonly childEnv: NodeJS.ProcessEnv = {}\n ) {}\n\n async start(workspacePath: string): Promise<void> {\n if (this.child) return;\n const spec = this.executable.endsWith(\".js\")\n ? { command: process.execPath, args: [this.executable, \"engine\"] }\n : { command: this.executable, args: [\"engine\"] };\n this.child = spawn(spec.command, spec.args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: {\n ...process.env,\n ...this.childEnv,\n APPTRACE_WORKSPACE_ROOT: workspacePath\n }\n });\n const lines = readline.createInterface({ input: this.child.stdout });\n lines.on(\"line\", (line) => {\n const parsed = engineResponseSchema.safeParse(JSON.parse(line));\n if (!parsed.success) return;\n this.pending.get(parsed.data.id)?.(parsed.data);\n this.pending.delete(parsed.data.id);\n });\n }\n\n async execute(\n workspacePath: string,\n capability: EngineCapability,\n input: {\n targetId?: string;\n artifactId?: string;\n values?: Record<string, unknown>;\n license?: { licenseId?: string; features: string[] };\n licenseToken?: string;\n } = {}\n ): Promise<Record<string, unknown>> {\n await this.start(workspacePath);\n const id = `req-${++this.sequence}`;\n const request: EngineRequest = {\n id,\n protocolVersion: PROTOCOL_VERSION,\n operation: \"execute\",\n capability,\n workspacePath,\n ...(input.targetId ? { targetId: input.targetId } : {}),\n ...(input.artifactId ? { artifactId: input.artifactId } : {}),\n ...(input.values ? { input: input.values } : {}),\n ...(input.license || input.licenseToken\n ? {\n license: {\n ...(input.license ?? { features: [] }),\n ...(input.licenseToken ? { token: input.licenseToken } : {})\n }\n }\n : {})\n };\n return new Promise((resolve, reject) => {\n if (!this.child) return reject(new Error(\"Engine is not running\"));\n this.pending.set(id, (response) => {\n if (!response.ok || !response.result) {\n reject(new Error(response.error?.message ?? \"Engine request failed\"));\n return;\n }\n resolve(response.result);\n });\n this.child.stdin.write(`${JSON.stringify(request)}\\n`);\n });\n }\n\n close(): void {\n this.child?.kill(\"SIGTERM\");\n this.child = null;\n this.pending.clear();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAA2D;AAC3D,2BAAqB;AACrB,sBAMO;AAEA,IAAM,eAAN,MAAmB;AAAA,EAKxB,YACmB,YACA,WAA8B,CAAC,GAChD;AAFiB;AACA;AAAA,EAChB;AAAA,EAPK,QAA+C;AAAA,EAC/C,WAAW;AAAA,EACF,UAAU,oBAAI,IAA6C;AAAA,EAO5E,MAAM,MAAM,eAAsC;AAChD,QAAI,KAAK,MAAO;AAChB,UAAM,OAAO,KAAK,WAAW,SAAS,KAAK,IACvC,EAAE,SAAS,QAAQ,UAAU,MAAM,CAAC,KAAK,YAAY,QAAQ,EAAE,IAC/D,EAAE,SAAS,KAAK,YAAY,MAAM,CAAC,QAAQ,EAAE;AACjD,SAAK,YAAQ,iCAAM,KAAK,SAAS,KAAK,MAAM;AAAA,MAC1C,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,MAC9B,KAAK;AAAA,QACH,GAAG,QAAQ;AAAA,QACX,GAAG,KAAK;AAAA,QACR,yBAAyB;AAAA,MAC3B;AAAA,IACF,CAAC;AACD,UAAM,QAAQ,qBAAAA,QAAS,gBAAgB,EAAE,OAAO,KAAK,MAAM,OAAO,CAAC;AACnE,UAAM,GAAG,QAAQ,CAAC,SAAS;AACzB,YAAM,SAAS,qCAAqB,UAAU,KAAK,MAAM,IAAI,CAAC;AAC9D,UAAI,CAAC,OAAO,QAAS;AACrB,WAAK,QAAQ,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,IAAI;AAC9C,WAAK,QAAQ,OAAO,OAAO,KAAK,EAAE;AAAA,IACpC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QACJ,eACA,YACA,QAMI,CAAC,GAC6B;AAClC,UAAM,KAAK,MAAM,aAAa;AAC9B,UAAM,KAAK,OAAO,EAAE,KAAK,QAAQ;AACjC,UAAM,UAAyB;AAAA,MAC7B;AAAA,MACA,iBAAiB;AAAA,MACjB,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,MACrD,GAAI,MAAM,aAAa,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,MAC3D,GAAI,MAAM,SAAS,EAAE,OAAO,MAAM,OAAO,IAAI,CAAC;AAAA,MAC9C,GAAI,MAAM,WAAW,MAAM,eACvB;AAAA,QACE,SAAS;AAAA,UACP,GAAI,MAAM,WAAW,EAAE,UAAU,CAAC,EAAE;AAAA,UACpC,GAAI,MAAM,eAAe,EAAE,OAAO,MAAM,aAAa,IAAI,CAAC;AAAA,QAC5D;AAAA,MACF,IACA,CAAC;AAAA,IACP;AACA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,CAAC,KAAK,MAAO,QAAO,OAAO,IAAI,MAAM,uBAAuB,CAAC;AACjE,WAAK,QAAQ,IAAI,IAAI,CAAC,aAAa;AACjC,YAAI,CAAC,SAAS,MAAM,CAAC,SAAS,QAAQ;AACpC,iBAAO,IAAI,MAAM,SAAS,OAAO,WAAW,uBAAuB,CAAC;AACpE;AAAA,QACF;AACA,gBAAQ,SAAS,MAAM;AAAA,MACzB,CAAC;AACD,WAAK,MAAM,MAAM,MAAM,GAAG,KAAK,UAAU,OAAO,CAAC;AAAA,CAAI;AAAA,IACvD,CAAC;AAAA,EACH;AAAA,EAEA,QAAc;AACZ,SAAK,OAAO,KAAK,SAAS;AAC1B,SAAK,QAAQ;AACb,SAAK,QAAQ,MAAM;AAAA,EACrB;AACF;","names":["readline"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport readline from \"node:readline\";\nimport {\n PROTOCOL_VERSION,\n engineResponseSchema,\n type EngineCapability,\n type EngineRequest,\n type EngineResponse\n} from \"@zhuixin/protocol\";\n\nexport class EngineClient {\n private child: ChildProcessWithoutNullStreams | null = null;\n private sequence = 0;\n private readonly pending = new Map<\n string,\n {\n resolve: (value: EngineResponse) => void;\n reject: (error: Error) => void;\n timer: NodeJS.Timeout;\n }\n >();\n\n constructor(\n private readonly executable: string,\n private readonly childEnv: NodeJS.ProcessEnv = {},\n private readonly onChild?: (child: ChildProcessWithoutNullStreams) => void,\n private readonly timeoutMs = 20 * 60_000\n ) {}\n\n async start(workspacePath: string): Promise<void> {\n if (this.child) return;\n const spec = this.executable.endsWith(\".js\")\n ? { command: process.execPath, args: [this.executable, \"engine\"] }\n : { command: this.executable, args: [\"engine\"] };\n this.child = spawn(spec.command, spec.args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: {\n ...process.env,\n ...this.childEnv,\n APPTRACE_WORKSPACE_ROOT: workspacePath\n }\n });\n this.onChild?.(this.child);\n this.child.on(\"error\", (error) => this.failAll(error.message));\n this.child.on(\"exit\", (code) => {\n this.failAll(`Engine exited${code === null ? \"\" : ` with code ${code}`}`);\n });\n const lines = readline.createInterface({ input: this.child.stdout });\n lines.on(\"line\", (line) => {\n const parsed = engineResponseSchema.safeParse(JSON.parse(line));\n if (!parsed.success) return;\n if (parsed.data.id === \"unknown\" && !parsed.data.ok) {\n this.failAll(\n parsed.data.error?.message ?? \"Engine rejected the request\"\n );\n return;\n }\n const entry = this.pending.get(parsed.data.id);\n if (!entry) return;\n clearTimeout(entry.timer);\n this.pending.delete(parsed.data.id);\n entry.resolve(parsed.data);\n });\n }\n\n private failAll(message: string): void {\n for (const entry of this.pending.values()) {\n clearTimeout(entry.timer);\n entry.reject(new Error(message));\n }\n this.pending.clear();\n }\n\n async execute(\n workspacePath: string,\n capability: EngineCapability,\n input: {\n targetId?: string;\n artifactId?: string;\n values?: Record<string, unknown>;\n license?: { licenseId?: string; features: string[] };\n licenseToken?: string;\n } = {}\n ): Promise<Record<string, unknown>> {\n await this.start(workspacePath);\n const id = `req-${++this.sequence}`;\n const request: EngineRequest = {\n id,\n protocolVersion: PROTOCOL_VERSION,\n operation: \"execute\",\n capability,\n workspacePath,\n ...(input.targetId ? { targetId: input.targetId } : {}),\n ...(input.artifactId ? { artifactId: input.artifactId } : {}),\n ...(input.values ? { input: input.values } : {}),\n ...(input.license || input.licenseToken\n ? {\n license: {\n ...(input.license ?? { features: [] }),\n ...(input.licenseToken ? { token: input.licenseToken } : {})\n }\n }\n : {})\n };\n return new Promise((resolve, reject) => {\n if (!this.child) return reject(new Error(\"Engine is not running\"));\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(`Engine request timed out after ${this.timeoutMs}ms`));\n }, this.timeoutMs);\n this.pending.set(id, {\n timer,\n reject,\n resolve: (response) => {\n if (!response.ok || !response.result) {\n reject(new Error(response.error?.message ?? \"Engine request failed\"));\n return;\n }\n resolve(response.result);\n }\n });\n this.child.stdin.write(`${JSON.stringify(request)}\\n`);\n });\n }\n\n close(): void {\n this.child?.kill(\"SIGTERM\");\n this.child = null;\n this.failAll(\"Engine client closed\");\n }\n}\n"],"mappings":"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","names":["readline"]}
package/dist/index.d.cts CHANGED
@@ -1,13 +1,17 @@
1
+ import { ChildProcessWithoutNullStreams } from 'node:child_process';
1
2
  import { EngineCapability } from '@zhuixin/protocol';
2
3
 
3
4
  declare class EngineClient {
4
5
  private readonly executable;
5
6
  private readonly childEnv;
7
+ private readonly onChild?;
8
+ private readonly timeoutMs;
6
9
  private child;
7
10
  private sequence;
8
11
  private readonly pending;
9
- constructor(executable: string, childEnv?: NodeJS.ProcessEnv);
12
+ constructor(executable: string, childEnv?: NodeJS.ProcessEnv, onChild?: ((child: ChildProcessWithoutNullStreams) => void) | undefined, timeoutMs?: number);
10
13
  start(workspacePath: string): Promise<void>;
14
+ private failAll;
11
15
  execute(workspacePath: string, capability: EngineCapability, input?: {
12
16
  targetId?: string;
13
17
  artifactId?: string;
package/dist/index.d.ts CHANGED
@@ -1,13 +1,17 @@
1
+ import { ChildProcessWithoutNullStreams } from 'node:child_process';
1
2
  import { EngineCapability } from '@zhuixin/protocol';
2
3
 
3
4
  declare class EngineClient {
4
5
  private readonly executable;
5
6
  private readonly childEnv;
7
+ private readonly onChild?;
8
+ private readonly timeoutMs;
6
9
  private child;
7
10
  private sequence;
8
11
  private readonly pending;
9
- constructor(executable: string, childEnv?: NodeJS.ProcessEnv);
12
+ constructor(executable: string, childEnv?: NodeJS.ProcessEnv, onChild?: ((child: ChildProcessWithoutNullStreams) => void) | undefined, timeoutMs?: number);
10
13
  start(workspacePath: string): Promise<void>;
14
+ private failAll;
11
15
  execute(workspacePath: string, capability: EngineCapability, input?: {
12
16
  targetId?: string;
13
17
  artifactId?: string;
package/dist/index.js CHANGED
@@ -6,9 +6,11 @@ import {
6
6
  engineResponseSchema
7
7
  } from "@zhuixin/protocol";
8
8
  var EngineClient = class {
9
- constructor(executable, childEnv = {}) {
9
+ constructor(executable, childEnv = {}, onChild, timeoutMs = 20 * 6e4) {
10
10
  this.executable = executable;
11
11
  this.childEnv = childEnv;
12
+ this.onChild = onChild;
13
+ this.timeoutMs = timeoutMs;
12
14
  }
13
15
  child = null;
14
16
  sequence = 0;
@@ -24,14 +26,35 @@ var EngineClient = class {
24
26
  APPTRACE_WORKSPACE_ROOT: workspacePath
25
27
  }
26
28
  });
29
+ this.onChild?.(this.child);
30
+ this.child.on("error", (error) => this.failAll(error.message));
31
+ this.child.on("exit", (code) => {
32
+ this.failAll(`Engine exited${code === null ? "" : ` with code ${code}`}`);
33
+ });
27
34
  const lines = readline.createInterface({ input: this.child.stdout });
28
35
  lines.on("line", (line) => {
29
36
  const parsed = engineResponseSchema.safeParse(JSON.parse(line));
30
37
  if (!parsed.success) return;
31
- this.pending.get(parsed.data.id)?.(parsed.data);
38
+ if (parsed.data.id === "unknown" && !parsed.data.ok) {
39
+ this.failAll(
40
+ parsed.data.error?.message ?? "Engine rejected the request"
41
+ );
42
+ return;
43
+ }
44
+ const entry = this.pending.get(parsed.data.id);
45
+ if (!entry) return;
46
+ clearTimeout(entry.timer);
32
47
  this.pending.delete(parsed.data.id);
48
+ entry.resolve(parsed.data);
33
49
  });
34
50
  }
51
+ failAll(message) {
52
+ for (const entry of this.pending.values()) {
53
+ clearTimeout(entry.timer);
54
+ entry.reject(new Error(message));
55
+ }
56
+ this.pending.clear();
57
+ }
35
58
  async execute(workspacePath, capability, input = {}) {
36
59
  await this.start(workspacePath);
37
60
  const id = `req-${++this.sequence}`;
@@ -53,12 +76,20 @@ var EngineClient = class {
53
76
  };
54
77
  return new Promise((resolve, reject) => {
55
78
  if (!this.child) return reject(new Error("Engine is not running"));
56
- this.pending.set(id, (response) => {
57
- if (!response.ok || !response.result) {
58
- reject(new Error(response.error?.message ?? "Engine request failed"));
59
- return;
79
+ const timer = setTimeout(() => {
80
+ this.pending.delete(id);
81
+ reject(new Error(`Engine request timed out after ${this.timeoutMs}ms`));
82
+ }, this.timeoutMs);
83
+ this.pending.set(id, {
84
+ timer,
85
+ reject,
86
+ resolve: (response) => {
87
+ if (!response.ok || !response.result) {
88
+ reject(new Error(response.error?.message ?? "Engine request failed"));
89
+ return;
90
+ }
91
+ resolve(response.result);
60
92
  }
61
- resolve(response.result);
62
93
  });
63
94
  this.child.stdin.write(`${JSON.stringify(request)}
64
95
  `);
@@ -67,7 +98,7 @@ var EngineClient = class {
67
98
  close() {
68
99
  this.child?.kill("SIGTERM");
69
100
  this.child = null;
70
- this.pending.clear();
101
+ this.failAll("Engine client closed");
71
102
  }
72
103
  };
73
104
  export {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport readline from \"node:readline\";\nimport {\n PROTOCOL_VERSION,\n engineResponseSchema,\n type EngineCapability,\n type EngineRequest,\n type EngineResponse\n} from \"@zhuixin/protocol\";\n\nexport class EngineClient {\n private child: ChildProcessWithoutNullStreams | null = null;\n private sequence = 0;\n private readonly pending = new Map<string, (value: EngineResponse) => void>();\n\n constructor(\n private readonly executable: string,\n private readonly childEnv: NodeJS.ProcessEnv = {}\n ) {}\n\n async start(workspacePath: string): Promise<void> {\n if (this.child) return;\n const spec = this.executable.endsWith(\".js\")\n ? { command: process.execPath, args: [this.executable, \"engine\"] }\n : { command: this.executable, args: [\"engine\"] };\n this.child = spawn(spec.command, spec.args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: {\n ...process.env,\n ...this.childEnv,\n APPTRACE_WORKSPACE_ROOT: workspacePath\n }\n });\n const lines = readline.createInterface({ input: this.child.stdout });\n lines.on(\"line\", (line) => {\n const parsed = engineResponseSchema.safeParse(JSON.parse(line));\n if (!parsed.success) return;\n this.pending.get(parsed.data.id)?.(parsed.data);\n this.pending.delete(parsed.data.id);\n });\n }\n\n async execute(\n workspacePath: string,\n capability: EngineCapability,\n input: {\n targetId?: string;\n artifactId?: string;\n values?: Record<string, unknown>;\n license?: { licenseId?: string; features: string[] };\n licenseToken?: string;\n } = {}\n ): Promise<Record<string, unknown>> {\n await this.start(workspacePath);\n const id = `req-${++this.sequence}`;\n const request: EngineRequest = {\n id,\n protocolVersion: PROTOCOL_VERSION,\n operation: \"execute\",\n capability,\n workspacePath,\n ...(input.targetId ? { targetId: input.targetId } : {}),\n ...(input.artifactId ? { artifactId: input.artifactId } : {}),\n ...(input.values ? { input: input.values } : {}),\n ...(input.license || input.licenseToken\n ? {\n license: {\n ...(input.license ?? { features: [] }),\n ...(input.licenseToken ? { token: input.licenseToken } : {})\n }\n }\n : {})\n };\n return new Promise((resolve, reject) => {\n if (!this.child) return reject(new Error(\"Engine is not running\"));\n this.pending.set(id, (response) => {\n if (!response.ok || !response.result) {\n reject(new Error(response.error?.message ?? \"Engine request failed\"));\n return;\n }\n resolve(response.result);\n });\n this.child.stdin.write(`${JSON.stringify(request)}\\n`);\n });\n }\n\n close(): void {\n this.child?.kill(\"SIGTERM\");\n this.child = null;\n this.pending.clear();\n }\n}\n"],"mappings":";AAAA,SAAS,aAAkD;AAC3D,OAAO,cAAc;AACrB;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAEA,IAAM,eAAN,MAAmB;AAAA,EAKxB,YACmB,YACA,WAA8B,CAAC,GAChD;AAFiB;AACA;AAAA,EAChB;AAAA,EAPK,QAA+C;AAAA,EAC/C,WAAW;AAAA,EACF,UAAU,oBAAI,IAA6C;AAAA,EAO5E,MAAM,MAAM,eAAsC;AAChD,QAAI,KAAK,MAAO;AAChB,UAAM,OAAO,KAAK,WAAW,SAAS,KAAK,IACvC,EAAE,SAAS,QAAQ,UAAU,MAAM,CAAC,KAAK,YAAY,QAAQ,EAAE,IAC/D,EAAE,SAAS,KAAK,YAAY,MAAM,CAAC,QAAQ,EAAE;AACjD,SAAK,QAAQ,MAAM,KAAK,SAAS,KAAK,MAAM;AAAA,MAC1C,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,MAC9B,KAAK;AAAA,QACH,GAAG,QAAQ;AAAA,QACX,GAAG,KAAK;AAAA,QACR,yBAAyB;AAAA,MAC3B;AAAA,IACF,CAAC;AACD,UAAM,QAAQ,SAAS,gBAAgB,EAAE,OAAO,KAAK,MAAM,OAAO,CAAC;AACnE,UAAM,GAAG,QAAQ,CAAC,SAAS;AACzB,YAAM,SAAS,qBAAqB,UAAU,KAAK,MAAM,IAAI,CAAC;AAC9D,UAAI,CAAC,OAAO,QAAS;AACrB,WAAK,QAAQ,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,IAAI;AAC9C,WAAK,QAAQ,OAAO,OAAO,KAAK,EAAE;AAAA,IACpC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QACJ,eACA,YACA,QAMI,CAAC,GAC6B;AAClC,UAAM,KAAK,MAAM,aAAa;AAC9B,UAAM,KAAK,OAAO,EAAE,KAAK,QAAQ;AACjC,UAAM,UAAyB;AAAA,MAC7B;AAAA,MACA,iBAAiB;AAAA,MACjB,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,MACrD,GAAI,MAAM,aAAa,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,MAC3D,GAAI,MAAM,SAAS,EAAE,OAAO,MAAM,OAAO,IAAI,CAAC;AAAA,MAC9C,GAAI,MAAM,WAAW,MAAM,eACvB;AAAA,QACE,SAAS;AAAA,UACP,GAAI,MAAM,WAAW,EAAE,UAAU,CAAC,EAAE;AAAA,UACpC,GAAI,MAAM,eAAe,EAAE,OAAO,MAAM,aAAa,IAAI,CAAC;AAAA,QAC5D;AAAA,MACF,IACA,CAAC;AAAA,IACP;AACA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,CAAC,KAAK,MAAO,QAAO,OAAO,IAAI,MAAM,uBAAuB,CAAC;AACjE,WAAK,QAAQ,IAAI,IAAI,CAAC,aAAa;AACjC,YAAI,CAAC,SAAS,MAAM,CAAC,SAAS,QAAQ;AACpC,iBAAO,IAAI,MAAM,SAAS,OAAO,WAAW,uBAAuB,CAAC;AACpE;AAAA,QACF;AACA,gBAAQ,SAAS,MAAM;AAAA,MACzB,CAAC;AACD,WAAK,MAAM,MAAM,MAAM,GAAG,KAAK,UAAU,OAAO,CAAC;AAAA,CAAI;AAAA,IACvD,CAAC;AAAA,EACH;AAAA,EAEA,QAAc;AACZ,SAAK,OAAO,KAAK,SAAS;AAC1B,SAAK,QAAQ;AACb,SAAK,QAAQ,MAAM;AAAA,EACrB;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport readline from \"node:readline\";\nimport {\n PROTOCOL_VERSION,\n engineResponseSchema,\n type EngineCapability,\n type EngineRequest,\n type EngineResponse\n} from \"@zhuixin/protocol\";\n\nexport class EngineClient {\n private child: ChildProcessWithoutNullStreams | null = null;\n private sequence = 0;\n private readonly pending = new Map<\n string,\n {\n resolve: (value: EngineResponse) => void;\n reject: (error: Error) => void;\n timer: NodeJS.Timeout;\n }\n >();\n\n constructor(\n private readonly executable: string,\n private readonly childEnv: NodeJS.ProcessEnv = {},\n private readonly onChild?: (child: ChildProcessWithoutNullStreams) => void,\n private readonly timeoutMs = 20 * 60_000\n ) {}\n\n async start(workspacePath: string): Promise<void> {\n if (this.child) return;\n const spec = this.executable.endsWith(\".js\")\n ? { command: process.execPath, args: [this.executable, \"engine\"] }\n : { command: this.executable, args: [\"engine\"] };\n this.child = spawn(spec.command, spec.args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: {\n ...process.env,\n ...this.childEnv,\n APPTRACE_WORKSPACE_ROOT: workspacePath\n }\n });\n this.onChild?.(this.child);\n this.child.on(\"error\", (error) => this.failAll(error.message));\n this.child.on(\"exit\", (code) => {\n this.failAll(`Engine exited${code === null ? \"\" : ` with code ${code}`}`);\n });\n const lines = readline.createInterface({ input: this.child.stdout });\n lines.on(\"line\", (line) => {\n const parsed = engineResponseSchema.safeParse(JSON.parse(line));\n if (!parsed.success) return;\n if (parsed.data.id === \"unknown\" && !parsed.data.ok) {\n this.failAll(\n parsed.data.error?.message ?? \"Engine rejected the request\"\n );\n return;\n }\n const entry = this.pending.get(parsed.data.id);\n if (!entry) return;\n clearTimeout(entry.timer);\n this.pending.delete(parsed.data.id);\n entry.resolve(parsed.data);\n });\n }\n\n private failAll(message: string): void {\n for (const entry of this.pending.values()) {\n clearTimeout(entry.timer);\n entry.reject(new Error(message));\n }\n this.pending.clear();\n }\n\n async execute(\n workspacePath: string,\n capability: EngineCapability,\n input: {\n targetId?: string;\n artifactId?: string;\n values?: Record<string, unknown>;\n license?: { licenseId?: string; features: string[] };\n licenseToken?: string;\n } = {}\n ): Promise<Record<string, unknown>> {\n await this.start(workspacePath);\n const id = `req-${++this.sequence}`;\n const request: EngineRequest = {\n id,\n protocolVersion: PROTOCOL_VERSION,\n operation: \"execute\",\n capability,\n workspacePath,\n ...(input.targetId ? { targetId: input.targetId } : {}),\n ...(input.artifactId ? { artifactId: input.artifactId } : {}),\n ...(input.values ? { input: input.values } : {}),\n ...(input.license || input.licenseToken\n ? {\n license: {\n ...(input.license ?? { features: [] }),\n ...(input.licenseToken ? { token: input.licenseToken } : {})\n }\n }\n : {})\n };\n return new Promise((resolve, reject) => {\n if (!this.child) return reject(new Error(\"Engine is not running\"));\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(`Engine request timed out after ${this.timeoutMs}ms`));\n }, this.timeoutMs);\n this.pending.set(id, {\n timer,\n reject,\n resolve: (response) => {\n if (!response.ok || !response.result) {\n reject(new Error(response.error?.message ?? \"Engine request failed\"));\n return;\n }\n resolve(response.result);\n }\n });\n this.child.stdin.write(`${JSON.stringify(request)}\\n`);\n });\n }\n\n close(): void {\n this.child?.kill(\"SIGTERM\");\n this.child = null;\n this.failAll(\"Engine client closed\");\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhuixin/engine-client",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "license": "Apache-2.0",
5
5
  "description": "AppTrace client for the private analysis engine",
6
6
  "repository": {
@@ -22,7 +22,7 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@zhuixin/protocol": "0.2.2"
25
+ "@zhuixin/protocol": "0.2.4"
26
26
  },
27
27
  "devDependencies": {
28
28
  "tsup": "^8.5.0",