Skip to content

Commit 5694960

Browse files
authored
Project harness for Typescript (#337)
## What was changed Adds `typescript/harness/` package, effectively a port of the same Python harness (`python/harness`). `typescript/harness` is a standalone Typescript package that supports the same harness semantics, structure, and API as the existing Python harness. The test suite (`typescript/harness/tests`) similarly mimics the existing Python harness test suite. A couple changes that are Typescript-specific: - the `workers/typescript` package now uses `npm workspaces`. This is so that we can enforce the same `temporalio` version between the test and the harness, which are separate Typescript packages. - `protogen.js` and `grpc-helpers.ts` exist to generate protobuf types and grpc helpers that the Typescript harness consumes (In contrast, Python commits the generated proto. The Typescript kitchensink protos were already generated on the fly, I stuck with that pattern for the harness protos) ## Why? - load testing ergonomics / DX - Language parity
1 parent 1747d53 commit 5694960

28 files changed

Lines changed: 1576 additions & 245 deletions

.dockerignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
**/node_modules
2+
workers/typescript/lib
3+
workers/typescript/tsconfig.tsbuildinfo
4+
workers/typescript/harness/dist*
5+
workers/typescript/harness/src/generated

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ omes.sln
2121
workers/dotnet/Temporalio.Omes.temp.csproj
2222

2323
workers/python/**/__pycache__/
24+
workers/typescript/harness/dist/
25+
workers/typescript/harness/dist-test/
26+
workers/typescript/harness/src/generated/
2427
workers/*/omes-temp-*/
2528
workers/*/prepared/
2629
workers/**/project-build-*/

cmd/dev/lint_and_format.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,21 @@ func lintAndFormatTypescriptWorker(ctx context.Context, workerDir string) error
174174
return err
175175
}
176176

177+
fmt.Println("Formatting TypeScript harness...")
178+
if err := runCommandInDir(ctx, workerDir, "npm", "run", "-w", "@temporalio/omes-project-harness", "format"); err != nil {
179+
return err
180+
}
181+
182+
fmt.Println("Linting TypeScript harness...")
183+
if err := runCommandInDir(ctx, workerDir, "npm", "run", "-w", "@temporalio/omes-project-harness", "lint"); err != nil {
184+
return err
185+
}
186+
187+
fmt.Println("Compiling TypeScript harness...")
188+
if err := runCommandInDir(ctx, workerDir, "npm", "run", "-w", "@temporalio/omes-project-harness", "typecheck"); err != nil {
189+
return err
190+
}
191+
177192
fmt.Println("✅ TypeScript lint-and-format completed successfully!")
178193
return nil
179194
}

cmd/dev/test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ func runTestWorker(ctx context.Context, language string) error {
8787
return err
8888
}
8989
}
90+
if language == "typescript" {
91+
if err := runTypeScriptHarnessTests(ctx, repoDir); err != nil {
92+
return err
93+
}
94+
}
9095
return testWorkerLocally(ctx, repoDir, language, sdkVersion)
9196
}
9297

@@ -110,6 +115,16 @@ func runDotnetHarnessTests(ctx context.Context, repoDir string) error {
110115
return nil
111116
}
112117

118+
func runTypeScriptHarnessTests(ctx context.Context, repoDir string) error {
119+
workerDir := filepath.Join(repoDir, "workers", "typescript")
120+
fmt.Println("Running TypeScript harness tests...")
121+
if err := runCommandInDir(ctx, workerDir, "npm", "run", "-w", "@temporalio/omes-project-harness", "test"); err != nil {
122+
return fmt.Errorf("failed TypeScript harness tests: %w", err)
123+
}
124+
fmt.Println("✅ TypeScript harness tests completed successfully!")
125+
return nil
126+
}
127+
113128
func testWorkerLocally(ctx context.Context, repoDir, language, sdkVersion string) error {
114129
args := []string{
115130
"go", "run", "./cmd", "run-scenario-with-worker",

dockerfiles/typescript.Dockerfile

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,8 @@ COPY workers/typescript ./workers/typescript
5151
# Install pnpm (sdkbuild uses pnpm to build typescript programs)
5252
RUN npm install -g pnpm
5353

54-
# Build typescript proto files
55-
# hadolint ignore=DL3003
56-
RUN cd workers/typescript && npm install && npm run proto-gen
57-
58-
# Build the worker
54+
# prepare-worker builds the TypeScript workspace itself: it installs npm deps,
55+
# runs the root build, and generates the prepared sdkbuild package.
5956
RUN CGO_ENABLED=0 ./temporal-omes prepare-worker --language ts --dir-name prepared --version "$SDK_VERSION"
6057

6158
# Copy the CLI and prepared feature to a "run" container.

workers/build.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package workers
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"html"
78
"io"
@@ -149,20 +150,23 @@ func (b *Builder) buildTypeScript(ctx context.Context, baseDir string) (sdkbuild
149150
// If version not provided, try to read it from package.json
150151
version := b.SdkOptions.Version
151152
if version == "" {
152-
b, err := os.ReadFile(filepath.Join(baseDir, "package.json"))
153+
packageJSON, err := os.ReadFile(filepath.Join(baseDir, "package.json"))
153154
if err != nil {
154155
return nil, fmt.Errorf("failed reading package.json: %w", err)
155156
}
156-
for line := range strings.SplitSeq(string(b), "\n") {
157-
line = strings.TrimSpace(line)
158-
if strings.HasPrefix(line, "\"temporalio:\"") || strings.HasPrefix(line, "\"@temporalio/") {
159-
split := strings.Split(line, "\"")
160-
version = split[len(split)-2]
161-
break
162-
}
157+
158+
var pkg struct {
159+
Dependencies map[string]string `json:"dependencies"`
160+
}
161+
if err := json.Unmarshal(packageJSON, &pkg); err != nil {
162+
return nil, fmt.Errorf("failed parsing package.json: %w", err)
163163
}
164+
// Pick a single temporal dependency, assumption is that the version for
165+
// other temporal dependency versions will match.
166+
const temporalTypeScriptSDKPackage = "@temporalio/client"
167+
version = pkg.Dependencies[temporalTypeScriptSDKPackage]
164168
if version == "" {
165-
return nil, fmt.Errorf("version not found in package.json")
169+
return nil, fmt.Errorf("version not found in package.json for %s", temporalTypeScriptSDKPackage)
166170
}
167171
}
168172

@@ -191,7 +195,8 @@ func (b *Builder) buildTypeScript(ctx context.Context, baseDir string) (sdkbuild
191195
ApplyToCommand: nil,
192196
Includes: []string{"../src/**/*.ts", "../src/protos/json-module.js", "../src/protos/root.js"},
193197
MoreDependencies: map[string]string{
194-
"winston": "^3.11.0",
198+
"@temporalio/omes-project-harness": "file:../harness",
199+
"winston": "^3.11.0",
195200
},
196201
Stdout: b.stdout,
197202
Stderr: b.stderr,

workers/run.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ func (r *Runner) Run(ctx context.Context, baseDir string) error {
130130
// The dotnet harness uses explicit subcommands like the Python harness.
131131
args = append(args, "worker")
132132
} else if r.SdkOptions.Language == clioptions.LangTypeScript {
133-
// Node also needs module
134-
args = append(args, "./tslib/omes.js")
133+
// Node also needs module before the harness subcommand.
134+
args = append(args, "./tslib/omes.js", "worker")
135135
}
136136
args = append(args, "--task-queue", r.TaskQueueName)
137137
if r.TaskQueueIndexSuffixEnd > 0 {

workers/typescript/.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
lib
22
src/protos/
3+
harness/
34
omes-temp-*
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist/
2+
dist-test/
3+
src/generated/
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import js from '@eslint/js';
2+
import tseslint from 'typescript-eslint';
3+
import prettierConfig from 'eslint-config-prettier';
4+
5+
export default tseslint.config(
6+
{
7+
ignores: [
8+
'**/node_modules/**',
9+
'**/dist/**',
10+
'**/dist-test/**',
11+
'**/*.js',
12+
'**/*.mjs',
13+
'**/*.cjs',
14+
'src/generated/**',
15+
'protogen.js',
16+
],
17+
},
18+
{
19+
files: ['src/**/*.ts', 'tests/**/*.ts'],
20+
extends: [js.configs.recommended, ...tseslint.configs.recommended, prettierConfig],
21+
languageOptions: {
22+
parserOptions: { project: ['./tsconfig.json', './tsconfig.test.json'] },
23+
},
24+
rules: {
25+
'@typescript-eslint/no-deprecated': 'warn',
26+
'@typescript-eslint/no-explicit-any': 'off',
27+
'@typescript-eslint/no-floating-promises': 'error',
28+
'@typescript-eslint/no-unused-vars': [
29+
'warn',
30+
{
31+
argsIgnorePattern: '^_',
32+
caughtErrorsIgnorePattern: '^_',
33+
varsIgnorePattern: '^_',
34+
},
35+
],
36+
'object-shorthand': ['error', 'always'],
37+
},
38+
},
39+
);

0 commit comments

Comments
 (0)