Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ jobs:
- uses: actions/setup-node@v4

- run: npm ci
- name: Update Temporal SDK to latest
run: >
npm update
@temporalio/activity @temporalio/client @temporalio/common
@temporalio/plugin @temporalio/proto @temporalio/worker @temporalio/workflow
- run: npm run build
- run: npm run lint

Expand All @@ -153,6 +158,8 @@ jobs:
python-version: '3.10'
- uses: astral-sh/setup-uv@v5
- run: uv tool install poethepoet
- name: Update Temporal SDK to latest
run: uv lock --upgrade-package temporalio
- run: uv sync
- run: poe lint

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ dependencies {
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'com.jayway.jsonpath:json-path:2.6.0'
implementation 'info.picocli:picocli:4.6.2'
implementation 'io.temporal:temporal-sdk:1.33.0'
implementation 'io.temporal:temporal-sdk:latest.release'
implementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
implementation 'org.reflections:reflections:0.10.2'
}
Expand Down
149 changes: 135 additions & 14 deletions cmd/latest_sdk_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func latestSdkVersionCmd() *cli.Command {
var config LatestSdkVersionConfig
return &cli.Command{
Name: "latest-sdk-version",
Usage: "get the latest SDK version",
Usage: "get the latest SDK version from the package registry",
Flags: config.flags(),
Action: func(ctx *cli.Context) error {
return getLatestSdkVersion(config)
Expand All @@ -32,34 +32,155 @@ func (p *LatestSdkVersionConfig) flags() []cli.Flag {
}
}

// registryQuery describes how to fetch the latest version from a package registry.
type registryQuery struct {
url string
extract func(body []byte) (string, error)
}

// registryQueries maps normalized language names to their package registry queries.
var registryQueries = map[string]registryQuery{
"go": {
url: "https://proxy.golang.org/go.temporal.io/sdk/@latest",
extract: func(body []byte) (string, error) {
var resp struct {
Version string `json:"Version"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return "", err
}
return resp.Version, nil
},
},
// All @temporalio/* packages are published from the same monorepo at the same
// version, so checking any one package (client) gives the version for all.
"typescript": {
url: "https://registry.npmjs.org/@temporalio/client/latest",
extract: func(body []byte) (string, error) {
var resp struct {
Version string `json:"version"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return "", err
}
return resp.Version, nil
},
},
"java": {
url: "https://search.maven.org/solrsearch/select?q=g:io.temporal+AND+a:temporal-sdk&rows=1&wt=json",
extract: func(body []byte) (string, error) {
var resp struct {
Response struct {
Docs []struct {
LatestVersion string `json:"latestVersion"`
} `json:"docs"`
} `json:"response"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return "", err
}
if len(resp.Response.Docs) == 0 {
return "", fmt.Errorf("no results found on Maven Central")
}
return resp.Response.Docs[0].LatestVersion, nil
},
},
"php": {
url: "https://repo.packagist.org/p2/temporal/sdk.json",
extract: func(body []byte) (string, error) {
var resp struct {
Packages map[string][]struct {
Version string `json:"version"`
} `json:"packages"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return "", err
}
versions := resp.Packages["temporal/sdk"]
if len(versions) == 0 {
return "", fmt.Errorf("no versions found on Packagist")
}
return versions[0].Version, nil
},
},
"python": {
url: "https://pypi.org/pypi/temporalio/json",
extract: func(body []byte) (string, error) {
var resp struct {
Info struct {
Version string `json:"version"`
} `json:"info"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return "", err
}
return resp.Info.Version, nil
},
},
"dotnet": {
url: "https://api.nuget.org/v3-flatcontainer/temporalio/index.json",
extract: func(body []byte) (string, error) {
var resp struct {
Versions []string `json:"versions"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return "", err
}
// Find the latest stable version (no pre-release suffix)
for i := len(resp.Versions) - 1; i >= 0; i-- {
if !strings.Contains(resp.Versions[i], "-") {
return resp.Versions[i], nil
}
}
return "", fmt.Errorf("no stable versions found on NuGet")
},
},
"ruby": {
url: "https://rubygems.org/api/v1/gems/temporalio.json",
extract: func(body []byte) (string, error) {
var resp struct {
Version string `json:"version"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return "", err
}
return resp.Version, nil
},
},
}

func getLatestSdkVersion(config LatestSdkVersionConfig) error {
var sdk string
sdk, err := expandLangName(config.Lang)
lang, err := expandLangName(config.Lang)
if err != nil {
return err
}

url := fmt.Sprintf("https://api.github.com/repos/temporalio/sdk-%s/releases/latest", sdk)
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("failed to query the GH API for SDK version: %w", err)
query, ok := registryQueries[lang]
if !ok {
return fmt.Errorf("no package registry configured for language %q", lang)
}

resp, err := http.Get(query.url)
if err != nil {
return fmt.Errorf("failed to query package registry: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("package registry returned status %d for %s", resp.StatusCode, query.url)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read body of GitHub Get request: %w", err)
return fmt.Errorf("failed to read response body: %w", err)
}

var version struct {
TagName string `json:"tag_name"`
}
err = json.Unmarshal(body, &version)
version, err := query.extract(body)
if err != nil {
return fmt.Errorf("failed to decode json response: %w", err)
return fmt.Errorf("failed to extract version from response: %w", err)
}

fmt.Println(strings.TrimPrefix(version.TagName, "v"))
fmt.Println(strings.TrimPrefix(version, "v"))

return nil
}
16 changes: 10 additions & 6 deletions cmd/run_python.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import (
func (p *Preparer) BuildPythonProgram(ctx context.Context) (sdkbuild.Program, error) {
p.log.Info("Building Python project", "DirName", p.config.DirName)

// Get version from pyproject.toml if not present
// Get version from pyproject.toml if not present.
// If no version constraint is specified, version stays empty
// and the package manager will resolve to the latest release.
version := p.config.Version
versionFromPyProj := ""
if version == "" {
Expand All @@ -28,14 +30,16 @@ func (p *Preparer) BuildPythonProgram(ctx context.Context) (sdkbuild.Program, er
for _, line := range strings.Split(string(b), "\n") {
line = strings.TrimSpace(line)
if strings.Contains(line, "temporalio") {
version = line[strings.Index(line, `"`)+1 : strings.LastIndex(line, `"`)]
extracted := line[strings.Index(line, `"`)+1 : strings.LastIndex(line, `"`)]
// Only treat as a version constraint if it contains specifiers.
// A bare package name like "temporalio" means no constraint.
if extracted != "temporalio" {
version = extracted
versionFromPyProj = version
}
break
}
}
if version == "" {
return nil, fmt.Errorf("version not found in pyproject.toml")
}
versionFromPyProj = version
}

prog, err := sdkbuild.BuildPythonProgram(ctx, sdkbuild.BuildPythonProgramOptions{
Expand Down
9 changes: 4 additions & 5 deletions cmd/run_ruby.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import (
func (p *Preparer) BuildRubyProgram(ctx context.Context) (sdkbuild.Program, error) {
p.log.Info("Building Ruby project", "DirName", p.config.DirName)

// Get version from harness/ruby/Gemfile if not present
// Get version from harness/ruby/Gemfile if not present.
// If no version constraint is specified in the Gemfile, version stays empty
// and the package manager will resolve to the latest release.
version := p.config.Version
if version == "" {
b, err := os.ReadFile(filepath.Join(p.rootDir, "harness", "ruby", "Gemfile"))
Expand All @@ -26,7 +28,7 @@ func (p *Preparer) BuildRubyProgram(ctx context.Context) (sdkbuild.Program, erro
}
for _, line := range strings.Split(string(b), "\n") {
line = strings.TrimSpace(line)
if strings.Contains(line, `"temporalio"`) {
if strings.Contains(line, `"temporalio"`) || strings.Contains(line, `'temporalio'`) {
// Extract version from: gem "temporalio", "~> 1.2"
parts := strings.Split(line, ",")
if len(parts) >= 2 {
Expand All @@ -36,9 +38,6 @@ func (p *Preparer) BuildRubyProgram(ctx context.Context) (sdkbuild.Program, erro
break
}
}
if version == "" {
return nil, fmt.Errorf("version not found in harness/ruby/Gemfile")
}
}

prog, err := sdkbuild.BuildRubyProgram(ctx, sdkbuild.BuildRubyProgramOptions{
Expand Down
6 changes: 3 additions & 3 deletions dockerfiles/py.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ COPY go.mod go.sum main.go ./
# Build the CLI
RUN CGO_ENABLED=0 /usr/local/go/bin/go build -o temporal-features

COPY uv.lock pyproject.toml ./
COPY pyproject.toml ./

ARG SDK_VERSION
ARG SDK_REPO_URL
Expand All @@ -54,10 +54,10 @@ COPY --from=build /app/features /app/features
COPY --from=build /app/prepared /app/prepared
COPY --from=build /app/harness/python /app/harness/python
COPY --from=build /app/${REPO_DIR_OR_PLACEHOLDER} /app/${REPO_DIR_OR_PLACEHOLDER}
COPY --from=build /app/uv.lock /app/pyproject.toml /app/
COPY --from=build /app/pyproject.toml /app/
COPY --from=build /bin/uv /bin/uvx /bin/

ENV UV_NO_SYNC=1 UV_FROZEN=1 UV_OFFLINE=1
ENV UV_NO_SYNC=1 UV_OFFLINE=1

# Use entrypoint instead of command to "bake" the default command options
ENTRYPOINT ["/app/temporal-features", "run", "--lang", "py", "--prepared-dir", "prepared"]
2 changes: 1 addition & 1 deletion dotnet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<!-- Default SDK version if not specified in command line -->
<PropertyGroup Condition="'$(TemporalioVersion)' == '' And '$(TemporalioProjectReference)' == ''">
<TemporalioVersion>1.11.1</TemporalioVersion>
<TemporalioVersion>*</TemporalioVersion>
</PropertyGroup>

<Target Name="CheckCommandLineProperties">
Expand Down
2 changes: 1 addition & 1 deletion features/activity/cancel_try_cancel/feature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public async Task CancellableActivity()
}

// Send result as signal to workflow
await client.GetWorkflowHandle<MyWorkflow>(ActivityExecutionContext.Current.Info.WorkflowId).
await client.GetWorkflowHandle<MyWorkflow>(ActivityExecutionContext.Current.Info.WorkflowId!).
SignalAsync(wf => wf.SetActivityResultAsync(result));
}
}
Expand Down
2 changes: 1 addition & 1 deletion features/update/async_accepted/feature.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as assert from 'assert';
import ms from 'ms';
import ms from 'ms'; // eslint-disable-line import/no-named-as-default
import * as wf from '@temporalio/workflow';
import { Feature } from '@temporalio/harness';
import { Duration, StringValue } from '@temporalio/common';
Expand Down
2 changes: 1 addition & 1 deletion features/update/self/feature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public async Task MyActivity()
{
var handle =
Client.GetWorkflowHandle<MyWorkflow>(ActivityExecutionContext.Current.Info
.WorkflowId);
.WorkflowId!);
await handle.ExecuteUpdateAsync(wf => wf.UpdateMe());
}
}
Expand Down
27 changes: 11 additions & 16 deletions harness/php/src/Runtime/Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,21 @@

namespace Harness\Runtime;

use Temporal\Testing\Environment;
use Symfony\Component\Process\Process;

final class Runner
{
private Environment $environment;
private bool $started = false;
private ?Process $process = null;

public function __construct(
private State $runtime,
) {
$this->environment = Environment::create();
\register_shutdown_function(fn() => $this->stop());
}

public function start(): void
{
if ($this->started) {
if ($this->process?->isRunning()) {
return;
}

Expand All @@ -44,21 +42,18 @@ public function start(): void
$run->tlsKey === null or $rrCommand = [...$rrCommand, '-o', "temporal.tls.key={$run->tlsKey}"];
$run->tlsCert === null or $rrCommand = [...$rrCommand, '-o', "temporal.tls.cert={$run->tlsCert}"];
$run->tlsCaCert === null or $rrCommand = [...$rrCommand, '-o', "temporal.tls.ca-cert={$run->tlsCaCert}"];
$command = \implode(' ', $rrCommand);

// echo "\e[1;36mStart RoadRunner with command:\e[0m {$command}\n";
$this->environment->startRoadRunner($command);
$this->started = true;
$this->process = new Process($rrCommand);
$this->process->setTimeout(null);
$this->process->start();
\usleep(500_000);
}

public function stop(): void
{
if (!$this->started) {
return;
if ($this->process?->isRunning()) {
$this->process->stop();
$this->process = null;
}

// echo "\e[1;36mStop RoadRunner\e[0m\n";
$this->environment->stop();
$this->started = false;
}
}
}
5 changes: 3 additions & 2 deletions harness/python/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,9 @@ async def wait_for_activity_task_scheduled(
"""Wait for an activity task scheduled event."""
return await self.wait_for_event(
handle,
lambda event: event.event_type
== EventType.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED,
lambda event: (
event.event_type == EventType.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED
),
timeout,
)

Expand Down
Loading
Loading