prool 0.0.4 → 0.0.5

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/_lib/exports/index.d.ts +1 -1
  3. package/_lib/exports/processes.d.ts +2 -0
  4. package/_lib/exports/processes.d.ts.map +1 -0
  5. package/_lib/exports/processes.js +2 -0
  6. package/_lib/exports/processes.js.map +1 -0
  7. package/_lib/instance.d.ts +4 -4
  8. package/_lib/instance.d.ts.map +1 -1
  9. package/_lib/instance.js +3 -2
  10. package/_lib/instance.js.map +1 -1
  11. package/_lib/instances/anvil.d.ts +1 -5
  12. package/_lib/instances/anvil.d.ts.map +1 -1
  13. package/_lib/instances/anvil.js +18 -44
  14. package/_lib/instances/anvil.js.map +1 -1
  15. package/_lib/instances/stackup.d.ts +86 -0
  16. package/_lib/instances/stackup.d.ts.map +1 -0
  17. package/_lib/instances/stackup.js +89 -0
  18. package/_lib/instances/stackup.js.map +1 -0
  19. package/_lib/pool.d.ts +6 -6
  20. package/_lib/pool.d.ts.map +1 -1
  21. package/_lib/pool.js +5 -2
  22. package/_lib/pool.js.map +1 -1
  23. package/_lib/processes/execa.d.ts +27 -0
  24. package/_lib/processes/execa.d.ts.map +1 -0
  25. package/_lib/processes/execa.js +63 -0
  26. package/_lib/processes/execa.js.map +1 -0
  27. package/_lib/server.d.ts +3 -3
  28. package/_lib/server.d.ts.map +1 -1
  29. package/_lib/utils.d.ts +4 -3
  30. package/_lib/utils.d.ts.map +1 -1
  31. package/_lib/utils.js +2 -2
  32. package/_lib/utils.js.map +1 -1
  33. package/exports/index.ts +2 -2
  34. package/exports/processes.test.ts +10 -0
  35. package/exports/processes.ts +8 -0
  36. package/instance.test.ts +2 -2
  37. package/instance.ts +15 -10
  38. package/instances/anvil.test.ts +5 -3
  39. package/instances/anvil.ts +21 -49
  40. package/instances/stackup.test.ts +106 -0
  41. package/instances/stackup.ts +162 -0
  42. package/package.json +6 -2
  43. package/pool.test.ts +195 -171
  44. package/pool.ts +18 -9
  45. package/processes/execa.test.ts +143 -0
  46. package/processes/execa.ts +100 -0
  47. package/server.test.ts +91 -97
  48. package/server.ts +4 -4
  49. package/tsconfig.build.tsbuildinfo +1 -1
  50. package/utils.ts +11 -9
@@ -0,0 +1,63 @@
1
+ import { execa as exec } from 'execa';
2
+ import { stripColors } from '../utils.js';
3
+ export function execa(parameters) {
4
+ const { name } = parameters;
5
+ let process;
6
+ async function stop() {
7
+ const killed = process.kill();
8
+ if (!killed)
9
+ return;
10
+ return new Promise((resolve) => process.on('close', resolve));
11
+ }
12
+ return {
13
+ _internal: {
14
+ get process() {
15
+ return process;
16
+ },
17
+ },
18
+ name,
19
+ start(command, { emitter, resolver, status }) {
20
+ const { promise, resolve, reject } = Promise.withResolvers();
21
+ process = command(exec({
22
+ cleanup: true,
23
+ reject: false,
24
+ }));
25
+ resolver({
26
+ process,
27
+ async reject(data) {
28
+ await stop();
29
+ reject(new Error(`Failed to start process "${name}": ${data.toString()}`));
30
+ },
31
+ resolve() {
32
+ emitter.emit('listening');
33
+ return resolve();
34
+ },
35
+ });
36
+ process.stdout.on('data', (data) => {
37
+ const message = stripColors(data.toString());
38
+ emitter.emit('message', message);
39
+ emitter.emit('stdout', message);
40
+ });
41
+ process.stderr.on('data', async (data) => {
42
+ const message = stripColors(data.toString());
43
+ emitter.emit('message', message);
44
+ emitter.emit('stderr', message);
45
+ });
46
+ process.on('close', () => process.removeAllListeners());
47
+ process.on('exit', (code, signal) => {
48
+ emitter.emit('exit', code, signal);
49
+ if (!code) {
50
+ process.removeAllListeners();
51
+ if (status === 'starting')
52
+ reject(new Error(`Failed to start process "${name}": exited.`));
53
+ }
54
+ });
55
+ return promise;
56
+ },
57
+ async stop() {
58
+ process.removeAllListeners();
59
+ await stop();
60
+ },
61
+ };
62
+ }
63
+ //# sourceMappingURL=execa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execa.js","sourceRoot":"","sources":["../../processes/execa.ts"],"names":[],"mappings":"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"}
package/_lib/server.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  import { type IncomingMessage, type Server, type ServerResponse } from 'node:http';
4
4
  import type { AddressInfo } from 'node:net';
5
5
  import { type DefinePoolParameters } from './pool.js';
6
- export type createServerParameters = DefinePoolParameters & ({
6
+ export type CreateServerParameters = DefinePoolParameters<number> & ({
7
7
  /** Host to run the server on. */
8
8
  host?: string | undefined;
9
9
  /** Port to run the server on. */
@@ -12,10 +12,10 @@ export type createServerParameters = DefinePoolParameters & ({
12
12
  host?: undefined;
13
13
  port?: undefined;
14
14
  });
15
- export type createServerReturnType = Omit<Server<typeof IncomingMessage, typeof ServerResponse>, 'address'> & {
15
+ export type CreateServerReturnType = Omit<Server<typeof IncomingMessage, typeof ServerResponse>, 'address'> & {
16
16
  address(): AddressInfo | null;
17
17
  start(): Promise<() => Promise<void>>;
18
18
  stop(): Promise<void>;
19
19
  };
20
- export declare function createServer(parameters: createServerParameters): createServerReturnType;
20
+ export declare function createServer(parameters: CreateServerParameters): CreateServerReturnType;
21
21
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":";;AAAA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,MAAM,EACX,KAAK,cAAc,EAEpB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAG3C,OAAO,EAAE,KAAK,oBAAoB,EAAc,MAAM,WAAW,CAAA;AAKjE,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,GACvD,CACI;IACE,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAA;CACb,GACD;IACE,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,CACJ,CAAA;AAEH,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,MAAM,CAAC,OAAO,eAAe,EAAE,OAAO,cAAc,CAAC,EACrD,SAAS,CACV,GAAG;IACF,OAAO,IAAI,WAAW,GAAG,IAAI,CAAA;IAC7B,KAAK,IAAI,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;IACrC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB,CAAA;AAED,wBAAgB,YAAY,CAC1B,UAAU,EAAE,sBAAsB,GACjC,sBAAsB,CAmGxB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":";;AAAA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,MAAM,EACX,KAAK,cAAc,EAEpB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAG3C,OAAO,EAAE,KAAK,oBAAoB,EAAc,MAAM,WAAW,CAAA;AAKjE,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,CAAC,MAAM,CAAC,GAC/D,CACI;IACE,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAA;CACb,GACD;IACE,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,CACJ,CAAA;AAEH,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,MAAM,CAAC,OAAO,eAAe,EAAE,OAAO,cAAc,CAAC,EACrD,SAAS,CACV,GAAG;IACF,OAAO,IAAI,WAAW,GAAG,IAAI,CAAA;IAC7B,KAAK,IAAI,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;IACrC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB,CAAA;AAED,wBAAgB,YAAY,CAC1B,UAAU,EAAE,sBAAsB,GACjC,sBAAsB,CAmGxB"}
package/_lib/utils.d.ts CHANGED
@@ -16,15 +16,16 @@ export declare function extractPath(request: string): ExtractPathReturnType;
16
16
  * @returns The stripped string.
17
17
  */
18
18
  export declare function stripColors(message: string): string;
19
+ export type ToArgsParameters = {
20
+ [key: string]: Record<string, string> | string | boolean | number | bigint | undefined;
21
+ };
19
22
  /**
20
23
  * Converts an object of options to an array of command line arguments.
21
24
  *
22
25
  * @param options The options object.
23
26
  * @returns The command line arguments.
24
27
  */
25
- export declare function toArgs(options: {
26
- [key: string]: Record<string, string> | string | boolean | number | bigint | undefined;
27
- }): string[];
28
+ export declare function toArgs(parameters: ToArgsParameters): string[];
28
29
  /**
29
30
  * Converts a camelCase string to a flag case string.
30
31
  *
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../utils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,CAkBlE;AAMD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,UAE1C;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE;IAC9B,CAAC,GAAG,EAAE,MAAM,GACR,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtB,MAAM,GACN,OAAO,GACP,MAAM,GACN,MAAM,GACN,SAAS,CAAA;CACd,YAsBA;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,UAErC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../utils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,CAkBlE;AAMD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,UAE1C;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,CAAC,GAAG,EAAE,MAAM,GACR,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtB,MAAM,GACN,OAAO,GACP,MAAM,GACN,MAAM,GACN,SAAS,CAAA;CACd,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,UAAU,EAAE,gBAAgB,YAsBlD;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,UAErC"}
package/_lib/utils.js CHANGED
@@ -36,8 +36,8 @@ export function stripColors(message) {
36
36
  * @param options The options object.
37
37
  * @returns The command line arguments.
38
38
  */
39
- export function toArgs(options) {
40
- return Object.entries(options).flatMap(([key, value]) => {
39
+ export function toArgs(parameters) {
40
+ return Object.entries(parameters).flatMap(([key, value]) => {
41
41
  if (value === undefined)
42
42
  return [];
43
43
  if (typeof value === 'object' && value !== null)
package/_lib/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../utils.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,MAAM,IAAI,GAAG,kBAAkB,CAAA,CAAC,kCAAkC;IAClE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,OAAO,IAAI,GAAG,EAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,QAAQ;SACzC,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAA;IAEhC,MAAM,EAAE,GACN,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QACrD,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;QAClB,CAAC,CAAC,SAAS,CAAA;IACf,MAAM,IAAI,GAAG,IACX,OAAO,EAAE,KAAK,QAAQ;QACpB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;QACpB,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CACtC,EAAE,CAAA;IAEF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;AACrB,CAAC;AAED,MAAM,cAAc;AAClB,yEAAyE;AACzE,6EAA6E,CAAA;AAE/E;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,OAQtB;IACC,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACtD,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,EAAE,CAAA;QAElC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;YAC7C,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,EAAE;gBAC1D,IAAI,QAAQ,KAAK,SAAS;oBAAE,OAAO,EAAE,CAAA;gBACrC,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;gBAC5B,MAAM,KAAK,GAAG,GAAG,MAAM,KAAK,QAAQ,EAAE,CAAA;gBACtC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YACtB,CAAC,CAAC,CAAA;QAEJ,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;QAE5B,IAAI,KAAK,KAAK,KAAK;YAAE,OAAO,EAAE,CAAA;QAC9B,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,CAAC,IAAI,CAAC,CAAA;QAEjC,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAA;QACpC,IAAI,WAAW,KAAK,EAAE;YAAE,OAAO,CAAC,IAAI,CAAC,CAAA;QAErC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,KAAK,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,CAAA;AAC3E,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../utils.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,MAAM,IAAI,GAAG,kBAAkB,CAAA,CAAC,kCAAkC;IAClE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,OAAO,IAAI,GAAG,EAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,QAAQ;SACzC,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAA;IAEhC,MAAM,EAAE,GACN,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QACrD,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;QAClB,CAAC,CAAC,SAAS,CAAA;IACf,MAAM,IAAI,GAAG,IACX,OAAO,EAAE,KAAK,QAAQ;QACpB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;QACpB,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CACtC,EAAE,CAAA;IAEF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;AACrB,CAAC;AAED,MAAM,cAAc;AAClB,yEAAyE;AACzE,6EAA6E,CAAA;AAE/E;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAA;AAC5C,CAAC;AAYD;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,UAA4B;IACjD,OAAO,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACzD,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,EAAE,CAAA;QAElC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;YAC7C,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,EAAE;gBAC1D,IAAI,QAAQ,KAAK,SAAS;oBAAE,OAAO,EAAE,CAAA;gBACrC,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;gBAC5B,MAAM,KAAK,GAAG,GAAG,MAAM,KAAK,QAAQ,EAAE,CAAA;gBACtC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YACtB,CAAC,CAAC,CAAA;QAEJ,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;QAE5B,IAAI,KAAK,KAAK,KAAK;YAAE,OAAO,EAAE,CAAA;QAC9B,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,CAAC,IAAI,CAAC,CAAA;QAEjC,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAA;QACpC,IAAI,WAAW,KAAK,EAAE;YAAE,OAAO,CAAC,IAAI,CAAC,CAAA;QAErC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,KAAK,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,CAAA;AAC3E,CAAC"}
package/exports/index.ts CHANGED
@@ -14,7 +14,7 @@ export {
14
14
  } from '../pool.js'
15
15
 
16
16
  export {
17
- type createServerParameters,
18
- type createServerReturnType,
17
+ type CreateServerParameters,
18
+ type CreateServerReturnType,
19
19
  createServer,
20
20
  } from '../server.js'
@@ -0,0 +1,10 @@
1
+ import { expect, test } from 'vitest'
2
+ import * as exports from './processes.js'
3
+
4
+ test('exports', () => {
5
+ expect(Object.keys(exports)).toMatchInlineSnapshot(`
6
+ [
7
+ "execa",
8
+ ]
9
+ `)
10
+ })
@@ -0,0 +1,8 @@
1
+ export {
2
+ type ExecaParameters,
3
+ type ExecaProcess,
4
+ type ExecaReturnType,
5
+ type ExecaStartOptions,
6
+ type Process_internal,
7
+ execa,
8
+ } from '../processes/execa.js'
package/instance.test.ts CHANGED
@@ -212,7 +212,7 @@ test('behavior: events', async () => {
212
212
  name: 'foo',
213
213
  host: 'localhost',
214
214
  port: 3000,
215
- async start({ emitter }) {
215
+ async start(_, { emitter }) {
216
216
  emitter.emit('message', count.toString())
217
217
  emitter.emit('listening')
218
218
  if (count > 0) emitter.emit('stderr', 'stderr')
@@ -268,7 +268,7 @@ test('behavior: messages', async () => {
268
268
  name: 'foo',
269
269
  host: 'localhost',
270
270
  port: 3000,
271
- async start({ emitter }) {
271
+ async start(_, { emitter }) {
272
272
  for (let i = 0; i < 50; i++) emitter.emit('message', i.toString())
273
273
  },
274
274
  async stop() {},
package/instance.ts CHANGED
@@ -8,11 +8,11 @@ type EventTypes = {
8
8
  stdout: [message: string]
9
9
  }
10
10
 
11
- type InstanceStartOptions_internal = {
11
+ export type InstanceStartOptions_internal = {
12
12
  emitter: EventEmitter<EventTypes>
13
13
  status: Instance['status']
14
14
  }
15
- type InstanceStopOptions_internal = {
15
+ export type InstanceStopOptions_internal = {
16
16
  emitter: EventEmitter<EventTypes>
17
17
  status: Instance['status']
18
18
  }
@@ -30,9 +30,10 @@ export type DefineInstanceFn<
30
30
  > = (parameters: parameters) => Pick<Instance, 'host' | 'name' | 'port'> & {
31
31
  _internal?: _internal | undefined
32
32
  start(
33
- options: InstanceStartOptions & InstanceStartOptions_internal,
33
+ options: InstanceStartOptions,
34
+ options_internal: InstanceStartOptions_internal,
34
35
  ): Promise<void>
35
- stop(options: InstanceStopOptions_internal): Promise<void>
36
+ stop(options_internal: InstanceStopOptions_internal): Promise<void>
36
37
  }
37
38
 
38
39
  export type Instance<
@@ -217,16 +218,20 @@ export function defineInstance<
217
218
  emitter.on('exit', onExit)
218
219
 
219
220
  status = 'starting'
220
- start({
221
- emitter,
222
- port,
223
- status: this.status,
224
- })
221
+ start(
222
+ {
223
+ port,
224
+ },
225
+ {
226
+ emitter,
227
+ status: this.status,
228
+ },
229
+ )
225
230
  .then(() => {
226
231
  status = 'started'
227
232
 
228
233
  stopResolver = Promise.withResolvers<void>()
229
- startResolver.resolve(this.stop)
234
+ startResolver.resolve(this.stop.bind(this))
230
235
  })
231
236
  .catch((error) => {
232
237
  status = 'idle'
@@ -47,7 +47,7 @@ test('behavior: instance errored (duplicate ports)', async () => {
47
47
 
48
48
  await instance_1.start()
49
49
  await expect(() => instance_2.start()).rejects.toThrowError(
50
- 'Failed to start anvil',
50
+ 'Failed to start process "anvil"',
51
51
  )
52
52
  })
53
53
 
@@ -81,7 +81,9 @@ test('behavior: can subscribe to stderr', async () => {
81
81
 
82
82
  await instance_1.start()
83
83
  instance_2.on('stderr', (message) => messages.push(message))
84
- await expect(instance_2.start()).rejects.toThrow('Failed to start anvil')
84
+ await expect(instance_2.start()).rejects.toThrow(
85
+ 'Failed to start process "anvil"',
86
+ )
85
87
  })
86
88
 
87
89
  test('behavior: starts anvil with custom options', async () => {
@@ -122,6 +124,6 @@ test('behavior: exit when status is starting', async () => {
122
124
  instance._internal.process.kill()
123
125
 
124
126
  await expect(promise).rejects.toThrowErrorMatchingInlineSnapshot(
125
- '[Error: Failed to start anvil: exited.]',
127
+ '[Error: Failed to start process "anvil": exited.]',
126
128
  )
127
129
  })
@@ -1,6 +1,5 @@
1
- import { type ResultPromise, execa } from 'execa'
2
1
  import { defineInstance } from '../instance.js'
3
- import { stripColors } from '../utils.js'
2
+ import { execa } from '../processes/execa.js'
4
3
  import { toArgs } from '../utils.js'
5
4
 
6
5
  export type AnvilParameters = {
@@ -259,64 +258,37 @@ export type AnvilParameters = {
259
258
  export const anvil = defineInstance((parameters?: AnvilParameters) => {
260
259
  const { binary = 'anvil', ...args } = parameters || {}
261
260
 
262
- let process: ResultPromise<{ cleanup: true; reject: false }>
263
-
264
- async function stop() {
265
- const killed = process.kill()
266
- if (!killed) throw new Error('Failed to stop anvil')
267
- return new Promise((resolve) => process.on('close', resolve))
268
- }
261
+ const name = 'anvil'
262
+ const process = execa({ name })
269
263
 
270
264
  return {
271
265
  _internal: {
272
266
  args,
273
267
  get process() {
274
- return process
268
+ return process._internal.process
275
269
  },
276
270
  },
277
271
  host: args.host ?? 'localhost',
278
- name: 'anvil',
272
+ name,
279
273
  port: args.port ?? 8545,
280
- async start({ emitter, port = args.port, status }) {
281
- const { promise, resolve, reject } = Promise.withResolvers<void>()
282
-
283
- process = execa(binary, toArgs({ ...args, port }), {
284
- cleanup: true,
285
- reject: false,
286
- })
287
-
288
- process.stdout.on('data', (data) => {
289
- const message = stripColors(data.toString())
290
- emitter.emit('message', message)
291
- emitter.emit('stdout', message)
292
- if (message.includes('Listening on')) {
293
- emitter.emit('listening')
294
- resolve()
295
- }
296
- })
297
- process.stderr.on('data', async (data) => {
298
- const message = stripColors(data.toString())
299
- emitter.emit('message', message)
300
- emitter.emit('stderr', message)
301
- await stop()
302
- reject(new Error(`Failed to start anvil: ${data.toString()}`))
303
- })
304
- process.on('close', () => process.removeAllListeners())
305
- process.on('exit', (code, signal) => {
306
- emitter.emit('exit', code, signal)
307
-
308
- if (!code) {
309
- process.removeAllListeners()
310
- if (status === 'starting')
311
- reject(new Error('Failed to start anvil: exited.'))
312
- }
313
- })
314
-
315
- return promise
274
+ async start({ port = args.port }, options) {
275
+ return await process.start(
276
+ ($) => $`${binary} ${toArgs({ ...args, port })}`,
277
+ {
278
+ ...options,
279
+ // Resolve when the process is listening via a "Listening on" message.
280
+ resolver({ process, reject, resolve }) {
281
+ process.stdout.on('data', (data) => {
282
+ const message = data.toString()
283
+ if (message.includes('Listening on')) resolve()
284
+ })
285
+ process.stderr.on('data', reject)
286
+ },
287
+ },
288
+ )
316
289
  },
317
290
  async stop() {
318
- process.removeAllListeners()
319
- await stop()
291
+ await process.stop()
320
292
  },
321
293
  }
322
294
  })
@@ -0,0 +1,106 @@
1
+ import getPort from 'get-port'
2
+ import { afterEach, beforeAll, expect, test } from 'vitest'
3
+
4
+ import { stackupOptions } from '../../test/utils.js'
5
+ import type { Instance } from '../instance.js'
6
+ import { anvil } from './anvil.js'
7
+ import { type StackupParameters, stackup } from './stackup.js'
8
+
9
+ const instances: Instance[] = []
10
+
11
+ const port = await getPort()
12
+
13
+ const defineInstance = (parameters?: Partial<StackupParameters>) => {
14
+ const instance = stackup({
15
+ ...stackupOptions({ port }),
16
+ ...parameters,
17
+ })
18
+ instances.push(instance)
19
+ return instance
20
+ }
21
+
22
+ beforeAll(() => anvil({ port }).start())
23
+
24
+ afterEach(async () => {
25
+ for (const instance of instances) await instance.stop().catch(() => {})
26
+ })
27
+
28
+ test('default', async () => {
29
+ const messages: string[] = []
30
+ const stdouts: string[] = []
31
+
32
+ const instance = defineInstance()
33
+
34
+ instance.on('message', (m) => messages.push(m))
35
+ instance.on('stdout', (m) => stdouts.push(m))
36
+
37
+ expect(instance.messages.get()).toMatchInlineSnapshot('[]')
38
+
39
+ await instance.start()
40
+ expect(instance.status).toEqual('started')
41
+
42
+ expect(messages.join('')).toBeDefined()
43
+ expect(stdouts.join('')).toBeDefined()
44
+ expect(instance.messages.get().join('')).toBeDefined()
45
+
46
+ await instance.stop()
47
+ expect(instance.status).toEqual('stopped')
48
+
49
+ expect(messages.join('')).toBeDefined()
50
+ expect(stdouts.join('')).toBeDefined()
51
+ expect(instance.messages.get()).toMatchInlineSnapshot('[]')
52
+ })
53
+
54
+ test('behavior: instance errored (duplicate ports)', async () => {
55
+ const instance_1 = defineInstance({
56
+ port: 1337,
57
+ })
58
+ const instance_2 = defineInstance({
59
+ port: 1337,
60
+ })
61
+
62
+ await instance_1.start()
63
+ await expect(() => instance_2.start()).rejects.toThrowError()
64
+ })
65
+
66
+ test('behavior: start and stop multiple times', async () => {
67
+ const instance = defineInstance()
68
+
69
+ await instance.start()
70
+ await instance.stop()
71
+ await instance.start()
72
+ await instance.stop()
73
+ await instance.start()
74
+ await instance.stop()
75
+ await instance.start()
76
+ await instance.stop()
77
+ })
78
+
79
+ test('behavior: can subscribe to stderr', async () => {
80
+ const messages: string[] = []
81
+
82
+ const instance_1 = defineInstance({ port: 1337 })
83
+ const instance_2 = defineInstance({ port: 1337 })
84
+
85
+ await instance_1.start()
86
+ instance_2.on('stderr', (message) => messages.push(message))
87
+ await expect(instance_2.start()).rejects.toThrowError()
88
+ })
89
+
90
+ test('behavior: exit', async () => {
91
+ const instance = defineInstance()
92
+
93
+ let exitCode: number | null | undefined = undefined
94
+ instance.on('exit', (code) => {
95
+ exitCode = code
96
+ })
97
+
98
+ await instance.start()
99
+ expect(instance.status).toEqual('started')
100
+
101
+ instance._internal.process.kill()
102
+
103
+ await new Promise<void>((res) => setTimeout(res, 100))
104
+ expect(instance.status).toEqual('stopped')
105
+ expect(typeof exitCode !== 'undefined').toBeTruthy()
106
+ })
@@ -0,0 +1,162 @@
1
+ import { defineInstance } from '../instance.js'
2
+ import { execa } from '../processes/execa.js'
3
+
4
+ export type StackupParameters = {
5
+ /**
6
+ * The name of the native tracer to use during validation. This will be "bundlerCollectorTracer" if using builds from ERC-4337 execution client repo.
7
+ * Defaults to address of `privateKey`.
8
+ */
9
+ beneficiary?: string | undefined
10
+ /**
11
+ * Directory to store the embedded database.
12
+ * @default /tmp/stackup_bundler
13
+ */
14
+ dataDirectory?: string | undefined
15
+ /**
16
+ * RPC url to the execution client.
17
+ */
18
+ ethClientUrl: string
19
+ /**
20
+ * A boolean value for bundlers on an Arbitrum stack network to properly account for the L1 callData cost.
21
+ * @default false
22
+ */
23
+ isArbStackNetwork?: boolean | undefined
24
+ /**
25
+ * A boolean value for bundlers on an OP stack network to properly account for the L1 callData cost.
26
+ * @default false
27
+ */
28
+ isOpStackNetwork?: boolean | undefined
29
+ /**
30
+ * A boolean value for bundlers on a network that supports RIP-7212 precompile for secp256r1 signature verification.
31
+ * @default false
32
+ */
33
+ isRip7212Supported?: boolean | undefined
34
+ /**
35
+ * The maximum gas limit that can be submitted per UserOperation batch.
36
+ * @default 18_000_000
37
+ */
38
+ maxBatchGasLimit?: number | undefined
39
+ /**
40
+ * The maximum duration that a userOp can stay in the mempool before getting dropped.
41
+ * @default 180
42
+ */
43
+ maxOpTtlSeconds?: number | undefined
44
+ /**
45
+ * The maximum verificationGasLimit on a received UserOperation.
46
+ * @default 6_000_000
47
+ */
48
+ maxVerificationGas?: number | undefined
49
+ /**
50
+ * The maximum block range when looking up a User Operation with eth_getUserOperationReceipt or eth_getUserOperationByHash.
51
+ *
52
+ * Higher limits allow for fetching older User Operations but will result in higher request latency due to additional compute on the underlying node.
53
+ *
54
+ * @default 2_000
55
+ */
56
+ opLookupLimit?: number | undefined
57
+ /**
58
+ * Port to start the instance on.
59
+ */
60
+ port?: number | undefined
61
+ /**
62
+ * The private key for the EOA used to relay User Operation bundles to the EntryPoint.
63
+ */
64
+ privateKey: string
65
+ /**
66
+ * EntryPoint addresses to support. The first address is the preferred EntryPoint.
67
+ */
68
+ supportedEntryPoints?: string[]
69
+ }
70
+
71
+ /**
72
+ * Defines an Anvil instance.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * const instance = stackup({
77
+ * ethClientUrl: 'http://localhost:8545',
78
+ * port: 4337,
79
+ * privateKey: '0x...'
80
+ * })
81
+ * await instance.start()
82
+ * // ...
83
+ * await instance.stop()
84
+ * ```
85
+ */
86
+ export const stackup = defineInstance((parameters?: StackupParameters) => {
87
+ const args = (parameters || {}) as StackupParameters
88
+
89
+ const host = 'localhost'
90
+ const name = 'stackup'
91
+ const process = execa({ name })
92
+
93
+ return {
94
+ _internal: {
95
+ args,
96
+ get process() {
97
+ return process._internal.process
98
+ },
99
+ },
100
+ host,
101
+ name,
102
+ port: args.port ?? 4337,
103
+ async start({ port = args.port }, options) {
104
+ const args_ = [
105
+ '--net',
106
+ 'host',
107
+ '--add-host',
108
+ 'host.docker.internal:host-gateway',
109
+ '--add-host',
110
+ 'localhost:host-gateway',
111
+ '-p',
112
+ `${port}:${port}`,
113
+ '-e',
114
+ `ERC4337_BUNDLER_PORT=${port}`,
115
+ ...Object.entries(args).flatMap(([key, value]) => {
116
+ if (key === 'port') return []
117
+ if (value === undefined) return []
118
+
119
+ if (key === 'ethClientUrl')
120
+ value = (value as string).replaceAll(
121
+ /127\.0\.0\.1|0\.0\.0\.0/g,
122
+ 'host.docker.internal',
123
+ )
124
+ if (key === 'privateKey') value = (value as string).replace('0x', '')
125
+
126
+ return [
127
+ '-e',
128
+ `ERC4337_BUNDLER_${key
129
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
130
+ .toUpperCase()}=${value}`,
131
+ ]
132
+ }),
133
+ 'stackupwallet/stackup-bundler:latest',
134
+ '/app/stackup-bundler',
135
+ 'start',
136
+ '--mode',
137
+ 'private',
138
+ ]
139
+ return await process.start(($) => $`docker run ${args_}`, {
140
+ ...options,
141
+ resolver({ process, resolve, reject }) {
142
+ process.stderr.on('data', async (data) => {
143
+ const message = data.toString()
144
+ // For some reason, `stackup-bundler` logs to stderr. So we have to try
145
+ // and dissect what is an error, and what is not. 😅
146
+ if (message.includes('Set nextTxnTs to'))
147
+ setTimeout(() => resolve(), 100)
148
+ else if (
149
+ message
150
+ .toLowerCase()
151
+ .match(/panic|error|connection refused|address already in use/)
152
+ )
153
+ reject(data)
154
+ })
155
+ },
156
+ })
157
+ },
158
+ async stop() {
159
+ await process.stop()
160
+ },
161
+ }
162
+ })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prool",
3
- "description": "",
4
- "version": "0.0.4",
3
+ "description": "HTTP testing instances for Ethereum",
4
+ "version": "0.0.5",
5
5
  "type": "module",
6
6
  "module": "_lib/exports/index.mjs",
7
7
  "types": "_lib/exports/index.d.ts",
@@ -15,6 +15,10 @@
15
15
  "types": "./_lib/exports/index.d.ts",
16
16
  "default": "./_lib/exports/index.js"
17
17
  },
18
+ "./processes": {
19
+ "types": "./_lib/exports/processes.d.ts",
20
+ "default": "./_lib/exports/processes.js"
21
+ },
18
22
  "./instances": {
19
23
  "types": "./_lib/exports/instances.d.ts",
20
24
  "default": "./_lib/exports/instances.js"