Skip to content

Commit e6dcf5b

Browse files
dougborgclaude
andauthored
feat(ts): wire semantic-release + npm publish workflow (#4)
Adds end-to-end release automation for the TypeScript client: - Package renamed from @statuspro/client to statuspro-client (unscoped; no npm org needed). - publishConfig sets access=public, provenance=true, registry=npmjs. - semantic-release + plugins added as devDependencies. Config lives at packages/statuspro-client/.releaserc.json with ts-scope filter: feat(ts) -> minor, fix/perf(ts) -> patch, (ts)! -> major, every other commit -> no release. Tag format: ts-v{version}. - New .github/workflows/release-ts.yml mirrors the Python release flow. Runs lint/typecheck/test/build, then semantic-release with OIDC (id-token: write + NPM_CONFIG_PROVENANCE=true) so publishes are signed via the npm Trusted Publisher pairing. Also fixes the /orders query-params test assertion that still referenced the old Katana-era `category=widgets` sample after the StatusPro rewrite. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d111f35 commit e6dcf5b

14 files changed

Lines changed: 2333 additions & 52 deletions

File tree

.github/workflows/release-ts.yml

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
name: Release TS Client
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
inputs:
9+
force_publish:
10+
description: "Force-publish current version to npm (bypass semantic-release)"
11+
type: boolean
12+
default: false
13+
14+
permissions: {}
15+
16+
jobs:
17+
test:
18+
name: TS tests
19+
runs-on: ubuntu-latest
20+
permissions:
21+
contents: read
22+
steps:
23+
- uses: actions/checkout@v6
24+
with:
25+
fetch-depth: 0
26+
27+
- uses: pnpm/action-setup@v4
28+
with:
29+
version: 9
30+
31+
- uses: actions/setup-node@v5
32+
with:
33+
node-version: "20"
34+
cache: "pnpm"
35+
36+
- name: Install dependencies
37+
run: pnpm install --frozen-lockfile
38+
39+
- name: Lint
40+
working-directory: packages/statuspro-client
41+
run: pnpm run lint
42+
43+
- name: Typecheck
44+
working-directory: packages/statuspro-client
45+
run: pnpm run typecheck
46+
47+
- name: Test
48+
working-directory: packages/statuspro-client
49+
run: pnpm run test
50+
51+
- name: Build
52+
working-directory: packages/statuspro-client
53+
run: pnpm run build
54+
55+
release-ts:
56+
name: Release TS client
57+
needs: test
58+
runs-on: ubuntu-latest
59+
concurrency:
60+
group: release-ts
61+
cancel-in-progress: false
62+
permissions:
63+
contents: write
64+
id-token: write
65+
issues: write
66+
pull-requests: write
67+
outputs:
68+
released: ${{ steps.release.outputs.new_release_published }}
69+
version: ${{ steps.release.outputs.new_release_version }}
70+
steps:
71+
- uses: actions/checkout@v6
72+
with:
73+
fetch-depth: 0
74+
persist-credentials: false
75+
76+
- uses: pnpm/action-setup@v4
77+
with:
78+
version: 9
79+
80+
- uses: actions/setup-node@v5
81+
with:
82+
node-version: "20"
83+
registry-url: "https://registry.npmjs.org"
84+
cache: "pnpm"
85+
86+
- name: Install dependencies
87+
run: pnpm install --frozen-lockfile
88+
89+
- name: Build
90+
working-directory: packages/statuspro-client
91+
run: pnpm run build
92+
93+
- name: Check for TS changes since last ts-v tag
94+
id: check
95+
run: |
96+
LAST_TAG=$(git tag --list 'ts-v*' --sort=-v:refname | head -n1)
97+
if [ -z "$LAST_TAG" ]; then
98+
echo "No prior ts-v tag — first release will run."
99+
echo "has_changes=true" >> "$GITHUB_OUTPUT"
100+
elif git diff --quiet "$LAST_TAG"..HEAD -- packages/statuspro-client/ docs/statuspro-openapi.yaml; then
101+
echo "No TS-client changes since $LAST_TAG — skipping release."
102+
echo "has_changes=false" >> "$GITHUB_OUTPUT"
103+
else
104+
echo "Changes detected since $LAST_TAG."
105+
echo "has_changes=true" >> "$GITHUB_OUTPUT"
106+
fi
107+
108+
- name: Semantic release
109+
id: release
110+
if: steps.check.outputs.has_changes == 'true' || inputs.force_publish == true
111+
working-directory: packages/statuspro-client
112+
env:
113+
GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }}
114+
NPM_CONFIG_PROVENANCE: "true"
115+
run: |
116+
# Capture semantic-release output so callers can read outputs.
117+
OUTPUT=$(npx semantic-release 2>&1) || {
118+
EXIT=$?
119+
echo "$OUTPUT"
120+
exit "$EXIT"
121+
}
122+
echo "$OUTPUT"
123+
if echo "$OUTPUT" | grep -q "Published release"; then
124+
VERSION=$(echo "$OUTPUT" | grep -oE "version [0-9]+\.[0-9]+\.[0-9]+" | head -n1 | awk '{print $2}')
125+
echo "new_release_published=true" >> "$GITHUB_OUTPUT"
126+
echo "new_release_version=$VERSION" >> "$GITHUB_OUTPUT"
127+
else
128+
echo "new_release_published=false" >> "$GITHUB_OUTPUT"
129+
fi

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ This monorepo ships:
2525
| -------------------------------------------------------- | ---------- | ------- | -------------------------------------------------- |
2626
| [statuspro-openapi-client](statuspro_public_api_client/) | Python | 0.1.0 | API client with transport-layer resilience |
2727
| [statuspro-mcp-server](statuspro_mcp_server/) | Python | 0.1.0 | Model Context Protocol server for AI assistants |
28-
| [@statuspro/client](packages/statuspro-client/) | TypeScript | 0.1.0 | TypeScript/JavaScript client with full type safety |
28+
| [statuspro-client](packages/statuspro-client/) | TypeScript | 0.1.0 | TypeScript/JavaScript client with full type safety |
2929

3030
## Features
3131

@@ -63,11 +63,11 @@ asyncio.run(main())
6363
### TypeScript Client
6464

6565
```bash
66-
npm install @statuspro/client
66+
npm install statuspro-client
6767
```
6868

6969
```typescript
70-
import { StatusProClient } from '@statuspro/client';
70+
import { StatusProClient } from 'statuspro-client';
7171

7272
const client = await StatusProClient.create();
7373
const response = await client.get('/orders');
@@ -236,7 +236,7 @@ uv run poe check # full: format + lint + typecheck + test
236236
uv run poe test # just tests (pytest -n 4)
237237
uv run poe regenerate-client # regenerate the Python client from docs/statuspro-openapi.yaml
238238
uv run poe generate-pydantic # regenerate Pydantic v2 models
239-
pnpm --filter @statuspro/client generate # regenerate the TypeScript client
239+
pnpm --filter statuspro-client generate # regenerate the TypeScript client
240240
```
241241

242242
### Commit Standards

docs/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ Initial monorepo for the StatusPro API client ecosystem:
88

99
- `statuspro-openapi-client` — Python client with transport-layer resilience.
1010
- `statuspro-mcp-server` — MCP server exposing the API as 9 tools.
11-
- `@statuspro/client` — TypeScript client.
11+
- `statuspro-client` — TypeScript client.
1212

1313
Bootstrapped from the `katana-openapi-client` monorepo harness.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"branches": ["main"],
3+
"tagFormat": "ts-v${version}",
4+
"plugins": [
5+
[
6+
"@semantic-release/commit-analyzer",
7+
{
8+
"preset": "conventionalcommits",
9+
"releaseRules": [
10+
{ "type": "feat", "scope": "ts", "release": "minor" },
11+
{ "type": "fix", "scope": "ts", "release": "patch" },
12+
{ "type": "perf", "scope": "ts", "release": "patch" },
13+
{ "type": "revert", "scope": "ts", "release": "patch" },
14+
{ "breaking": true, "scope": "ts", "release": "major" },
15+
{ "type": "*", "release": false }
16+
],
17+
"parserOpts": {
18+
"noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"]
19+
}
20+
}
21+
],
22+
[
23+
"@semantic-release/release-notes-generator",
24+
{
25+
"preset": "conventionalcommits",
26+
"parserOpts": {
27+
"noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"]
28+
},
29+
"presetConfig": {
30+
"types": [
31+
{ "type": "feat", "section": "Features", "hidden": false },
32+
{ "type": "fix", "section": "Bug Fixes", "hidden": false },
33+
{ "type": "perf", "section": "Performance Improvements", "hidden": false },
34+
{ "type": "revert", "section": "Reverts", "hidden": false },
35+
{ "type": "docs", "section": "Documentation", "hidden": true },
36+
{ "type": "chore", "section": "Chores", "hidden": true }
37+
]
38+
}
39+
}
40+
],
41+
[
42+
"@semantic-release/changelog",
43+
{
44+
"changelogFile": "CHANGELOG.md",
45+
"changelogTitle": "# @statuspro/client Changelog"
46+
}
47+
],
48+
[
49+
"@semantic-release/npm",
50+
{
51+
"pkgRoot": ".",
52+
"tarballDir": "dist-npm"
53+
}
54+
],
55+
[
56+
"@semantic-release/git",
57+
{
58+
"assets": [
59+
"CHANGELOG.md",
60+
"package.json"
61+
],
62+
"message": "chore(ts-release): ${nextRelease.version}\n\n${nextRelease.notes}"
63+
}
64+
],
65+
[
66+
"@semantic-release/github",
67+
{
68+
"successComment": false,
69+
"failComment": false,
70+
"assets": [
71+
{ "path": "dist-npm/*.tgz", "label": "npm tarball" }
72+
]
73+
}
74+
]
75+
]
76+
}

packages/statuspro-client/README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# @statuspro/client
1+
# statuspro-client
22

33
TypeScript/JavaScript client for the
44
[StatusPro API](https://app.orderstatuspro.com/api/v1) with automatic
@@ -21,17 +21,17 @@ updating the status of orders.
2121
## Installation
2222

2323
```bash
24-
npm install @statuspro/client
24+
npm install statuspro-client
2525
# or
26-
pnpm add @statuspro/client
26+
pnpm add statuspro-client
2727
# or
28-
yarn add @statuspro/client
28+
yarn add statuspro-client
2929
```
3030

3131
## Quick Start
3232

3333
```typescript
34-
import { StatusProClient } from '@statuspro/client';
34+
import { StatusProClient } from 'statuspro-client';
3535

3636
// API key from STATUSPRO_API_KEY env var (or .env)
3737
const client = await StatusProClient.create();
@@ -48,7 +48,7 @@ console.log(`Found ${meta.total} orders across ${meta.last_page} pages`);
4848
## Types-only import
4949

5050
```typescript
51-
import type { OrderListItem, Status, OrderResponse } from '@statuspro/client/types';
51+
import type { OrderListItem, Status, OrderResponse } from 'statuspro-client/types';
5252

5353
function render(order: OrderListItem) {
5454
// ...
@@ -123,7 +123,7 @@ import {
123123
AuthenticationError,
124124
RateLimitError,
125125
ValidationError,
126-
} from '@statuspro/client';
126+
} from 'statuspro-client';
127127

128128
const response = await client.post('/orders/123/comment', {
129129
comment: 'Shipped.',
@@ -179,7 +179,7 @@ POSTs.
179179
## Advanced: Generated SDK
180180

181181
```typescript
182-
import { StatusProClient, listOrders, updateOrderStatus } from '@statuspro/client';
182+
import { StatusProClient, listOrders, updateOrderStatus } from 'statuspro-client';
183183

184184
const statuspro = await StatusProClient.create();
185185

@@ -216,7 +216,7 @@ npm install dotenv
216216

217217
```typescript
218218
import 'dotenv/config';
219-
import { StatusProClient } from '@statuspro/client';
219+
import { StatusProClient } from 'statuspro-client';
220220

221221
const client = StatusProClient.withApiKey(process.env.STATUSPRO_API_KEY!);
222222
```

packages/statuspro-client/docs/cookbook.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Ready-to-use recipes for the StatusPro TypeScript client. See the
1919
## List orders with filters
2020

2121
```typescript
22-
import { StatusProClient, listOrders } from '@statuspro/client';
22+
import { StatusProClient, listOrders } from 'statuspro-client';
2323

2424
const client = await StatusProClient.create();
2525

@@ -41,7 +41,7 @@ for (const order of data?.data ?? []) {
4141
## Look up an order by number + email
4242

4343
```typescript
44-
import { lookupOrder } from '@statuspro/client';
44+
import { lookupOrder } from 'statuspro-client';
4545

4646
const { data: order } = await lookupOrder({
4747
client: client.sdk,
@@ -56,7 +56,7 @@ if (order) {
5656
## Get one order with full detail
5757

5858
```typescript
59-
import { getOrder } from '@statuspro/client';
59+
import { getOrder } from 'statuspro-client';
6060

6161
const { data: order } = await getOrder({
6262
client: client.sdk,
@@ -77,7 +77,7 @@ transition.
7777
import {
7878
getViableStatuses,
7979
updateOrderStatus,
80-
} from '@statuspro/client';
80+
} from 'statuspro-client';
8181

8282
async function advanceToShipped(orderId: number): Promise<boolean> {
8383
const { data: viable } = await getViableStatuses({
@@ -108,7 +108,7 @@ async function advanceToShipped(orderId: number): Promise<boolean> {
108108
## Add a public comment
109109

110110
```typescript
111-
import { addOrderComment, RateLimitError, parseError } from '@statuspro/client';
111+
import { addOrderComment, RateLimitError, parseError } from 'statuspro-client';
112112

113113
const { response } = await addOrderComment({
114114
client: client.sdk,
@@ -128,7 +128,7 @@ if (!response.ok) {
128128
## Push back a due date
129129

130130
```typescript
131-
import { setOrderDueDate } from '@statuspro/client';
131+
import { setOrderDueDate } from 'statuspro-client';
132132

133133
await setOrderDueDate({
134134
client: client.sdk,
@@ -140,7 +140,7 @@ await setOrderDueDate({
140140
## Bulk-update up to 50 orders
141141

142142
```typescript
143-
import { bulkUpdateOrderStatus } from '@statuspro/client';
143+
import { bulkUpdateOrderStatus } from 'statuspro-client';
144144

145145
const { response } = await bulkUpdateOrderStatus({
146146
client: client.sdk,
@@ -158,7 +158,7 @@ console.log(`Bulk status code: ${response.status}`);
158158
## Load the full status catalog
159159

160160
```typescript
161-
import { getStatuses } from '@statuspro/client';
161+
import { getStatuses } from 'statuspro-client';
162162

163163
const { data: statuses } = await getStatuses({ client: client.sdk });
164164

@@ -187,7 +187,7 @@ Mock the fetch layer rather than the SDK:
187187

188188
```typescript
189189
import { describe, it, expect, beforeEach, vi } from 'vitest';
190-
import { StatusProClient } from '@statuspro/client';
190+
import { StatusProClient } from 'statuspro-client';
191191

192192
describe('my app', () => {
193193
beforeEach(() => {

0 commit comments

Comments
 (0)