@temporalio/testing 0.23.1 → 1.0.0-rc.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 ADDED
@@ -0,0 +1,9 @@
1
+ # `@temporalio/testing`
2
+
3
+ [![NPM](https://img.shields.io/npm/v/@temporalio/testing?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/testing)
4
+
5
+ Part of [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/).
6
+
7
+ - [Testing docs](https://docs.temporal.io/typescript/testing)
8
+ - [API reference](https://typescript.temporal.io/api/namespaces/testing)
9
+ - [Sample projects](https://github.com/temporalio/samples-typescript)
@@ -0,0 +1,7 @@
1
+ import { WorkflowInterceptors } from '@temporalio/workflow';
2
+ /**
3
+ * Simple interceptor that transforms {@link assert.AssertionError} into non retryable failures.
4
+ *
5
+ * This allows conveniently using `assert` directly from Workflows.
6
+ */
7
+ export declare function interceptors(): WorkflowInterceptors;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.interceptors = void 0;
7
+ const assert_1 = __importDefault(require("assert"));
8
+ const workflow_1 = require("@temporalio/workflow");
9
+ /**
10
+ * Simple interceptor that transforms {@link assert.AssertionError} into non retryable failures.
11
+ *
12
+ * This allows conveniently using `assert` directly from Workflows.
13
+ */
14
+ function interceptors() {
15
+ return {
16
+ inbound: [
17
+ {
18
+ async handleSignal(input, next) {
19
+ try {
20
+ return await next(input);
21
+ }
22
+ catch (err) {
23
+ if (err instanceof assert_1.default.AssertionError) {
24
+ const appErr = workflow_1.ApplicationFailure.nonRetryable(err.message);
25
+ appErr.stack = err.stack;
26
+ throw appErr;
27
+ }
28
+ }
29
+ },
30
+ async execute(input, next) {
31
+ try {
32
+ return await next(input);
33
+ }
34
+ catch (err) {
35
+ if (err instanceof assert_1.default.AssertionError) {
36
+ const appErr = workflow_1.ApplicationFailure.nonRetryable(err.message);
37
+ appErr.stack = err.stack;
38
+ throw appErr;
39
+ }
40
+ }
41
+ },
42
+ },
43
+ ],
44
+ };
45
+ }
46
+ exports.interceptors = interceptors;
47
+ //# sourceMappingURL=assert-to-failure-interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-to-failure-interceptor.js","sourceRoot":"","sources":["../src/assert-to-failure-interceptor.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAC5B,mDAAgF;AAEhF;;;;GAIG;AACH,SAAgB,YAAY;IAC1B,OAAO;QACL,OAAO,EAAE;YACP;gBACE,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI;oBAC5B,IAAI;wBACF,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;qBAC1B;oBAAC,OAAO,GAAG,EAAE;wBACZ,IAAI,GAAG,YAAY,gBAAM,CAAC,cAAc,EAAE;4BACxC,MAAM,MAAM,GAAG,6BAAkB,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;4BAC5D,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;4BACzB,MAAM,MAAM,CAAC;yBACd;qBACF;gBACH,CAAC;gBACD,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI;oBACvB,IAAI;wBACF,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;qBAC1B;oBAAC,OAAO,GAAG,EAAE;wBACZ,IAAI,GAAG,YAAY,gBAAM,CAAC,cAAc,EAAE;4BACxC,MAAM,MAAM,GAAG,6BAAkB,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;4BAC5D,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;4BACzB,MAAM,MAAM,CAAC;yBACd;qBACF;gBACH,CAAC;aACF;SACF;KACF,CAAC;AACJ,CAAC;AA7BD,oCA6BC"}
@@ -0,0 +1,15 @@
1
+ /// <reference types="node" />
2
+ import { ChildProcess } from 'child_process';
3
+ export declare class ChildProcessError extends Error {
4
+ readonly code: number | null;
5
+ readonly signal: NodeJS.Signals | null;
6
+ readonly name = "ChildProcessError";
7
+ constructor(message: string, code: number | null, signal: NodeJS.Signals | null);
8
+ }
9
+ export interface WaitOptions {
10
+ validReturnCodes: number[];
11
+ }
12
+ export declare function waitOnChild(child: ChildProcess, opts?: WaitOptions): Promise<void>;
13
+ export declare function kill(child: ChildProcess, signal?: NodeJS.Signals, opts?: WaitOptions): Promise<void>;
14
+ export declare function killIfExists(child: ChildProcess, signal?: NodeJS.Signals, opts?: WaitOptions): Promise<void>;
15
+ export declare const shell: boolean;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.shell = exports.killIfExists = exports.kill = exports.waitOnChild = exports.ChildProcessError = void 0;
4
+ class ChildProcessError extends Error {
5
+ constructor(message, code, signal) {
6
+ super(message);
7
+ this.code = code;
8
+ this.signal = signal;
9
+ this.name = 'ChildProcessError';
10
+ }
11
+ }
12
+ exports.ChildProcessError = ChildProcessError;
13
+ async function waitOnChild(child, opts) {
14
+ return new Promise((resolve, reject) => {
15
+ child.on('exit', (code, signal) => {
16
+ if (code !== null && (opts?.validReturnCodes ?? [0]).includes(code)) {
17
+ resolve();
18
+ }
19
+ else {
20
+ reject(new ChildProcessError('Process failed', code, signal));
21
+ }
22
+ });
23
+ child.on('error', reject);
24
+ });
25
+ }
26
+ exports.waitOnChild = waitOnChild;
27
+ async function kill(child, signal = 'SIGINT', opts) {
28
+ if (child.pid === undefined) {
29
+ throw new TypeError('Expected child with pid');
30
+ }
31
+ process.kill(child.pid, signal);
32
+ try {
33
+ await waitOnChild(child, opts);
34
+ }
35
+ catch (err) {
36
+ // Should error if the error is not a child process error or it is a child
37
+ // process and either the platform is Windows or the signal matches.
38
+ const shouldError = !(err instanceof ChildProcessError) || (process.platform !== 'win32' && err.signal !== signal);
39
+ if (shouldError) {
40
+ throw err;
41
+ }
42
+ }
43
+ }
44
+ exports.kill = kill;
45
+ async function killIfExists(child, signal = 'SIGINT', opts) {
46
+ try {
47
+ await kill(child, signal, opts);
48
+ }
49
+ catch (err) {
50
+ if (err.code !== 'ESRCH') {
51
+ throw err;
52
+ }
53
+ }
54
+ }
55
+ exports.killIfExists = killIfExists;
56
+ exports.shell = process.platform === 'win32';
57
+ //# sourceMappingURL=child-process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child-process.js","sourceRoot":"","sources":["../src/child-process.ts"],"names":[],"mappings":";;;AAGA,MAAa,iBAAkB,SAAQ,KAAK;IAG1C,YAAY,OAAe,EAAkB,IAAmB,EAAkB,MAA6B;QAC7G,KAAK,CAAC,OAAO,CAAC,CAAC;QAD4B,SAAI,GAAJ,IAAI,CAAe;QAAkB,WAAM,GAAN,MAAM,CAAuB;QAF/F,SAAI,GAAG,mBAAmB,CAAC;IAI3C,CAAC;CACF;AAND,8CAMC;AAMM,KAAK,UAAU,WAAW,CAAC,KAAmB,EAAE,IAAkB;IACvE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YAChC,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,gBAAgB,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;gBACnE,OAAO,EAAE,CAAC;aACX;iBAAM;gBACL,MAAM,CAAC,IAAI,iBAAiB,CAAC,gBAAgB,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;aAC/D;QACH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC;AAXD,kCAWC;AAEM,KAAK,UAAU,IAAI,CAAC,KAAmB,EAAE,SAAyB,QAAQ,EAAE,IAAkB;IACnG,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE;QAC3B,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,CAAC;KAChD;IACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI;QACF,MAAM,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;KAChC;IAAC,OAAO,GAAG,EAAE;QACZ,0EAA0E;QAC1E,oEAAoE;QACpE,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,YAAY,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;QACnH,IAAI,WAAW,EAAE;YACf,MAAM,GAAG,CAAC;SACX;KACF;AACH,CAAC;AAfD,oBAeC;AAEM,KAAK,UAAU,YAAY,CAChC,KAAmB,EACnB,SAAyB,QAAQ,EACjC,IAAkB;IAElB,IAAI;QACF,MAAM,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;KACjC;IAAC,OAAO,GAAQ,EAAE;QACjB,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE;YACxB,MAAM,GAAG,CAAC;SACX;KACF;AACH,CAAC;AAZD,oCAYC;AAEY,QAAA,KAAK,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC"}
@@ -0,0 +1 @@
1
+ export * as testing from './index';
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.testing = void 0;
27
+ // Reexports the contents of this package under the "testing" namespace for better docs generation
28
+ exports.testing = __importStar(require("./index"));
29
+ //# sourceMappingURL=index-for-docs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-for-docs.js","sourceRoot":"","sources":["../src/index-for-docs.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kGAAkG;AAClG,mDAAmC"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,181 @@
1
+ /// <reference types="node" />
2
+ import * as activity from '@temporalio/activity';
3
+ import { AsyncCompletionClient, WorkflowClient as BaseWorkflowClient, WorkflowClientOptions as BaseWorkflowClientOptions, WorkflowResultOptions as BaseWorkflowResultOptions, WorkflowStartOptions as BaseWorkflowStartOptions } from '@temporalio/client';
4
+ import { ActivityFunction, Workflow, WorkflowResultType } from '@temporalio/common';
5
+ import { NativeConnection, Logger } from '@temporalio/worker';
6
+ import { ChildProcess, StdioOptions } from 'child_process';
7
+ import events from 'events';
8
+ import { Connection, TestService } from './test-service-client';
9
+ export declare const DEFAULT_TEST_SERVER_PATH: string;
10
+ /**
11
+ * Options passed to {@link WorkflowClient.result}, these are the same as the
12
+ * {@link BaseWorkflowResultOptions} with an additional option that controls
13
+ * whether to toggle time skipping in the Test server while waiting on a
14
+ * Workflow's result.
15
+ */
16
+ export interface WorkflowResultOptions extends BaseWorkflowResultOptions {
17
+ /**
18
+ * If set to `true`, waiting for the result does not enable time skipping
19
+ */
20
+ runInNormalTime?: boolean;
21
+ }
22
+ /**
23
+ * Options passed to {@link WorkflowClient.execute}, these are the same as the
24
+ * {@link BaseWorkflowStartOptions} with an additional option that controls
25
+ * whether to toggle time skipping in the Test server while waiting on a
26
+ * Workflow's result.
27
+ */
28
+ export declare type WorkflowStartOptions<T extends Workflow> = BaseWorkflowStartOptions<T> & {
29
+ /**
30
+ * If set to `true`, waiting for the result does not enable time skipping
31
+ */
32
+ runInNormalTime?: boolean;
33
+ };
34
+ export interface WorkflowClientOptions extends BaseWorkflowClientOptions {
35
+ connection: Connection;
36
+ }
37
+ /**
38
+ * A client with the exact same API as the "normal" client with 1 exception,
39
+ * When this client waits on a Workflow's result, it will enable time skipping
40
+ * in the test server.
41
+ */
42
+ export declare class WorkflowClient extends BaseWorkflowClient {
43
+ protected readonly testService: TestService;
44
+ constructor(options: WorkflowClientOptions);
45
+ /**
46
+ * Execute a Workflow and wait for completion.
47
+ *
48
+ * @see {@link BaseWorkflowClient.execute}
49
+ */
50
+ execute<T extends Workflow>(workflowTypeOrFunc: string | T, options: WorkflowStartOptions<T>): Promise<WorkflowResultType<T>>;
51
+ /**
52
+ * Gets the result of a Workflow execution.
53
+ *
54
+ * @see {@link BaseWorkflowClient.result}
55
+ */
56
+ result<T>(workflowId: string, runId?: string | undefined, opts?: WorkflowResultOptions | undefined): Promise<T>;
57
+ }
58
+ /**
59
+ * Convenience workflow interceptors
60
+ *
61
+ * Contains a single interceptor for transforming `AssertionError`s into non
62
+ * retryable `ApplicationFailure`s.
63
+ */
64
+ export declare const workflowInterceptorModules: string[];
65
+ export interface TestServerSpawnerOptions {
66
+ /**
67
+ * @default {@link DEFAULT_TEST_SERVER_PATH}
68
+ */
69
+ path?: string;
70
+ /**
71
+ * @default ignore
72
+ */
73
+ stdio?: StdioOptions;
74
+ }
75
+ /**
76
+ * A generic callback that returns a child process
77
+ */
78
+ export declare type TestServerSpawner = (port: number) => ChildProcess;
79
+ /**
80
+ * Options for {@link TestWorkflowEnvironment.create}
81
+ */
82
+ export interface TestWorkflowEnvironmentOptions {
83
+ testServer?: TestServerSpawner | TestServerSpawnerOptions;
84
+ logger?: Logger;
85
+ }
86
+ /**
87
+ * An execution environment for running Workflow integration tests.
88
+ *
89
+ * Runs an external server.
90
+ * By default, the Java test server is used which supports time skipping.
91
+ */
92
+ export declare class TestWorkflowEnvironment {
93
+ protected readonly serverProc: ChildProcess;
94
+ /**
95
+ * Get an extablished {@link Connection} to the test server
96
+ */
97
+ readonly connection: Connection;
98
+ /**
99
+ * An {@link AsyncCompletionClient} for interacting with the test server
100
+ */
101
+ readonly asyncCompletionClient: AsyncCompletionClient;
102
+ /**
103
+ * A {@link WorkflowClient} for interacting with the test server
104
+ */
105
+ readonly workflowClient: WorkflowClient;
106
+ /**
107
+ * A {@link NativeConnection} for interacting with the test server.
108
+ *
109
+ * Use this connection when creating Workers for testing.
110
+ */
111
+ readonly nativeConnection: NativeConnection;
112
+ protected constructor(serverProc: ChildProcess, connection: Connection, nativeConnection: NativeConnection);
113
+ /**
114
+ * Create a new test environment
115
+ */
116
+ static create(opts?: TestWorkflowEnvironmentOptions): Promise<TestWorkflowEnvironment>;
117
+ /**
118
+ * Kill the test server process and close the connection to it
119
+ */
120
+ teardown(): Promise<void>;
121
+ /**
122
+ * Wait for `durationMs` in "test server time".
123
+ *
124
+ * The test server toggles between skipped time and normal time depending on what it needs to execute.
125
+ *
126
+ * This method is likely to resolve in less than `durationMs` of "real time".
127
+ *
128
+ * Useful for simulating events far into the future like completion of long running activities.
129
+ *
130
+ * @param durationMs {@link https://www.npmjs.com/package/ms | ms} formatted string or number of milliseconds
131
+ *
132
+ * @example
133
+ *
134
+ * `workflow.ts`
135
+ *
136
+ * ```ts
137
+ * const activities = proxyActivities({ startToCloseTimeout: 2_000_000 });
138
+ *
139
+ * export async function raceActivityAndTimer(): Promise<string> {
140
+ * return await Promise.race([
141
+ * wf.sleep(500_000).then(() => 'timer'),
142
+ * activities.longRunning().then(() => 'activity'),
143
+ * ]);
144
+ * }
145
+ * ```
146
+ *
147
+ * `test.ts`
148
+ *
149
+ * ```ts
150
+ * const worker = await Worker.create({
151
+ * connection: testEnv.nativeConnection,
152
+ * activities: {
153
+ * async longRunning() {
154
+ * await testEnv.sleep(1_000_000); // <-- sleep called here
155
+ * },
156
+ * },
157
+ * // ...
158
+ * });
159
+ * ```
160
+ */
161
+ sleep: (durationMs: number | string) => Promise<void>;
162
+ }
163
+ /**
164
+ * Used as the default activity info for Activities executed in the {@link MockActivityEnvironment}
165
+ */
166
+ export declare const defaultActivityInfo: activity.Info;
167
+ /**
168
+ * An execution environment for testing Activities.
169
+ *
170
+ * Mocks Activity {@link Context | activity.Context} and exposes hooks for
171
+ * cancellation and heartbeats.
172
+ */
173
+ export declare class MockActivityEnvironment extends events.EventEmitter {
174
+ cancel: (reason?: any) => void;
175
+ readonly context: activity.Context;
176
+ constructor(info?: Partial<activity.Info>);
177
+ /**
178
+ * Run a function in Activity Context
179
+ */
180
+ run<P extends any[], R, F extends ActivityFunction<P, R>>(fn: F, ...args: P): Promise<R>;
181
+ }
package/lib/index.js ADDED
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.MockActivityEnvironment = exports.defaultActivityInfo = exports.TestWorkflowEnvironment = exports.workflowInterceptorModules = exports.WorkflowClient = exports.DEFAULT_TEST_SERVER_PATH = void 0;
30
+ const activity = __importStar(require("@temporalio/activity"));
31
+ const client_1 = require("@temporalio/client");
32
+ const common_1 = require("@temporalio/common");
33
+ const worker_1 = require("@temporalio/worker");
34
+ const path_1 = __importDefault(require("path"));
35
+ const os_1 = __importDefault(require("os"));
36
+ const abort_controller_1 = require("abort-controller");
37
+ const child_process_1 = require("child_process");
38
+ const events_1 = __importDefault(require("events"));
39
+ const child_process_2 = require("./child-process");
40
+ const test_service_client_1 = require("./test-service-client");
41
+ const TEST_SERVER_EXECUTABLE_NAME = os_1.default.platform() === 'win32' ? 'test-server.exe' : 'test-server';
42
+ exports.DEFAULT_TEST_SERVER_PATH = path_1.default.join(__dirname, `../${TEST_SERVER_EXECUTABLE_NAME}`);
43
+ /**
44
+ * A client with the exact same API as the "normal" client with 1 exception,
45
+ * When this client waits on a Workflow's result, it will enable time skipping
46
+ * in the test server.
47
+ */
48
+ class WorkflowClient extends client_1.WorkflowClient {
49
+ constructor(options) {
50
+ super(options);
51
+ this.testService = options.connection.testService;
52
+ }
53
+ /**
54
+ * Execute a Workflow and wait for completion.
55
+ *
56
+ * @see {@link BaseWorkflowClient.execute}
57
+ */
58
+ async execute(workflowTypeOrFunc, options) {
59
+ return super.execute(workflowTypeOrFunc, options);
60
+ }
61
+ /**
62
+ * Gets the result of a Workflow execution.
63
+ *
64
+ * @see {@link BaseWorkflowClient.result}
65
+ */
66
+ async result(workflowId, runId, opts) {
67
+ if (opts?.runInNormalTime) {
68
+ return await super.result(workflowId, runId, opts);
69
+ }
70
+ await this.testService.unlockTimeSkipping({});
71
+ try {
72
+ return await super.result(workflowId, runId, opts);
73
+ }
74
+ finally {
75
+ await this.testService.lockTimeSkipping({});
76
+ }
77
+ }
78
+ }
79
+ exports.WorkflowClient = WorkflowClient;
80
+ /**
81
+ * Convenience workflow interceptors
82
+ *
83
+ * Contains a single interceptor for transforming `AssertionError`s into non
84
+ * retryable `ApplicationFailure`s.
85
+ */
86
+ exports.workflowInterceptorModules = [path_1.default.join(__dirname, 'assert-to-failure-interceptor')];
87
+ function addDefaults({ testServer, logger, }) {
88
+ return {
89
+ testServerSpawner: typeof testServer === 'function'
90
+ ? testServer
91
+ : (port) => (0, child_process_1.spawn)(testServer?.path || exports.DEFAULT_TEST_SERVER_PATH, [`${port}`], {
92
+ stdio: testServer?.stdio || 'ignore',
93
+ }),
94
+ logger: logger ?? new worker_1.DefaultLogger('INFO'),
95
+ };
96
+ }
97
+ // TS transforms `import` statements into `require`s, this is a workaround until
98
+ // tsconfig module nodenext is stable.
99
+ const _importDynamic = new Function('modulePath', 'return import(modulePath)');
100
+ /**
101
+ * An execution environment for running Workflow integration tests.
102
+ *
103
+ * Runs an external server.
104
+ * By default, the Java test server is used which supports time skipping.
105
+ */
106
+ class TestWorkflowEnvironment {
107
+ constructor(serverProc, connection, nativeConnection) {
108
+ this.serverProc = serverProc;
109
+ /**
110
+ * Wait for `durationMs` in "test server time".
111
+ *
112
+ * The test server toggles between skipped time and normal time depending on what it needs to execute.
113
+ *
114
+ * This method is likely to resolve in less than `durationMs` of "real time".
115
+ *
116
+ * Useful for simulating events far into the future like completion of long running activities.
117
+ *
118
+ * @param durationMs {@link https://www.npmjs.com/package/ms | ms} formatted string or number of milliseconds
119
+ *
120
+ * @example
121
+ *
122
+ * `workflow.ts`
123
+ *
124
+ * ```ts
125
+ * const activities = proxyActivities({ startToCloseTimeout: 2_000_000 });
126
+ *
127
+ * export async function raceActivityAndTimer(): Promise<string> {
128
+ * return await Promise.race([
129
+ * wf.sleep(500_000).then(() => 'timer'),
130
+ * activities.longRunning().then(() => 'activity'),
131
+ * ]);
132
+ * }
133
+ * ```
134
+ *
135
+ * `test.ts`
136
+ *
137
+ * ```ts
138
+ * const worker = await Worker.create({
139
+ * connection: testEnv.nativeConnection,
140
+ * activities: {
141
+ * async longRunning() {
142
+ * await testEnv.sleep(1_000_000); // <-- sleep called here
143
+ * },
144
+ * },
145
+ * // ...
146
+ * });
147
+ * ```
148
+ */
149
+ this.sleep = async (durationMs) => {
150
+ await this.connection.testService.unlockTimeSkippingWithSleep({ duration: (0, common_1.msToTs)(durationMs) });
151
+ };
152
+ this.connection = connection;
153
+ this.nativeConnection = nativeConnection;
154
+ this.workflowClient = new WorkflowClient({ connection });
155
+ this.asyncCompletionClient = new client_1.AsyncCompletionClient({ connection });
156
+ }
157
+ /**
158
+ * Create a new test environment
159
+ */
160
+ static async create(opts) {
161
+ // No, we're not going to compile this to ESM for one dependency
162
+ const getPort = (await _importDynamic('get-port')).default;
163
+ const port = await getPort();
164
+ const { testServerSpawner, logger } = addDefaults(opts ?? {});
165
+ const child = testServerSpawner(port);
166
+ const address = `127.0.0.1:${port}`;
167
+ const connPromise = test_service_client_1.Connection.connect({ address });
168
+ try {
169
+ await Promise.race([
170
+ connPromise,
171
+ (0, child_process_2.waitOnChild)(child).then(() => {
172
+ throw new Error('Test server child process exited prematurely');
173
+ }),
174
+ ]);
175
+ }
176
+ catch (err) {
177
+ try {
178
+ await (0, child_process_2.kill)(child);
179
+ }
180
+ catch (error) {
181
+ logger.error('Failed to kill test server child process', { error });
182
+ }
183
+ throw err;
184
+ }
185
+ const conn = await connPromise;
186
+ const nativeConnection = await worker_1.NativeConnection.connect({ address });
187
+ return new this(child, conn, nativeConnection);
188
+ }
189
+ /**
190
+ * Kill the test server process and close the connection to it
191
+ */
192
+ async teardown() {
193
+ await this.connection.close();
194
+ await this.nativeConnection.close();
195
+ // TODO: the server should return exit code 0
196
+ await (0, child_process_2.kill)(this.serverProc, 'SIGINT', { validReturnCodes: [0, 130] });
197
+ }
198
+ }
199
+ exports.TestWorkflowEnvironment = TestWorkflowEnvironment;
200
+ /**
201
+ * Used as the default activity info for Activities executed in the {@link MockActivityEnvironment}
202
+ */
203
+ exports.defaultActivityInfo = {
204
+ attempt: 1,
205
+ isLocal: false,
206
+ taskToken: Buffer.from('test'),
207
+ activityId: 'test',
208
+ activityType: 'unknown',
209
+ workflowType: 'test',
210
+ base64TaskToken: Buffer.from('test').toString('base64'),
211
+ heartbeatDetails: undefined,
212
+ activityNamespace: 'default',
213
+ workflowNamespace: 'default',
214
+ workflowExecution: { workflowId: 'test', runId: 'dead-beef' },
215
+ scheduledTimestampMs: 1,
216
+ startToCloseTimeoutMs: 1000,
217
+ scheduleToCloseTimeoutMs: 1000,
218
+ };
219
+ /**
220
+ * An execution environment for testing Activities.
221
+ *
222
+ * Mocks Activity {@link Context | activity.Context} and exposes hooks for
223
+ * cancellation and heartbeats.
224
+ */
225
+ class MockActivityEnvironment extends events_1.default.EventEmitter {
226
+ constructor(info) {
227
+ super();
228
+ this.cancel = () => undefined;
229
+ const abortController = new abort_controller_1.AbortController();
230
+ const promise = new Promise((_, reject) => {
231
+ this.cancel = (reason) => {
232
+ abortController.abort();
233
+ reject(new common_1.CancelledFailure(reason));
234
+ };
235
+ });
236
+ const heartbeatCallback = (details) => this.emit('heartbeat', details);
237
+ this.context = new activity.Context({ ...exports.defaultActivityInfo, ...info }, promise, abortController.signal, heartbeatCallback);
238
+ }
239
+ /**
240
+ * Run a function in Activity Context
241
+ */
242
+ run(fn, ...args) {
243
+ return activity.asyncLocalStorage.run(this.context, fn, ...args);
244
+ }
245
+ }
246
+ exports.MockActivityEnvironment = MockActivityEnvironment;
247
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import * as grpc from '@grpc/grpc-js';
2
+ import { Connection as BaseConnection, ConnectionOptions } from '@temporalio/client';
3
+ import { ConnectionCtorOptions as BaseConnectionCtorOptions } from '@temporalio/client/lib/connection';
4
+ import { temporal } from '../generated-protos';
5
+ export declare type TestService = temporal.api.testservice.v1.TestService;
6
+ export declare const TestService: typeof temporal.api.testservice.v1.TestService;
7
+ interface ConnectionCtorOptions extends BaseConnectionCtorOptions {
8
+ testService: TestService;
9
+ }
10
+ /**
11
+ * A Connection class that can be used to interact with both the test server's TestService and WorkflowService
12
+ */
13
+ export declare class Connection extends BaseConnection {
14
+ static readonly TestServiceClient: grpc.ServiceClientConstructor;
15
+ readonly testService: TestService;
16
+ protected static createCtorOptions(options?: ConnectionOptions): ConnectionCtorOptions;
17
+ static lazy(options?: ConnectionOptions): Connection;
18
+ static connect(options?: ConnectionOptions): Promise<Connection>;
19
+ protected constructor(options: ConnectionCtorOptions);
20
+ }
21
+ export {};
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.Connection = exports.TestService = void 0;
27
+ const grpc = __importStar(require("@grpc/grpc-js"));
28
+ const client_1 = require("@temporalio/client");
29
+ const generated_protos_1 = require("../generated-protos");
30
+ exports.TestService = generated_protos_1.temporal.api.testservice.v1.TestService;
31
+ /**
32
+ * A Connection class that can be used to interact with both the test server's TestService and WorkflowService
33
+ */
34
+ class Connection extends client_1.Connection {
35
+ constructor(options) {
36
+ super(options);
37
+ this.testService = options.testService;
38
+ }
39
+ static createCtorOptions(options) {
40
+ const ctorOptions = client_1.Connection.createCtorOptions(options);
41
+ const rpcImpl = this.generateRPCImplementation({
42
+ serviceName: 'temporal.api.testservice.v1.TestService',
43
+ client: ctorOptions.client,
44
+ callContextStorage: ctorOptions.callContextStorage,
45
+ interceptors: ctorOptions.options.interceptors,
46
+ });
47
+ const testService = exports.TestService.create(rpcImpl, false, false);
48
+ return { ...ctorOptions, testService };
49
+ }
50
+ static lazy(options) {
51
+ const ctorOptions = this.createCtorOptions(options);
52
+ return new this(ctorOptions);
53
+ }
54
+ static async connect(options) {
55
+ const ret = this.lazy(options);
56
+ await ret.ensureConnected();
57
+ return ret;
58
+ }
59
+ }
60
+ exports.Connection = Connection;
61
+ Connection.TestServiceClient = grpc.makeGenericClientConstructor({}, 'TestService', {});
62
+ //# sourceMappingURL=test-service-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-service-client.js","sourceRoot":"","sources":["../src/test-service-client.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAAsC;AACtC,+CAAqF;AAErF,0DAA+C;AAGhC,mBAAW,GAAK,2BAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,aAAC;AAM3D;;GAEG;AACH,MAAa,UAAW,SAAQ,mBAAc;IA2B5C,YAAsB,OAA8B;QAClD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACzC,CAAC;IA1BS,MAAM,CAAC,iBAAiB,CAAC,OAA2B;QAC5D,MAAM,WAAW,GAAG,mBAAc,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,yBAAyB,CAAC;YAC7C,WAAW,EAAE,yCAAyC;YACtD,MAAM,EAAE,WAAW,CAAC,MAAM;YAC1B,kBAAkB,EAAE,WAAW,CAAC,kBAAkB;YAClD,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,YAAY;SAC/C,CAAC,CAAC;QACH,MAAM,WAAW,GAAG,mBAAW,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QAC9D,OAAO,EAAE,GAAG,WAAW,EAAE,WAAW,EAAE,CAAC;IACzC,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,OAA2B;QACrC,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QACpD,OAAO,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAA2B;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/B,MAAM,GAAG,CAAC,eAAe,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC;IACb,CAAC;;AAzBH,gCA+BC;AA9BwB,4BAAiB,GAAG,IAAI,CAAC,4BAA4B,CAAC,EAAE,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC"}
package/lib/utils.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { Connection } from '@temporalio/client';
2
+ export declare function waitOnNamespace(connection: Connection, namespace: string, maxAttempts?: number, retryIntervalSecs?: number): Promise<void>;
3
+ export declare function createNamespace(connection: Connection, namespace: string, maxAttempts?: number, retryIntervalSecs?: number): Promise<void>;
package/lib/utils.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createNamespace = exports.waitOnNamespace = void 0;
4
+ const internal_workflow_common_1 = require("@temporalio/internal-workflow-common");
5
+ async function waitOnNamespace(connection, namespace, maxAttempts = 100, retryIntervalSecs = 1) {
6
+ const runId = '12345678-dead-beef-1234-1234567890ab';
7
+ for (let attempt = 1; attempt <= maxAttempts; ++attempt) {
8
+ try {
9
+ await connection.workflowService.getWorkflowExecutionHistory({
10
+ namespace,
11
+ execution: { workflowId: 'fake', runId },
12
+ });
13
+ }
14
+ catch (err) {
15
+ if (err.details.includes('workflow history not found') || err.details.includes(runId)) {
16
+ break;
17
+ }
18
+ if (attempt === maxAttempts) {
19
+ throw err;
20
+ }
21
+ await new Promise((resolve) => setTimeout(resolve, retryIntervalSecs * 1000));
22
+ }
23
+ }
24
+ }
25
+ exports.waitOnNamespace = waitOnNamespace;
26
+ async function createNamespace(connection, namespace, maxAttempts = 100, retryIntervalSecs = 1) {
27
+ for (let attempt = 1; attempt <= maxAttempts; ++attempt) {
28
+ try {
29
+ await connection.workflowService.registerNamespace({
30
+ namespace,
31
+ workflowExecutionRetentionPeriod: (0, internal_workflow_common_1.msToTs)('1 day'),
32
+ });
33
+ break;
34
+ }
35
+ catch (err) {
36
+ if (err.details === 'Namespace already exists.') {
37
+ break;
38
+ }
39
+ if (attempt === maxAttempts) {
40
+ throw err;
41
+ }
42
+ await new Promise((resolve) => setTimeout(resolve, retryIntervalSecs * 1000));
43
+ }
44
+ }
45
+ }
46
+ exports.createNamespace = createNamespace;
47
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AACA,mFAA8D;AAEvD,KAAK,UAAU,eAAe,CACnC,UAAsB,EACtB,SAAiB,EACjB,WAAW,GAAG,GAAG,EACjB,iBAAiB,GAAG,CAAC;IAErB,MAAM,KAAK,GAAG,sCAAsC,CAAC;IACrD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,EAAE,OAAO,EAAE;QACvD,IAAI;YACF,MAAM,UAAU,CAAC,eAAe,CAAC,2BAA2B,CAAC;gBAC3D,SAAS;gBACT,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE;aACzC,CAAC,CAAC;SACJ;QAAC,OAAO,GAAQ,EAAE;YACjB,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,4BAA4B,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;gBACrF,MAAM;aACP;YACD,IAAI,OAAO,KAAK,WAAW,EAAE;gBAC3B,MAAM,GAAG,CAAC;aACX;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC,CAAC,CAAC;SAC/E;KACF;AACH,CAAC;AAvBD,0CAuBC;AAEM,KAAK,UAAU,eAAe,CACnC,UAAsB,EACtB,SAAiB,EACjB,WAAW,GAAG,GAAG,EACjB,iBAAiB,GAAG,CAAC;IAErB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,EAAE,OAAO,EAAE;QACvD,IAAI;YACF,MAAM,UAAU,CAAC,eAAe,CAAC,iBAAiB,CAAC;gBACjD,SAAS;gBACT,gCAAgC,EAAE,IAAA,iCAAM,EAAC,OAAO,CAAC;aAClD,CAAC,CAAC;YACH,MAAM;SACP;QAAC,OAAO,GAAQ,EAAE;YACjB,IAAI,GAAG,CAAC,OAAO,KAAK,2BAA2B,EAAE;gBAC/C,MAAM;aACP;YACD,IAAI,OAAO,KAAK,WAAW,EAAE;gBAC3B,MAAM,GAAG,CAAC;aACX;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC,CAAC,CAAC;SAC/E;KACF;AACH,CAAC;AAvBD,0CAuBC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@temporalio/testing",
3
- "version": "0.23.1",
3
+ "version": "1.0.0-rc.0",
4
4
  "description": "Temporal.io SDK Testing sub-package",
5
5
  "main": "lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -18,10 +18,10 @@
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
20
  "@grpc/grpc-js": "^1.5.7",
21
- "@temporalio/activity": "^0.23.0",
22
- "@temporalio/client": "^0.23.0",
23
- "@temporalio/common": "^0.23.0",
24
- "@temporalio/worker": "^0.23.1",
21
+ "@temporalio/activity": "^1.0.0-rc.0",
22
+ "@temporalio/client": "^1.0.0-rc.0",
23
+ "@temporalio/common": "^1.0.0-rc.0",
24
+ "@temporalio/worker": "^1.0.0-rc.0",
25
25
  "@types/long": "^4.0.1",
26
26
  "abort-controller": "^3.0.0",
27
27
  "get-port": "^6.1.2",
@@ -37,10 +37,11 @@
37
37
  "homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/testing",
38
38
  "files": [
39
39
  "lib",
40
- "generated-protos"
40
+ "generated-protos",
41
+ "scripts"
41
42
  ],
42
43
  "publishConfig": {
43
44
  "access": "public"
44
45
  },
45
- "gitHead": "02f84f02cf2b28df57dd77f02514976a1ce83822"
46
+ "gitHead": "c25e91309b980f2118df4048d760306982019871"
46
47
  }
@@ -0,0 +1,18 @@
1
+ import os from 'node:os';
2
+ import { URL, fileURLToPath } from 'node:url';
3
+
4
+ const platformMapping = { darwin: 'macOS', linux: 'linux', win32: 'windows' };
5
+ const archAlias = { x64: 'amd64', arm64: 'aarch64' };
6
+
7
+ export const systemPlatform = platformMapping[os.platform()];
8
+ if (!systemPlatform) {
9
+ throw new Error(`Unsupported platform ${os.platform()}`);
10
+ }
11
+
12
+ export const systemArch = archAlias[os.arch()];
13
+ if (!systemArch) {
14
+ throw new Error(`Unsupported architecture ${os.arch()}`);
15
+ }
16
+
17
+ const ext = systemPlatform === 'windows' ? '.exe' : '';
18
+ export const outputPath = fileURLToPath(new URL(`../test-server${ext}`, import.meta.url));
@@ -0,0 +1,81 @@
1
+ import { resolve, dirname } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import { promisify } from 'util';
4
+ import dedent from 'dedent';
5
+ import glob from 'glob';
6
+ import { statSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
7
+ import pbjs from 'protobufjs/cli/pbjs.js';
8
+ import pbts from 'protobufjs/cli/pbts.js';
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const outputDir = resolve(__dirname, '../generated-protos');
12
+ const outputFile = resolve(outputDir, 'index.js');
13
+ const protoBaseDir = resolve(__dirname, '../proto');
14
+
15
+ const serviceProtoPath = resolve(protoBaseDir, 'temporal/api/testservice/v1/service.proto');
16
+
17
+ function mtime(path) {
18
+ try {
19
+ return statSync(path).mtimeMs;
20
+ } catch (err) {
21
+ if (err.code === 'ENOENT') {
22
+ return 0;
23
+ }
24
+ throw err;
25
+ }
26
+ }
27
+
28
+ async function compileProtos(protoPath, jsOutputFile, dtsOutputFile, ...args) {
29
+ console.log(`Creating protobuf JS definitions from ${protoPath}`);
30
+
31
+ const pbjsArgs = [
32
+ ...args,
33
+ '--wrap',
34
+ 'commonjs',
35
+ '--target',
36
+ 'static-module',
37
+ '--force-long',
38
+ '--no-verify',
39
+ '--root',
40
+ '__temporal_testing',
41
+ '--out',
42
+ jsOutputFile,
43
+ protoPath,
44
+ ];
45
+ await promisify(pbjs.main)(pbjsArgs);
46
+
47
+ console.log(`Creating protobuf TS definitions from ${protoPath}`);
48
+ await promisify(pbts.main)(['--out', dtsOutputFile, jsOutputFile]);
49
+
50
+ // Fix issue where Long is not found in TS definitions (https://github.com/protobufjs/protobuf.js/issues/1533)
51
+ const pbtsOutput = readFileSync(dtsOutputFile, 'utf8');
52
+ writeFileSync(
53
+ dtsOutputFile,
54
+ dedent`
55
+ import Long from "long";
56
+ ${pbtsOutput}
57
+ `
58
+ );
59
+ }
60
+
61
+ async function main() {
62
+ mkdirSync(outputDir, { recursive: true });
63
+
64
+ const protoFiles = glob.sync(resolve(protoBaseDir, '**/*.proto'));
65
+ const protosMTime = Math.max(...protoFiles.map(mtime));
66
+ const genMTime = mtime(outputFile);
67
+
68
+ if (protosMTime < genMTime) {
69
+ console.log('Assuming protos are up to date');
70
+ return;
71
+ }
72
+
73
+ await compileProtos(serviceProtoPath, outputFile, resolve(outputDir, 'index.d.ts'), '--path', resolve(protoBaseDir));
74
+
75
+ console.log('Done');
76
+ }
77
+
78
+ main().catch((err) => {
79
+ console.error(err);
80
+ process.exit(1);
81
+ });
@@ -0,0 +1,91 @@
1
+ import stream from 'node:stream';
2
+ import util from 'node:util';
3
+ import zlib from 'node:zlib';
4
+ import fs from 'node:fs';
5
+ import got from 'got';
6
+ import tar from 'tar-stream';
7
+ import unzipper from 'unzipper';
8
+ import { outputPath, systemArch, systemPlatform } from './common.mjs';
9
+
10
+ try {
11
+ if (fs.statSync(outputPath).isFile) {
12
+ console.log('Found existing test server executable', { path: outputPath });
13
+ process.exit(0);
14
+ }
15
+ } catch (err) {
16
+ if (err.code !== 'ENOENT') {
17
+ throw err;
18
+ }
19
+ }
20
+
21
+ const pipeline = util.promisify(stream.pipeline);
22
+
23
+ const defaultHeaders = {
24
+ 'User-Agent': '@temporalio/testing installer',
25
+ };
26
+
27
+ const { GITHUB_TOKEN } = process.env;
28
+
29
+ if (GITHUB_TOKEN) {
30
+ console.log(`Using GITHUB_TOKEN`);
31
+ defaultHeaders['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
32
+ }
33
+
34
+ const latestReleaseRes = await got('https://api.github.com/repos/temporalio/sdk-java/releases/latest', {
35
+ headers: {
36
+ ...defaultHeaders,
37
+ Accept: 'application/vnd.github.v3+json',
38
+ },
39
+ }).json();
40
+
41
+ function findTestServerAsset(assets) {
42
+ for (const asset of assets) {
43
+ const m = asset.name.match(/^temporal-test-server_[^_]+_([^_]+)_([^.]+)\.(?:zip|tar.gz)$/);
44
+ if (m) {
45
+ const [_, assetPlatform, _assetArch] = m;
46
+ if (assetPlatform === systemPlatform) {
47
+ // TODO: assetArch === systemArch (no arm builds for test server yet)
48
+ return asset;
49
+ }
50
+ }
51
+ }
52
+ throw new Error(`No prebuilt test server for ${systemPlatform}-${systemArch}`);
53
+ }
54
+
55
+ const asset = findTestServerAsset(latestReleaseRes.assets);
56
+ console.log('Downloading test server', { asset: asset.name, outputPath });
57
+
58
+ if (asset.content_type === 'application/x-gzip' || asset.content_type === 'application/x-gtar') {
59
+ const extract = tar.extract();
60
+ extract.on('entry', (_headers, stream, next) => {
61
+ stream.pipe(fs.createWriteStream(outputPath));
62
+ next();
63
+ });
64
+ await pipeline(
65
+ got.stream(asset.browser_download_url, {
66
+ headers: {
67
+ ...defaultHeaders,
68
+ },
69
+ }),
70
+ zlib.createGunzip(),
71
+ extract
72
+ );
73
+ await fs.promises.chmod(outputPath, 0o755);
74
+ } else if (asset.content_type === 'application/zip') {
75
+ got
76
+ .stream(asset.browser_download_url, {
77
+ headers: {
78
+ ...defaultHeaders,
79
+ },
80
+ })
81
+ .pipe(unzipper.Parse())
82
+ .on('entry', (entry) => {
83
+ if (entry.type === 'File') {
84
+ entry.pipe(fs.createWriteStream(outputPath));
85
+ } else {
86
+ entry.autodrain();
87
+ }
88
+ });
89
+ } else {
90
+ throw new Error(`Unexpected content type for Test server download: ${asset.content_type}`);
91
+ }