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
15 changes: 15 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"snapshot": {
"useCalculatedVersion": true,
"prereleaseTemplate": "{tag}-{datetime}-{commit}"
}
}
6 changes: 6 additions & 0 deletions .changeset/polite-ends-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@upstash/box-cli": minor
"@upstash/box": minor
---

Initalize SDK and CLI
75 changes: 75 additions & 0 deletions .github/workflows/canary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Canary Release

on:
workflow_dispatch:
inputs:
package:
description: Package to release
required: true
type: choice
options:
- "@upstash/box"
- "@upstash/box-cli"

jobs:
canary:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: Version snapshot
run: pnpm changeset version --snapshot canary
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Read snapshot version
id: version
run: |
if [ "${{ inputs.package }}" = "@upstash/box" ]; then
PKG_DIR="packages/sdk"
else
PKG_DIR="packages/cli"
fi
VERSION=$(node -p "require('./${PKG_DIR}/package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Snapshot version: $VERSION"

- run: pnpm build

- name: Create GitHub prerelease
run: |
TAG="${{ inputs.package }}@${{ steps.version.outputs.version }}"
gh release create "$TAG" \
--prerelease \
--target "${{ github.sha }}" \
--title "$TAG" \
--generate-notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create release metadata
run: |
jq -n \
--arg name "${{ inputs.package }}" \
--arg version "${{ steps.version.outputs.version }}" \
'{packages: [{name: $name, version: $version}], prerelease: true}' > release-meta.json

- name: Upload release metadata
uses: actions/upload-artifact@v4
with:
name: release-meta
path: release-meta.json
55 changes: 55 additions & 0 deletions .github/workflows/changeset.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Changeset

on:
push:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- run: pnpm install --frozen-lockfile

- run: pnpm build

- uses: changesets/action@v1
id: changesets
with:
version: pnpm ci:version
publish: pnpm ci:tag
createGithubReleases: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create release metadata
if: steps.changesets.outputs.published == 'true'
run: |
echo "$PUBLISHED" | jq '{packages: ., prerelease: false}' > release-meta.json
env:
PUBLISHED: ${{ steps.changesets.outputs.publishedPackages }}

- name: Upload release metadata
if: steps.changesets.outputs.published == 'true'
uses: actions/upload-artifact@v4
with:
name: release-meta
path: release-meta.json
106 changes: 106 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: npm Publish

on:
workflow_run:
workflows: ["Changeset", "Canary Release"]
types: [completed]

permissions:
id-token: write
contents: read
actions: read

jobs:
load:
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.meta.outputs.packages }}
prerelease: ${{ steps.meta.outputs.prerelease }}
has_packages: ${{ steps.meta.outputs.has_packages }}

steps:
- name: Download release metadata
id: download
uses: actions/download-artifact@v4
with:
name: release-meta
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true

- name: Read metadata
id: meta
run: |
if [ -f release-meta.json ]; then
echo "has_packages=true" >> "$GITHUB_OUTPUT"
echo "packages=$(jq -c '.packages' release-meta.json)" >> "$GITHUB_OUTPUT"
echo "prerelease=$(jq -r '.prerelease' release-meta.json)" >> "$GITHUB_OUTPUT"
else
echo "has_packages=false" >> "$GITHUB_OUTPUT"
echo "packages=[]" >> "$GITHUB_OUTPUT"
echo "prerelease=false" >> "$GITHUB_OUTPUT"
fi

publish:
needs: load
if: needs.load.outputs.has_packages == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
package: ${{ fromJson(needs.load.outputs.packages) }}

steps:
- name: Parse package info
id: parse
run: |
PACKAGE="${{ matrix.package.name }}"
VERSION="${{ matrix.package.version }}"

if [ "$PACKAGE" = "@upstash/box" ]; then
PKG_DIR="packages/sdk"
elif [ "$PACKAGE" = "@upstash/box-cli" ]; then
PKG_DIR="packages/cli"
else
echo "Unknown package: $PACKAGE"
exit 1
fi

echo "package=$PACKAGE" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "pkg_dir=$PKG_DIR" >> "$GITHUB_OUTPUT"
echo "Package: $PACKAGE @ $VERSION (dir: $PKG_DIR)"

- uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_sha }}

- uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
registry-url: https://registry.npmjs.org

- run: pnpm install --frozen-lockfile

- name: Set version
run: npm version "${{ steps.parse.outputs.version }}" --no-git-tag-version
working-directory: ${{ steps.parse.outputs.pkg_dir }}

- run: pnpm build

- name: Publish (canary)
if: needs.load.outputs.prerelease == 'true'
run: pnpm --filter "${{ steps.parse.outputs.package }}" publish --tag canary --no-git-checks --access public
env:
NPM_CONFIG_PROVENANCE: "true"

- name: Publish (stable)
if: needs.load.outputs.prerelease != 'true'
run: pnpm --filter "${{ steps.parse.outputs.package }}" publish --no-git-checks --access public
env:
NPM_CONFIG_PROVENANCE: "true"
33 changes: 31 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ TypeScript SDK and CLI for [Upstash Box](https://upstash.com/docs/box) — sandb

| Package | Description |
|---------|-------------|
| [`@buggyhunter/box`](./packages/sdk) | TypeScript SDK — programmatic access to the Box API |
| [`@upstash/box`](./packages/sdk) | TypeScript SDK — programmatic access to the Box API |
| [`@upstash/box-cli`](./packages/cli) | CLI — REPL-first terminal interface wrapping the SDK |

## Quick start
Expand All @@ -27,7 +27,7 @@ pnpm dev
```
.
├── packages/
│ ├── sdk/ # @buggyhunter/box — TypeScript SDK
│ ├── sdk/ # @upstash/box — TypeScript SDK
│ │ ├── src/
│ │ └── examples/
│ └── cli/ # @upstash/box-cli — CLI + interactive REPL
Expand Down Expand Up @@ -69,6 +69,35 @@ cd packages/sdk && pnpm test
cd packages/cli && pnpm test
```

## Releasing

This repo uses [Changesets](https://github.com/changesets/changesets) for versioning and automated npm publishing via OIDC.

### Stable release

1. Create a changeset while working on your feature:
```bash
pnpm changeset
```
2. Merge your PR to `main`. The **Changeset** workflow creates a "Version Packages" PR that bumps versions and updates changelogs.
3. Merge the version PR. The workflow tags the release, creates a GitHub Release, and triggers **npm Publish** which publishes to npm.

### Canary release

1. Go to **Actions → Canary Release → Run workflow**, pick a package and branch.
2. The workflow creates a snapshot version (e.g. `0.2.0-canary-20260219131415-abc1234`), publishes to npm under the `canary` tag, and creates a GitHub prerelease.

### Workflows

| Workflow | Trigger | Purpose |
|----------|---------|---------|
| `ci.yml` | PR + push to main | Build and test (Node 18/20/22) |
| `changeset.yml` | Push to main | Version PR or tag + GitHub Release |
| `canary.yml` | Manual dispatch | Snapshot version + GitHub prerelease |
| `npm-publish.yml` | `workflow_run` (after changeset/canary) | Publish to npm with OIDC provenance |

`npm-publish.yml` is the sole npm trusted publisher — configure it on npmjs.com for both packages. No npm tokens or PATs required.

## Requirements

- Node.js >= 18
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
"build": "pnpm -r build",
"dev": "pnpm -r --parallel dev",
"test": "vitest run",
"test:integration": "vitest run -c packages/sdk/vitest.integration.config.ts"
"test:integration": "vitest run -c packages/sdk/vitest.integration.config.ts",
"changeset": "changeset",
"ci:version": "changeset version",
"ci:tag": "changeset tag"
},
"engines": {
"node": ">=18.0.0"
},
"devDependencies": {
"@changesets/cli": "^2.29.4",
"dotenv": "^17.3.1",
"vitest": "^4.0.18"
}
Expand Down
10 changes: 8 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@upstash/box-cli",
"version": "0.1.0",
"version": "0.0.0",
"description": "CLI for Upstash Box — REPL-first interface for AI coding agents",
"type": "module",
"bin": {
Expand All @@ -21,14 +21,20 @@
"author": "Upstash",
"license": "MIT",
"dependencies": {
"@buggyhunter/box": "workspace:*",
"@upstash/box": "workspace:*",
"commander": "^13.0.0",
"dotenv": "^17.3.1"
},
"devDependencies": {
"@types/node": "^20.10.0",
"typescript": "^5.3.0"
},
"publishConfig": {
"access": "public"
},
"files": [
"dist"
],
"engines": {
"node": ">=18.0.0"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/__tests__/commands/connect.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { connectCommand } from "../../commands/connect.js";

vi.mock("@buggyhunter/box", () => ({
vi.mock("@upstash/box", () => ({
Box: {
get: vi.fn(),
list: vi.fn(),
Expand All @@ -16,7 +16,7 @@ vi.mock("../../auth.js", () => ({
resolveToken: vi.fn((token?: string) => token ?? "resolved-token"),
}));

import { Box } from "@buggyhunter/box";
import { Box } from "@upstash/box";
import { startRepl } from "../../repl.js";

describe("connectCommand", () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/__tests__/commands/create.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { createCommand } from "../../commands/create.js";

vi.mock("@buggyhunter/box", () => ({
vi.mock("@upstash/box", () => ({
Box: {
create: vi.fn(),
},
Expand All @@ -15,7 +15,7 @@ vi.mock("../../auth.js", () => ({
resolveToken: vi.fn((token?: string) => token ?? "resolved-token"),
}));

import { Box } from "@buggyhunter/box";
import { Box } from "@upstash/box";
import { startRepl } from "../../repl.js";

describe("createCommand", () => {
Expand Down
Loading