Skip to content
Draft
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
233 changes: 233 additions & 0 deletions .github/workflows/ts-sdk-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
---
name: Release TypeScript SDK

on: # yamllint disable-line rule:truthy
workflow_dispatch:
inputs:
release_type:
description: "Stage the package for review or publish it directly"
required: true
type: choice
options:
- staged
- formal
tag:
description: "TypeScript SDK release tag (ts-sdk/<version>)"
required: true
type: string
npm_tag:
description: "npm dist-tag (for example: beta or latest)"
required: true
type: string

permissions:
contents: read

concurrency:
group: ts-sdk-npm-release
cancel-in-progress: false

jobs:
staged-release:
name: Stage release on npm
if: inputs.release_type == 'staged'
runs-on: ubuntu-latest
environment: ts-sdk-npm
permissions:
contents: read
id-token: write
steps:
- name: Validate release inputs
id: release
env:
NPM_TAG: ${{ inputs.npm_tag }}
RELEASE_TAG: ${{ inputs.tag }}
SEMVER_PATTERN: >-
^ts-sdk/([0-9]+\.[0-9]+\.[0-9]+
(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?)$
run: |
set -euo pipefail
if [[ ! "${RELEASE_TAG}" =~ ${SEMVER_PATTERN// /} ]]; then
echo "Release tag must have the form ts-sdk/<semver>" >&2
exit 1
fi
version="${BASH_REMATCH[1]}"
if [[ ! "${NPM_TAG}" =~ ^[0-9A-Za-z][0-9A-Za-z._-]*$ ]]; then
echo "A valid npm_tag is required" >&2
exit 1
fi
if [[ "${version}" == *-* && "${NPM_TAG}" == "latest" ]]; then
echo "Prereleases must not use the latest npm dist-tag" >&2
exit 1
fi
if [[ "${version}" != *-* && "${NPM_TAG}" != "latest" ]]; then
echo "Stable releases must use the latest npm dist-tag" >&2
exit 1
fi
echo "version=${version}" >> "${GITHUB_OUTPUT}"
- name: Checkout release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.tag }}
fetch-depth: 0
persist-credentials: false
- name: Confirm checkout is the requested tag
env:
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
test "$(git rev-parse "refs/tags/${RELEASE_TAG}^{commit}")" = "$(git rev-parse HEAD)"
git merge-base --is-ancestor HEAD "$(git rev-parse refs/remotes/origin/main)"
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24.19.0"
registry-url: "https://registry.npmjs.org"
package-manager-cache: false
- name: Enable release toolchain
run: |
set -euo pipefail
node --input-type=module --eval '
import { execFileSync } from "node:child_process";
const npmVersion = execFileSync("npm", ["--version"], { encoding: "utf8" });
const [major, minor] = npmVersion.split(".").map(Number);
if (major < 11 || (major === 11 && minor < 15)) process.exit(1);
'
corepack enable
- name: Verify package identity
working-directory: ts-sdk
env:
EXPECTED_VERSION: ${{ steps.release.outputs.version }}
run: |
set -euo pipefail
test "$(node --print "require('./package.json').name")" = "apache-airflow-ts-sdk"
test "$(node --print "require('./package.json').version")" = "${EXPECTED_VERSION}"
- name: Verify and package release
working-directory: ts-sdk
run: |
set -euo pipefail
pnpm install --frozen-lockfile
pnpm run lint
pnpm run format:check
pnpm run typecheck
pnpm test
pnpm run build
npm pack --dry-run
mkdir -p ../package-artifact
npm pack --pack-destination ../package-artifact
- name: Stage with npm trusted publishing
env:
NPM_TAG: ${{ inputs.npm_tag }}
run: >-
npm stage publish package-artifact/*.tgz
--access public --tag "${NPM_TAG}"

formal-release:
name: Publish formal release to npm
if: inputs.release_type == 'formal'
runs-on: ubuntu-latest
environment: ts-sdk-npm
permissions:
contents: read
id-token: write
steps:
- name: Validate release inputs
id: release
env:
NPM_TAG: ${{ inputs.npm_tag }}
RELEASE_TAG: ${{ inputs.tag }}
SEMVER_PATTERN: >-
^ts-sdk/([0-9]+\.[0-9]+\.[0-9]+
(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?)$
run: |
set -euo pipefail
if [[ ! "${RELEASE_TAG}" =~ ${SEMVER_PATTERN// /} ]]; then
echo "Release tag must have the form ts-sdk/<semver>" >&2
exit 1
fi
version="${BASH_REMATCH[1]}"
if [[ ! "${NPM_TAG}" =~ ^[0-9A-Za-z][0-9A-Za-z._-]*$ ]]; then
echo "A valid npm_tag is required" >&2
exit 1
fi
if [[ "${version}" == *-* && "${NPM_TAG}" == "latest" ]]; then
echo "Prereleases must not use the latest npm dist-tag" >&2
exit 1
fi
if [[ "${version}" != *-* && "${NPM_TAG}" != "latest" ]]; then
echo "Stable releases must use the latest npm dist-tag" >&2
exit 1
fi
echo "version=${version}" >> "${GITHUB_OUTPUT}"
- name: Checkout release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.tag }}
fetch-depth: 0
persist-credentials: false
- name: Confirm checkout is the requested tag
env:
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
test "$(git rev-parse "refs/tags/${RELEASE_TAG}^{commit}")" = "$(git rev-parse HEAD)"
git merge-base --is-ancestor HEAD "$(git rev-parse refs/remotes/origin/main)"
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24.19.0"
registry-url: "https://registry.npmjs.org"
package-manager-cache: false
- name: Enable release toolchain
run: |
set -euo pipefail
node --input-type=module --eval '
import { execFileSync } from "node:child_process";
const npmVersion = execFileSync("npm", ["--version"], { encoding: "utf8" });
const [major, minor] = npmVersion.split(".").map(Number);
if (major < 11 || (major === 11 && minor < 15)) process.exit(1);
'
corepack enable
- name: Verify package identity
working-directory: ts-sdk
env:
EXPECTED_VERSION: ${{ steps.release.outputs.version }}
run: |
set -euo pipefail
test "$(node --print "require('./package.json').name")" = "apache-airflow-ts-sdk"
test "$(node --print "require('./package.json').version")" = "${EXPECTED_VERSION}"
- name: Verify and package release
working-directory: ts-sdk
run: |
set -euo pipefail
pnpm install --frozen-lockfile
pnpm run lint
pnpm run format:check
pnpm run typecheck
pnpm test
pnpm run build
npm pack --dry-run
mkdir -p ../package-artifact
npm pack --pack-destination ../package-artifact
- name: Publish with npm trusted publishing
env:
NPM_TAG: ${{ inputs.npm_tag }}
run: >-
npm publish package-artifact/*.tgz
--access public --tag "${NPM_TAG}"
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The TypeScript SDK lets you implement Airflow task logic in TypeScript (or plain
Node.js. The Dag and its scheduling remain in Python; individual tasks delegate to a Node.js subprocess that
is spawned by :class:`~airflow.sdk.coordinators.node.NodeCoordinator` for each task instance.

The SDK is an ESM-only package that ships from the ``ts-sdk/`` directory of the Airflow repository. It is currently in **alpha** and its API may change.
The SDK is the ``apache-airflow-ts-sdk`` package (ESM-only). It is currently in **alpha** and its API may change.

.. warning::

Expand Down Expand Up @@ -93,7 +93,7 @@ entry point.

.. code-block:: typescript

import { Dag, DagRegistry, serveDags, type TaskHandlerArgs } from "@apache-airflow/ts-sdk";
import { Dag, DagRegistry, serveDags, type TaskHandlerArgs } from "apache-airflow-ts-sdk";

export async function buildMessage({ ctx, client }: TaskHandlerArgs) {
const upstream = await client.getXCom<string>({
Expand Down
2 changes: 1 addition & 1 deletion dev/breeze/tests/test_release_management_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ def test_get_package_version_possibly_from_stable_txt_for_ts_sdk(
monkeypatch.setattr(global_constants, "AIRFLOW_ROOT_PATH", tmp_path)
package_json = tmp_path / "ts-sdk" / "package.json"
package_json.parent.mkdir(parents=True)
package_json.write_text('{"name": "@apache-airflow/ts-sdk", "version": "0.2.0-alpha.1"}\n')
package_json.write_text('{"name": "apache-airflow-ts-sdk", "version": "0.2.0-alpha.1"}\n')
if stable_txt_content is not None:
stable_txt = tmp_path / "generated" / "_build" / "docs" / "ts-sdk" / "stable.txt"
stable_txt.parent.mkdir(parents=True)
Expand Down
65 changes: 62 additions & 3 deletions ts-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ runtime used to execute registered TypeScript handlers from Airflow.
## Task Handlers

```ts
import { Dag, DagRegistry, serveDags, type TaskHandlerArgs } from "@apache-airflow/ts-sdk";
import { Dag, DagRegistry, serveDags, type TaskHandlerArgs } from "apache-airflow-ts-sdk";

export async function sayHello({ ctx, client }: TaskHandlerArgs) {
const greeting = await client.getVariable("greeting");
Expand Down Expand Up @@ -98,7 +98,7 @@ Airflow metadata in the bundle itself.
TypeScript entrypoint:

```ts
import { Dag, DagRegistry, serveDags, type TaskHandlerArgs } from "@apache-airflow/ts-sdk";
import { Dag, DagRegistry, serveDags, type TaskHandlerArgs } from "apache-airflow-ts-sdk";

export async function extract({ client }: TaskHandlerArgs) {
const connection = await client.getConnection("sales_db");
Expand Down Expand Up @@ -151,7 +151,7 @@ entrypoint that serves them all:
```ts
import { salesDag } from "./sales/dag";
import { billingDag } from "./billing/dag";
import { DagRegistry, serveDags } from "@apache-airflow/ts-sdk";
import { DagRegistry, serveDags } from "apache-airflow-ts-sdk";

await serveDags(new DagRegistry(salesDag, billingDag));
```
Expand Down Expand Up @@ -271,3 +271,62 @@ gh workflow run "Publish Docs to S3" --repo apache/airflow --ref main \
Use `destination=staging` first to check the output, then `live`. Confirm that
`https://airflow.apache.org/docs/ts-sdk/stable/` resolves (allow time for cache
invalidation) and that `/docs/ts-sdk/` redirects to it.

## Publishing

The manually dispatched `Release TypeScript SDK` workflow has separate jobs
for [npm's staged-publishing flow](https://docs.npmjs.com/staged-publishing/)
and for direct formal publication. Configure the `apache-airflow-ts-sdk`
trusted publisher to permit both operations from the workflow's protected
`ts-sdk-npm` environment:

```bash
npm trust github apache-airflow-ts-sdk \
--repo apache/airflow \
--file ts-sdk-release.yml \
--environment ts-sdk-npm \
--allow-stage-publish \
--allow-publish
```

Create a `ts-sdk/<version>` tag whose version exactly matches `package.json`.
To submit the package to npm's private staging area for review, run:

```bash
gh workflow run ts-sdk-release.yml --repo apache/airflow --ref main \
-f release_type=staged \
-f tag=ts-sdk/1.0.0-beta1 \
-f npm_tag=beta
```

The staged job runs the full verification and package build, then calls
`npm stage publish`. The version is not publicly installable until a maintainer
reviews and approves it with 2FA. The following commands require npm 11.15 or
later:

```bash
npm stage list apache-airflow-ts-sdk
npm stage view <stage-id>
npm stage download <stage-id>
npm stage approve <stage-id>
```

The approval cannot run through the trusted-publisher workflow because npm
requires interactive proof of presence. Reject an unsuitable staged version
with `npm stage reject <stage-id>`. Do not run the formal workflow for a version
that is already staged; approve or reject that staged version instead.

To publish directly without npm's staging review, trigger the formal job:

```bash
gh workflow run ts-sdk-release.yml --repo apache/airflow --ref main \
-f release_type=formal \
-f tag=ts-sdk/1.0.0-beta1 \
-f npm_tag=beta
```

Use `latest` for stable releases and a non-`latest` tag such as `alpha`,
`beta`, or `rc` for prereleases. Both jobs use short-lived npm OIDC credentials
and automatically publish provenance. After verifying the trusted-publisher
setup, disable token-based publishing and revoke obsolete npm automation
tokens.
2 changes: 1 addition & 1 deletion ts-sdk/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ the `"return_value"` key by the active runtime, matching Python `@task`
behavior:

```ts
import { Dag, DagRegistry, serveDags, type TaskHandlerArgs } from "@apache-airflow/ts-sdk";
import { Dag, DagRegistry, serveDags, type TaskHandlerArgs } from "apache-airflow-ts-sdk";

export async function sayHello({ ctx, client }: TaskHandlerArgs) {
const greeting = await client.getVariable("greeting");
Expand Down
4 changes: 2 additions & 2 deletions ts-sdk/example/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@apache-airflow/ts-sdk-example",
"name": "apache-airflow-ts-sdk-example",
"private": true,
"version": "0.0.0",
"type": "module",
Expand All @@ -9,7 +9,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@apache-airflow/ts-sdk": "file:.."
"apache-airflow-ts-sdk": "file:.."
},
"devDependencies": {
"@types/node": "^26.1.2",
Expand Down
2 changes: 1 addition & 1 deletion ts-sdk/example/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { Dag, DagRegistry, serveDags, type TaskHandlerArgs } from "@apache-airflow/ts-sdk";
import { Dag, DagRegistry, serveDags, type TaskHandlerArgs } from "apache-airflow-ts-sdk";

const dag = new Dag("typescript_example");

Expand Down
2 changes: 1 addition & 1 deletion ts-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@apache-airflow/ts-sdk",
"name": "apache-airflow-ts-sdk",
"version": "0.1.0-alpha.0",
"packageManager": "pnpm@10.28.1",
"description": "TypeScript Task SDK for Apache Airflow task handlers",
Expand Down
Loading
Loading