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
131 changes: 131 additions & 0 deletions .github/workflows/publish-sdk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Publish `@redrob-labs/sdk` to npm.
#
# DELIBERATELY SEPARATE from release.yml. That workflow reaches the Apple and Windows signing
# credentials, and bolting an npm publish onto it would mean a missing or expired npm token turns a
# signed binary release red after the artifacts are already out. Here a token problem fails one run
# that has published nothing else.
#
# `workflow_dispatch` only, and dry-run by default, because the first publish of a public package
# under this scope is not reversible: npm allows unpublishing a new version only within 72 hours,
# and a name, once taken, stays taken.
#
# The version is NOT read from packages/sdk/js/package.json, which holds 0.0.0 on purpose. It comes
# from `version` below and reaches `publish.ts` as REDROB_VERSION, so an SDK build is answerable to
# the CLI release it was generated from.
#
# NPM_TOKEN MUST BE 2FA-EXEMPT. The first real run failed with `EOTP: This operation requires a
# one-time password` AFTER authenticating successfully and reaching "Publishing to
# registry.npmjs.org" -- so a token that works for reads is not enough. npm enforces two-factor on
# publish, and only two kinds of credential are exempt:
#
# * a classic token of type AUTOMATION (a classic "Publish" token still prompts for an OTP), or
# * a granular access token with Read and write on this package or scope.
#
# There is no workflow-side fix for EOTP: an interactive one-time password cannot be supplied by CI,
# which is the point of the exemption. Trusted publishing (OIDC) is the other route and needs no
# token at all, but it must be configured for the package on npmjs.com first and therefore cannot be
# used for a name that does not exist yet.
name: publish-sdk

on:
workflow_dispatch:
inputs:
version:
description: "Version to publish, without the leading v (e.g. 0.4.1). Defaults to the latest release tag."
required: false
type: string
dry_run:
description: "Build and pack, but publish nothing."
required: false
default: true
type: boolean

concurrency:
group: publish-sdk
cancel-in-progress: false

permissions:
contents: read

jobs:
publish:
runs-on: ubuntu-latest
# Pinned to this repository's immutable id, matching release.yml: a fork must never publish under
# this scope, and a rename must not silently switch the guard off.
if: github.repository_id == '1371675259'
steps:
# Tags are the version state when `version` is not given, and a shallow checkout carries none.
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: develop
fetch-depth: 0

- uses: ./.github/actions/setup-bun

- id: resolve
env:
REQUESTED: ${{ inputs.version }}
run: |
set -euo pipefail
if [ -n "$REQUESTED" ]; then
version="$REQUESTED"
else
# Same tag discipline as release.yml: only plain vMAJOR.MINOR.PATCH counts, so a
# prerelease or a legacy upstream-derived tag cannot become the SDK's version.
latest="$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname \
| grep -v -- '-' | head -n1 || true)"
if [ -z "$latest" ]; then
echo "::error::no release tag to take a version from, and none was given"
exit 1
fi
version="${latest#v}"
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "publishing version $version" >> "$GITHUB_STEP_SUMMARY"

# The package ships `dist` only, so this is what there is to publish.
- name: Build the SDK
working-directory: packages/sdk/js
run: bun run build

# Checks the token AUTHENTICATES, not merely that it is non-empty. The previous version only
# tested for emptiness, passed, and was followed by a publish that failed on auth policy -- which
# is worse than no check, because a green step implied the credential was good. `whoami` cannot
# prove the token is 2FA-exempt (only a publish attempt discovers EOTP), so the failure message
# names that as the remaining possibility rather than claiming the credential is fine.
- name: Check the npm token authenticates
if: ${{ !inputs.dry_run }}
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
if [ -z "${NPM_TOKEN:-}" ]; then
echo "::error::NPM_TOKEN is not set for this repository or its organization"
exit 1
fi
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
if ! who="$(npm whoami 2>&1)"; then
echo "::error::NPM_TOKEN did not authenticate: $who"
exit 1
fi
echo "authenticated as $who (2FA exemption is only provable by the publish itself)"

# One step for both modes, so the rehearsal walks the same code as the real thing and stops only at
# the registry call. Packing directly here instead would skip `publish.ts` -- and therefore skip the
# version injection, which is the part most likely to be wrong.
- name: Publish
working-directory: packages/sdk/js
env:
REDROB_VERSION: ${{ steps.resolve.outputs.version }}
REDROB_CHANNEL: latest
REDROB_PUBLISH_DRY_RUN: ${{ inputs.dry_run }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
# npm reads the token from the registry-scoped line, not from the environment name alone.
if [ -n "${NPM_TOKEN:-}" ]; then
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
fi
bun run ./script/publish.ts
ls -la ./*.tgz
18 changes: 9 additions & 9 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions github/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion github/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as core from "@actions/core"
import * as github from "@actions/github"
import type { Context as GitHubContext } from "@actions/github/lib/context"
import type { IssueCommentEvent, PullRequestReviewCommentEvent } from "@octokit/webhooks-types"
import { createRedrobClient } from "@redrob-code/sdk"
import { createRedrobClient } from "@redrob-labs/sdk"
import { spawn } from "node:child_process"
import { setTimeout as sleep } from "node:timers/promises"

Expand Down
2 changes: 1 addition & 1 deletion github/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
"@actions/github": "6.0.1",
"@octokit/graphql": "9.0.1",
"@octokit/rest": "catalog:",
"@redrob-code/sdk": "workspace:*"
"@redrob-labs/sdk": "workspace:*"
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"@aws-sdk/client-s3": "3.933.0",
"@redrob-code/plugin": "workspace:*",
"@redrob-code/script": "workspace:*",
"@redrob-code/sdk": "workspace:*",
"@redrob-labs/sdk": "workspace:*",
"heap-snapshot-toolkit": "1.1.3",
"typescript": "catalog:"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"dependencies": {
"@effect/platform-node": "catalog:",
"@redrob-code/core": "workspace:*",
"@redrob-code/sdk": "workspace:*",
"@redrob-labs/sdk": "workspace:*",
"@redrob-code/server": "workspace:*",
"@redrob-code/tui": "workspace:*",
"@opentui/core": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/services/daemon.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Global } from "@redrob-code/core/global"
import { InstallationVersion } from "@redrob-code/core/installation/version"
import { createRedrobClient } from "@redrob-code/sdk/v2/client"
import { createRedrobClient } from "@redrob-labs/sdk/v2/client"
import { ServerAuth } from "@redrob-code/server/auth"
import { Context, Effect, FileSystem, Layer, Option, Schedule, Schema, Scope } from "effect"
import { HttpServer } from "effect/unstable/http"
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/plugin/models-dev.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { define } from "./internal"
import type { ModelV2Info } from "@redrob-code/sdk/v2/types"
import type { ModelV2Info } from "@redrob-labs/sdk/v2/types"
import { Effect, Stream } from "effect"
import { EventV2 } from "../event"
import { ModelsDev } from "../models-dev"
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/plugin/variant.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * as VariantPlugin from "./variant"

import type { ModelV2Info } from "@redrob-code/sdk/v2/types"
import type { ModelV2Info } from "@redrob-labs/sdk/v2/types"
import { Effect } from "effect"
import { define } from "./internal"

Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/plugin/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Credential } from "@redrob-code/core/credential"
import { Integration } from "@redrob-code/core/integration"
import { ModelV2 } from "@redrob-code/core/model"
import { ProviderV2 } from "@redrob-code/core/provider"
import type { IntegrationEnvMethod, IntegrationKeyMethod, IntegrationOAuthMethod } from "@redrob-code/sdk/v2/types"
import type { IntegrationEnvMethod, IntegrationKeyMethod, IntegrationOAuthMethod } from "@redrob-labs/sdk/v2/types"
import { Effect } from "effect"

type Overrides = Partial<Omit<PluginContext, "options">>
Expand Down
Loading
Loading