@connectedxm/admin 6.30.1 → 6.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/ship-to-prod/SKILL.md +217 -0
- package/.claude/skills/ship-to-staging/SKILL.md +220 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/openapi.json +43 -5
- package/package.json +1 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ship-to-prod
|
|
3
|
+
description: Open a release PR from `staging` to `main` in this repo. Handles the full flow — gathers commits, pulls Linear ticket references from commit messages and the PRs that merged into staging, drafts a title and body that match the repo's PR template, picks reviewers, and (after the user confirms) creates the PR with `gh pr create`. Use this whenever the user says anything along the lines of "open a staging PR", "release staging", "ship staging to prod", "cut a release", "PR staging", "promote staging", "merge staging into main", or any variant of opening/preparing a PR between the staging and main branches. Prefer this skill over running `gh pr create` directly — the manual flow misses Linear references, the version-bump check, and reviewer conventions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Staging → Main release PR
|
|
7
|
+
|
|
8
|
+
Use this when the user wants to open a release PR from `staging` into `main` in the `connectedxm/admin-sdk` repo (npm package `@connectedxm/admin`). Production is `main` here — pushing to `main` triggers `publish.yml`, which publishes both `@connectedxm/admin` and the generated `@connectedxm/admin-sdk` (typescript-axios output of `sdks/typescript/`) to npm. The backend repo uses `prod`, but this SDK ships from `main`.
|
|
9
|
+
|
|
10
|
+
## What you're producing
|
|
11
|
+
|
|
12
|
+
A draft PR (or, on confirmation, an actual PR via `gh pr create`) with:
|
|
13
|
+
|
|
14
|
+
1. **Base/head**: base `main`, head `staging` — never invent a feature branch
|
|
15
|
+
2. **Title** that reflects what's in the diff (see "Writing the title")
|
|
16
|
+
3. **Body** matching `.github/pull_request_template.md`, with Linear refs harvested from commits AND from any sub-PRs they reference, plus the Semantic Versioning section ticked
|
|
17
|
+
4. **Reviewers** drawn from the repo's collaborators (excluding the PR author)
|
|
18
|
+
|
|
19
|
+
The user has asked to **always confirm before creating** the PR. Show the draft, wait for the go-ahead, then call `gh pr create`.
|
|
20
|
+
|
|
21
|
+
## Workflow
|
|
22
|
+
|
|
23
|
+
### 1. Sync and check there's something to ship
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git fetch origin main staging
|
|
27
|
+
git log --oneline origin/main..origin/staging
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If the log is empty, tell the user `staging` has nothing ahead of `main` — there's no PR to open. Stop.
|
|
31
|
+
|
|
32
|
+
If `staging` is behind `origin/staging` locally, that's fine — the PR is opened against the remote refs, not your working copy. But mention it so the user knows.
|
|
33
|
+
|
|
34
|
+
### 2. Check the version bump (hard gate)
|
|
35
|
+
|
|
36
|
+
`version-check.yml` rejects any PR to `main` whose `package.json` version isn't strictly greater than main's, in plain `x.y.z` semver (no pre-release tags, no build metadata).
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git show origin/main:package.json | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version"
|
|
40
|
+
git show origin/staging:package.json | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Compare the two. If staging's version is **not** strictly greater than main's, stop and tell the user — they need to land a version bump on staging first (recent precedent: `chore: bump version to X.Y.Z` PRs into staging) before this release PR can pass CI. Don't open the PR; don't bump for them.
|
|
44
|
+
|
|
45
|
+
If the bump looks right, note which kind it is (major/minor/patch) — you'll tick the matching box in the body's Semantic Versioning section.
|
|
46
|
+
|
|
47
|
+
### 3. Gather the commits
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git log origin/main..origin/staging --pretty=format:'%H%x09%s%x09%b%x1e'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The `%x1e` (ASCII record separator) lets you split multi-line commit bodies cleanly. You want the subject AND the body — Linear references like `closes ENG-1901` usually live in the body, not the subject.
|
|
54
|
+
|
|
55
|
+
Skip merge commits whose subject starts with `Merge branch 'staging'` — they're noise from local merges, not a unit of work.
|
|
56
|
+
|
|
57
|
+
### 4. Resolve sub-PRs referenced in commit subjects
|
|
58
|
+
|
|
59
|
+
Squash-merged PRs leave a trail like `feat(accounts): expose tier on account responses (#561)`. The `(#NNNN)` is a real PR with its own body that often holds the Linear reference. Pull each one:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
gh api repos/connectedxm/admin-sdk/pulls/<NNNN> --jq '{title, body}'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Do this for every `(#NNNN)` you find in the commit subjects in step 3. These bodies feed into the Linear extraction below — and sometimes into the PR description summary, when the squashed commit message is terse.
|
|
66
|
+
|
|
67
|
+
### 5. Extract Linear ticket references
|
|
68
|
+
|
|
69
|
+
Linear refs in this repo look like `[A-Z]{2,5}-\d+` — the dominant prefix is `ENG`, with `CXM` and others appearing occasionally. They appear as:
|
|
70
|
+
|
|
71
|
+
- `closes ENG-1234`
|
|
72
|
+
- `ref CXM-1901`
|
|
73
|
+
- bare `[ENG-1913]` in PR titles, e.g. `[ENG-1913] ci: gate auto-approve on AUTO_APPROVE_USERS allowlist`
|
|
74
|
+
|
|
75
|
+
**Important: don't confuse `(#561)` (a GitHub PR number) with `ENG-1234` (a Linear ticket).** Only the latter goes in the Linear Issues section.
|
|
76
|
+
|
|
77
|
+
**`linear-check.yml` is a hard gate.** It runs on every PR to `main` and requires at least one literal `ENG-\d+` somewhere in the PR body. If you can only find `CXM-…` refs, the workflow will fail — flag this to the user before opening so they can decide whether to add an `ENG-…` ref or update the workflow.
|
|
78
|
+
|
|
79
|
+
Collect refs from:
|
|
80
|
+
|
|
81
|
+
- the body of every commit in step 3
|
|
82
|
+
- the title and body of every sub-PR resolved in step 4
|
|
83
|
+
|
|
84
|
+
**Only include refs you actually saw in those sources for this PR.** Don't carry refs over from prior staging→main PRs, recent conversation context, or memory of past releases — even if a ticket "feels relevant," it doesn't go in the list unless it's literally present in the commits/PRs you just gathered. Re-listing a ticket that was already shipped in a previous release is a real failure mode and confuses Linear's auto-close on merge.
|
|
85
|
+
|
|
86
|
+
Dedupe. Preserve the verb the user wrote when possible — if a sub-PR said `closes ENG-1901`, your output should say `closes ENG-1901`, not `ref`. Only fall back to `ref` if the original said `ref` or no verb at all.
|
|
87
|
+
|
|
88
|
+
### 6. Pick reviewers
|
|
89
|
+
|
|
90
|
+
There's no CODEOWNERS file in this repo. Build the candidate list at runtime:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
gh api repos/connectedxm/admin-sdk/collaborators \
|
|
94
|
+
--jq '[.[] | select(.permissions.push == true) | .login]'
|
|
95
|
+
gh api user --jq '.login' # you, the PR author — exclude from candidates
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Use the unfiltered `collaborators` endpoint — do **not** add `?affiliation=direct`, because that excludes org admins (e.g., `swiftoO` is an admin and won't appear with the direct filter). The unfiltered endpoint plus a `permissions.push == true` jq filter gives you the right candidate set.
|
|
99
|
+
|
|
100
|
+
Take everyone with push permission, drop the author. Default to requesting all of them. If the user said something specific about reviewers in their prompt ("just Tyler", "skip Spencer"), honor that and don't ask.
|
|
101
|
+
|
|
102
|
+
If only one candidate remains, just use them — don't bother the user about it.
|
|
103
|
+
|
|
104
|
+
### 7. Write the title
|
|
105
|
+
|
|
106
|
+
Look at what's actually in the diff before reaching for a template. The right title depends on the shape of the changes.
|
|
107
|
+
|
|
108
|
+
**One dominant change:** copy the conventional-commit subject from the main commit/PR. Example: `feat(accounts): expose tier and tags on account responses`.
|
|
109
|
+
|
|
110
|
+
**Several unrelated changes:** use a short generic title like `Staging Fixes` or a noun phrase that captures the theme. Look at recent staging→main PRs (`gh pr list --base main --head staging --state merged --limit 10`) to match the house style — they oscillate between conventional-commit and short noun phrases, both are fine.
|
|
111
|
+
|
|
112
|
+
**Version-bump-only releases.** When the only thing in the diff is `chore: bump version to X.Y.Z`, a short title like `Release X.Y.Z` is fine.
|
|
113
|
+
|
|
114
|
+
Keep it under ~70 characters.
|
|
115
|
+
|
|
116
|
+
### 8. Write the body
|
|
117
|
+
|
|
118
|
+
Use this exact template — it matches `.github/pull_request_template.md`:
|
|
119
|
+
|
|
120
|
+
```markdown
|
|
121
|
+
### Description
|
|
122
|
+
|
|
123
|
+
<2-4 sentence summary of what's shipping. Group by theme if there are several unrelated changes — bullet list is fine when that's clearer than prose. Lead with SDK-visible changes (new hooks, new fields on response/params types, new error codes, OpenAPI schema changes), since this PR triggers an npm publish of both `@connectedxm/admin` and the generated `@connectedxm/admin-sdk`.>
|
|
124
|
+
|
|
125
|
+
### Linear Issues
|
|
126
|
+
|
|
127
|
+
- closes ENG-1234
|
|
128
|
+
- ref CXM-1901
|
|
129
|
+
<one line per ticket; use the verb the source used. Must include at least one ENG-#### or linear-check.yml will fail.>
|
|
130
|
+
|
|
131
|
+
### Testing
|
|
132
|
+
|
|
133
|
+
<Generated testing plan — see "Writing the testing plan" below. Do NOT use the template's default `I have manually tested the changes / New integration tests have been added` checkboxes; replace them with concrete steps grounded in the actual diff.>
|
|
134
|
+
|
|
135
|
+
### Semantic Versioning
|
|
136
|
+
|
|
137
|
+
Indicate the appropriate version bump for this PR:
|
|
138
|
+
|
|
139
|
+
- [ ] Breaking changes - Major version bump
|
|
140
|
+
- [ ] New features / improvements - Minor version bump
|
|
141
|
+
- [ ] Bug fixes - Patch version bump
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Tick the Semantic Versioning box** that matches the actual version delta you noted in step 2 (`X.Y.0` → minor, `X.Y.Z` patch bump only → patch, major version bump → major). If multiple things shipped, tick the highest-impact box (one breaking change makes the whole release major).
|
|
145
|
+
|
|
146
|
+
**Writing the testing plan.** Replace the template's default Testing checkboxes with a per-PR plan derived from what actually changed. The reviewer should be able to read the plan and know exactly what to verify before merging triggers the npm publish. Aim for 3–7 concrete checkbox items.
|
|
147
|
+
|
|
148
|
+
Walk the file diff (`git diff --name-only origin/main..origin/staging`) and turn it into specific verification steps. This is a TypeScript SDK consumed by other apps (notably `admin-web`), so most verification happens via type-check + a smoke pass from a consumer. Note: this repo also generates a second SDK in `sdks/typescript/` from `openapi.json` via `npm run generate` / `npm run generate:sdks` — interface and param changes can affect what shows up there too.
|
|
149
|
+
|
|
150
|
+
- **Query files** (`src/queries/<resource>/use*.ts`) → "Confirm `useGet*` returns the new field/shape; spot-check the query key and that 401/403/404/460/461 routes through `onNotAuthorized` / `onNotFound` / `onModuleForbidden`."
|
|
151
|
+
- **Mutation files** (`src/mutations/<resource>/use*.ts`) → "Run the mutation from a consumer app and confirm the listed query keys invalidate / `SET_*_QUERY_DATA` updates as expected; on failure confirm `onMutationError` fires."
|
|
152
|
+
- **Interface changes** (`src/interfaces.ts`) → "Run `npm run lint` and `npx tsc` (configured `noEmit`); `npm pack` the SDK and install into `admin-web` — confirm the new fields surface in IDE completions and `tsc` is clean on the consumer side. Run `npm run generate` and spot-check `openapi.json` if the change should appear in the generated SDK."
|
|
153
|
+
- **Param/input shape changes** (`src/params.ts`) → "Confirm callers in `admin-web` still type-check against the new params; if a field became required, the consumer-side update needs to land before publish."
|
|
154
|
+
- **`ConnectedXMProvider` / context changes** (`src/ConnectedXMProvider.tsx`, `src/hooks/useConnectedXM.ts`) → "Mount a consumer with the updated provider and confirm `adminApiParams`, `organizationId`, `getToken`, `getExecuteAs`, and the `onNotAuthorized` / `onModuleForbidden` / `onNotFound` / `onMutationError` callbacks still wire through."
|
|
155
|
+
- **`AdminAPI` / axios changes** (`src/AdminAPI.ts`) → "Confirm `GetAdminAPI(params)` returns an axios instance with the expected `baseURL`, `organization`, `authorization`, `api-key`, and `executeAs` headers (when set)."
|
|
156
|
+
- **OpenAPI generator changes** (`scripts/generate.ts`, `scripts/generate-sdks.ts`) → "Run `npm run generate` and inspect `openapi.json` for the expected diff; if the SDK output is in scope, run `npm run generate:sdks` and confirm `sdks/typescript/` regenerates cleanly. Note: the `staging` → `main` PR triggers `generate-openapi.yml`, which re-runs the generator on the PR branch."
|
|
157
|
+
- **Index/barrel changes** (`src/index.ts`, per-folder `index.ts`) → "If files were added/moved, confirm `npm run exports` was run on staging (per `.cursor/rules/index-exports.mdc` — don't hand-edit). Then `npm run build` and inspect `dist/index.d.ts` to confirm the new symbols are exported."
|
|
158
|
+
- **CI workflow changes** (`.github/workflows/`) → "Confirm `tests.yml`, `linear-check.yml`, `version-check.yml`, `publish.yml`, `approve.yml`, or `generate-openapi.yml` pass on this branch."
|
|
159
|
+
- **Publish gate** → "After merge, confirm `publish.yml` runs cleanly on `main` and both `@connectedxm/admin@X.Y.Z` and the generated `@connectedxm/admin-sdk@X.Y.Z` are live on npm."
|
|
160
|
+
|
|
161
|
+
Group related items where it tightens the plan. If a single domain dominates the diff (e.g., the whole PR is account/auth polish), it's fine to have all 3–7 items in that domain. If you genuinely cannot derive a plan from the diff (rare — usually means something's wrong with how you read the commits), fall back to the original checkbox pair, but flag this to the user when presenting the draft.
|
|
162
|
+
|
|
163
|
+
Format as a markdown checklist:
|
|
164
|
+
|
|
165
|
+
```markdown
|
|
166
|
+
### Testing
|
|
167
|
+
|
|
168
|
+
- [ ] Run `npm run lint` and `npm run build` and confirm both pass cleanly.
|
|
169
|
+
- [ ] `npm pack` the SDK and install into an `admin-web` checkout — confirm `tsc` passes on the consumer with the new types.
|
|
170
|
+
- [ ] On a staging consumer, exercise the affected mutation/query and confirm the new fields land in the cache.
|
|
171
|
+
- [ ] Run `npm run generate` and confirm `openapi.json` reflects the interface/param diff (CI's `generate-openapi.yml` will also run this on the PR branch).
|
|
172
|
+
- [ ] After merge, confirm `publish.yml` succeeds and both `@connectedxm/admin@X.Y.Z` and `@connectedxm/admin-sdk@X.Y.Z` appear on npm.
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
If there are zero Linear references, omit the bullet list and write `- (none)` under the Linear Issues heading rather than deleting the section — keeps the template recognizable. (But: `linear-check.yml` requires at least one `ENG-####` in the body, so this case will fail CI. Flag it to the user before opening.)
|
|
176
|
+
|
|
177
|
+
### 9. Show the draft and wait
|
|
178
|
+
|
|
179
|
+
Print the title, body, reviewer list, AND the version delta from step 2 back to the user, clearly labelled. Ask whether to create the PR as drafted, modify, or cancel. Don't run `gh pr create` until they've said yes.
|
|
180
|
+
|
|
181
|
+
The reason for confirming: the title and body are interpretive — you're summarizing several commits' worth of work. The user knows things you don't (which change is the headline feature, which is incidental cleanup, whether the Semantic Versioning box is right, whether something is already covered by another ticket). Five seconds of their attention up front beats editing the PR after the fact — especially since merging this PR triggers an npm publish.
|
|
182
|
+
|
|
183
|
+
### 10. Create the PR
|
|
184
|
+
|
|
185
|
+
After confirmation, use a HEREDOC for the body so newlines and markdown survive:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
gh pr create \
|
|
189
|
+
--base main \
|
|
190
|
+
--head staging \
|
|
191
|
+
--title "<title>" \
|
|
192
|
+
--reviewer "<comma,separated,logins>" \
|
|
193
|
+
--body "$(cat <<'EOF'
|
|
194
|
+
<body here>
|
|
195
|
+
EOF
|
|
196
|
+
)"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Return the PR URL from the command output so the user can click straight to it.
|
|
200
|
+
|
|
201
|
+
## Things to watch out for
|
|
202
|
+
|
|
203
|
+
- **`main`, not `prod`.** This repo's production branch is `main` (the backend repo uses `prod`). Don't paste backend instincts here — `gh pr create --base prod` will fail.
|
|
204
|
+
- **Merging this PR publishes two packages to npm.** `publish.yml` runs on push to `main` and publishes both `@connectedxm/admin` (the source SDK) and `@connectedxm/admin-sdk` (the typescript-axios output regenerated from `openapi.json`). There is no manual gate. Take the testing plan seriously.
|
|
205
|
+
- **The version bump is a hard gate.** If staging's `package.json` version isn't strictly greater than main's in plain `x.y.z` semver, `version-check.yml` will fail. Don't open the PR until the bump is on staging.
|
|
206
|
+
- **`linear-check.yml` requires `ENG-####` in the body.** A `CXM-####`-only body will fail CI. If you only found `CXM` refs, surface that to the user before opening.
|
|
207
|
+
- **`generate-openapi.yml` runs on the staging→main PR.** It regenerates `openapi.json` from the source. If the PR branch's `openapi.json` is stale relative to the source, expect this workflow to push a regen commit or fail. Don't be surprised by the extra activity on the PR.
|
|
208
|
+
- **Don't paste `<!-- CURSOR_SUMMARY -->` blocks** from sub-PR bodies into the new PR body. Those are auto-generated by the Cursor Bugbot reviewer and re-including them looks weird.
|
|
209
|
+
- **Don't include the HTML comment placeholders** from the PR template (`<!-- A brief description -->`) in your output — replace them with real content.
|
|
210
|
+
- **Keep the Semantic Versioning section.** Don't drop it from the body — the template explicitly includes it. Tick the box that matches the actual version bump.
|
|
211
|
+
- **`closes` vs `ref`**: `closes` auto-closes the Linear ticket on merge to `main`. `ref` just links it. Preserve whichever the source commit/PR used. When in doubt, `ref` is the safer default — it doesn't change ticket state.
|
|
212
|
+
- **Bot reviews are not human reviews.** Don't infer reviewer preferences from `github-actions` or `cursor` reviews on past PRs — those are automated. Look at human reviewers (`authorAssociation: MEMBER`) only.
|
|
213
|
+
- **The author isn't a reviewer.** GitHub will reject `--reviewer <self>`. Always exclude `gh api user --jq '.login'` from the list.
|
|
214
|
+
|
|
215
|
+
## Why this exists
|
|
216
|
+
|
|
217
|
+
Opening these PRs by hand consistently misses Linear references buried in sub-PR bodies, drops the Semantic Versioning section, and either skips the version-bump check or has to back-patch it after CI fails. Because merging this PR auto-publishes two npm packages, the cost of a botched release is higher than for an app deploy — there's no easy "revert" once a version is live. Hitting the gh API once per sub-PR, comparing versions up front, and templating the body is mechanical work that's easy to get wrong when done manually but easy to get right in a script-shaped flow.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ship-to-staging
|
|
3
|
+
description: Open a feature PR from the current branch into `staging` in this repo. Handles the full flow — detects the current branch, gathers commits, extracts the Linear ticket reference from the branch name and commit messages, drafts a title and body that match the repo's PR template, picks reviewers, pushes the branch if needed, and (after the user confirms) creates the PR with `gh pr create`. Use this whenever the user says anything along the lines of "open a PR to staging", "ship this branch", "PR this feature", "submit this", "merge this into staging", "open a feature PR", "create the staging PR for this", or any variant of opening a PR from a feature branch into staging. Prefer this skill over running `gh pr create` directly — the manual flow misses Linear references and reviewer conventions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Feature → Staging PR
|
|
7
|
+
|
|
8
|
+
Use this when the user wants to open a PR from a feature branch into `staging` in the `connectedxm/admin-sdk` repo (npm package `@connectedxm/admin`). Typical case: one Linear ticket per branch, branch named like `ENG-1804` or `feat/eng-1234-cool-thing`.
|
|
9
|
+
|
|
10
|
+
For the staging→main release flow, use `ship-to-prod` instead. (Production is `main` in this repo, not `prod`.)
|
|
11
|
+
|
|
12
|
+
## What you're producing
|
|
13
|
+
|
|
14
|
+
A PR (after confirmation) with:
|
|
15
|
+
|
|
16
|
+
1. **Base/head**: base `staging`, head is the current feature branch — never `staging` or `main`
|
|
17
|
+
2. **Title** that reflects the work in the diff (usually the dominant commit's conventional-commit subject)
|
|
18
|
+
3. **Body** matching `.github/pull_request_template.md`, with the Linear ref harvested from the branch name and commit messages
|
|
19
|
+
4. **Reviewers** drawn from the repo's collaborators (excluding the PR author)
|
|
20
|
+
|
|
21
|
+
The user has asked to **always confirm before creating** the PR. Show the draft, wait for the go-ahead, then push (if needed) and call `gh pr create`.
|
|
22
|
+
|
|
23
|
+
## Workflow
|
|
24
|
+
|
|
25
|
+
### 1. Identify the branch and check there's something to ship
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git rev-parse --abbrev-ref HEAD
|
|
29
|
+
git fetch origin staging
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If the current branch is `staging` or `main`, stop and tell the user — they probably wanted `ship-to-prod`, or they need to check out the feature branch first.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git log --oneline origin/staging..HEAD
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If the log is empty, tell the user the current branch has nothing ahead of `staging` — there's no PR to open. Stop.
|
|
39
|
+
|
|
40
|
+
If the branch already has an open PR against staging, mention it (`gh pr list --head <branch> --base staging --state open`) and ask whether to add commits to that one or close it first. Don't open a duplicate.
|
|
41
|
+
|
|
42
|
+
### 2. Check push state
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git rev-parse --verify --quiet "origin/<branch>" && \
|
|
46
|
+
git log --oneline "origin/<branch>..HEAD" || echo "branch not on origin"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Three possible states — note which applies:
|
|
50
|
+
|
|
51
|
+
- **Branch not on origin** — needs `git push -u origin <branch>` before `gh pr create` will work.
|
|
52
|
+
- **Local commits ahead of `origin/<branch>`** — needs `git push` so the PR includes them.
|
|
53
|
+
- **Up to date** — nothing to push.
|
|
54
|
+
|
|
55
|
+
Don't push yet. Bundle the push with the PR-create step after the user confirms the draft (step 8).
|
|
56
|
+
|
|
57
|
+
### 3. Gather the commits
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git log origin/staging..HEAD --pretty=format:'%H%x09%s%x09%b%x1e'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The `%x1e` (ASCII record separator) lets you split multi-line commit bodies cleanly. You want the subject AND the body — Linear refs like `closes ENG-1901` typically live in the body.
|
|
64
|
+
|
|
65
|
+
Skip merge commits (`Merge branch 'staging'`, `Merge pull request #...`) — they're noise on a feature branch.
|
|
66
|
+
|
|
67
|
+
Feature branches generally don't have squashed sub-PRs (they ARE the sub-PR), so you can skip the sub-PR resolution step that `ship-to-prod` uses.
|
|
68
|
+
|
|
69
|
+
### 4. Extract the Linear ticket reference
|
|
70
|
+
|
|
71
|
+
Linear refs in this repo look like `[A-Z]{2,5}-\d+` — the dominant prefix is `ENG`. The PR template still shows `closes CXM-…` as a placeholder, but recent merged PRs and the `linear-check.yml` workflow both expect `ENG-\d+`. `CXM` and other prefixes appear occasionally; preserve whatever the source actually used. Note that branches in this repo are often named just by ticket ID (e.g. `ENG-1804`). Look in this order:
|
|
72
|
+
|
|
73
|
+
1. **Branch name** — `ENG-1804`, `fix/eng-1886-deletion`, or `feat/cxm-1234-cool-thing` → extract `ENG-1804` / `ENG-1886` / `CXM-1234`. Pattern: `([a-zA-Z]{2,5}-\d+)` (case-insensitive — uppercase it).
|
|
74
|
+
2. **Commit subjects and bodies** — `closes ENG-1886`, `ref CXM-1234`, or bare ticket IDs.
|
|
75
|
+
|
|
76
|
+
For the verb:
|
|
77
|
+
|
|
78
|
+
- If a commit explicitly used `closes ENG-XXXX` or `ref ENG-XXXX`, preserve that verb.
|
|
79
|
+
- If the ref came only from the branch name, default to `closes` — a feature PR is the canonical place to close its ticket.
|
|
80
|
+
- When in real doubt, `ref` is safer (doesn't change ticket state on merge).
|
|
81
|
+
|
|
82
|
+
Dedupe. The common case is a single ticket; if you find more than one, list each on its own line.
|
|
83
|
+
|
|
84
|
+
If you find zero refs, ask the user before proceeding — feature branches almost always have a ticket, and a missing ref usually means the branch was named differently than usual or the ticket was forgotten. Don't fabricate one.
|
|
85
|
+
|
|
86
|
+
Note: `linear-check.yml` only runs on PRs targeting `main`, so a missing Linear ref won't block a staging PR from opening. Still ask — the ref needs to land somewhere on the branch before the staging→main release PR is opened.
|
|
87
|
+
|
|
88
|
+
### 5. Pick reviewers
|
|
89
|
+
|
|
90
|
+
Same logic as `ship-to-prod` — there's no CODEOWNERS file:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
gh api repos/connectedxm/admin-sdk/collaborators \
|
|
94
|
+
--jq '[.[] | select(.permissions.push == true) | .login]'
|
|
95
|
+
gh api user --jq '.login' # exclude self
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Use the unfiltered `collaborators` endpoint (NOT `?affiliation=direct`, which excludes org admins like `swiftoO`). Filter by `permissions.push == true`, drop the author.
|
|
99
|
+
|
|
100
|
+
Default to requesting all of them. If the user said something specific in their prompt ("just Tyler", "skip Spencer"), honor that. If only one candidate remains, just use them.
|
|
101
|
+
|
|
102
|
+
### 6. Write the title
|
|
103
|
+
|
|
104
|
+
For a feature branch, the title is usually obvious — copy the dominant commit's conventional-commit subject. Examples that work well:
|
|
105
|
+
|
|
106
|
+
- `feat(accounts): expose tier and tags on account responses`
|
|
107
|
+
- `fix(events/passes): preserve query key when search arg changes`
|
|
108
|
+
- `chore(ci): gate auto-approve on AUTO_APPROVE_USERS allowlist`
|
|
109
|
+
|
|
110
|
+
If the branch has many small commits with no clear headline, fall back to a noun phrase that captures the change (`Image upload on activation`, `Activity hook cleanup`). Keep it under ~70 characters.
|
|
111
|
+
|
|
112
|
+
Don't include the Linear ticket in the title — it goes in the body.
|
|
113
|
+
|
|
114
|
+
### 7. Write the body
|
|
115
|
+
|
|
116
|
+
Use this exact template — it matches `.github/pull_request_template.md`:
|
|
117
|
+
|
|
118
|
+
```markdown
|
|
119
|
+
### Description
|
|
120
|
+
|
|
121
|
+
<2-4 sentence summary of what's shipping and why. Pull from the dominant commit body where it's well-written; otherwise synthesize from the diff. Lead with the SDK-visible behavior change (new hook, new field on a response/params type, new query key, etc.), then root cause / approach if relevant.>
|
|
122
|
+
|
|
123
|
+
### Linear Issues
|
|
124
|
+
|
|
125
|
+
- closes ENG-1886
|
|
126
|
+
<one line per ticket; preserve the verb from the source (or default to `closes` for branch-only refs)>
|
|
127
|
+
|
|
128
|
+
### Testing
|
|
129
|
+
|
|
130
|
+
<Generated testing plan — see "Writing the testing plan" below. Do NOT use the template's default `I have manually tested the changes / New integration tests have been added` checkboxes; replace them with concrete steps grounded in the actual diff.>
|
|
131
|
+
|
|
132
|
+
### Semantic Versioning
|
|
133
|
+
|
|
134
|
+
Indicate the appropriate version bump for this PR:
|
|
135
|
+
|
|
136
|
+
- [ ] Breaking changes - Major version bump
|
|
137
|
+
- [ ] New features / improvements - Minor version bump
|
|
138
|
+
- [ ] Bug fixes - Patch version bump
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**The Semantic Versioning section.** Keep this section in the body — the staging→main release PR re-uses the bumped version. Tick the box that matches the diff:
|
|
142
|
+
|
|
143
|
+
- New required fields, removed/renamed exports, changed function signatures → **major**.
|
|
144
|
+
- New optional fields on response/params interfaces, new hooks/queries/mutations, new WS event handlers → **minor**.
|
|
145
|
+
- Bug fixes, internal refactors, validation tightening that doesn't break callers → **patch**.
|
|
146
|
+
|
|
147
|
+
If the user has already bumped `package.json` on this branch, that decides it — match the box to the bump that's actually in the diff. The version itself is enforced on the staging→main PR by `version-check.yml`, not here.
|
|
148
|
+
|
|
149
|
+
**Writing the testing plan.** A feature PR's testing plan is tighter than a release's — usually 2–5 specific items. Walk the file diff (`git diff --name-only origin/staging..HEAD`) and turn it into concrete verification steps. This is a TypeScript SDK consumed by other apps (notably `admin-web`), so most verification happens via type-check + a smoke pass from a consumer. Note: this repo also generates a second SDK in `sdks/typescript/` from `openapi.json` via `npm run generate` / `npm run generate:sdks` — interface and param changes can affect what shows up there too.
|
|
150
|
+
|
|
151
|
+
- **Query files** (`src/queries/<resource>/use*.ts`) → "Confirm `useGet*` returns the new field/shape; spot-check the query key and that 401/403/404/460/461 routes through the right `onNotAuthorized` / `onNotFound` / `onModuleForbidden` handler."
|
|
152
|
+
- **Mutation files** (`src/mutations/<resource>/use*.ts`) → "Run the mutation from a consumer app and confirm the listed query keys invalidate / `SET_*_QUERY_DATA` updates as expected; on failure confirm `onMutationError` fires."
|
|
153
|
+
- **Interface changes** (`src/interfaces.ts`) → "Run `npm run lint` and `npx tsc` (configured `noEmit`) on the SDK; then `npm pack` and install the tarball into a consumer (`admin-web`) — confirm the new fields surface in IDE completions and `tsc` is clean. If the change should appear in the generated SDK too, run `npm run generate` and spot-check `openapi.json`."
|
|
154
|
+
- **Param/input shape changes** (`src/params.ts`) → "Confirm callers in `admin-web` still type-check against the new params; if a field became required, flag the consumer-side update needed."
|
|
155
|
+
- **`ConnectedXMProvider` / context changes** (`src/ConnectedXMProvider.tsx`, `src/hooks/useConnectedXM.ts`) → "Mount a consumer with the updated provider and confirm `adminApiParams`, `organizationId`, `getToken`, `getExecuteAs`, and the `onNotAuthorized` / `onModuleForbidden` / `onNotFound` / `onMutationError` callbacks still wire through."
|
|
156
|
+
- **`AdminAPI` / axios changes** (`src/AdminAPI.ts`) → "Confirm `GetAdminAPI(params)` returns an axios instance with the expected `baseURL`, `organization`, `authorization`, `api-key`, and `executeAs` headers (when set)."
|
|
157
|
+
- **OpenAPI generator changes** (`scripts/generate.ts`, `scripts/generate-sdks.ts`) → "Run `npm run generate` and inspect `openapi.json` for the expected diff; if the SDK output is in scope, run `npm run generate:sdks` and check `sdks/typescript/`."
|
|
158
|
+
- **Index/barrel changes** (`src/index.ts`, per-folder `index.ts`) → "If you added or moved files, run `npm run exports` to regenerate barrels (per `.cursor/rules/index-exports.mdc` — don't hand-edit). Then `npm run build` and inspect `dist/index.d.ts` to confirm the new symbols are exported."
|
|
159
|
+
- **CI workflow changes** (`.github/workflows/`) → "Confirm `tests.yml` / `linear-check.yml` / `version-check.yml` / `publish.yml` / `approve.yml` / `generate-openapi.yml` pass on this branch."
|
|
160
|
+
- **Local smoke** (when changes affect runtime, not just types) → "Run `npm run local` to produce a `.tgz`, install into `admin-web` (or another consumer), and walk the affected screen."
|
|
161
|
+
|
|
162
|
+
Format as a markdown checklist:
|
|
163
|
+
|
|
164
|
+
```markdown
|
|
165
|
+
### Testing
|
|
166
|
+
|
|
167
|
+
- [ ] Run `npm run lint` and `npx tsc` and confirm both pass cleanly.
|
|
168
|
+
- [ ] `npm pack` the SDK, install the tarball into `admin-web`, and confirm the new field surfaces on `useGet<Resource>` / `useUpdate<Resource>` types in IDE completions.
|
|
169
|
+
- [ ] On a staging consumer, exercise the affected mutation — confirm it succeeds and the cache update / `SET_*_QUERY_DATA` reflects the new fields.
|
|
170
|
+
- [ ] Confirm `tests.yml` passes on the next push.
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
If there are zero Linear references (you flagged this in step 4 and the user proceeded anyway), write `- (none)` under the Linear Issues heading rather than deleting the section.
|
|
174
|
+
|
|
175
|
+
### 8. Show the draft and wait
|
|
176
|
+
|
|
177
|
+
Print the title, body, reviewer list, AND the push state from step 2 back to the user, clearly labelled. If a push is needed, state explicitly that creating the PR will push first. Ask whether to proceed, modify, or cancel. Don't run `git push` or `gh pr create` until they've said yes.
|
|
178
|
+
|
|
179
|
+
The reason for confirming: the title and body are interpretive — you're summarizing the work. The user knows things you don't (whether the description undersells the change, whether the testing plan is missing a non-obvious manual step, which Semantic Versioning box is right). Five seconds of their attention up front beats editing the PR after the fact.
|
|
180
|
+
|
|
181
|
+
### 9. Push the branch (if needed) and create the PR
|
|
182
|
+
|
|
183
|
+
After confirmation:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Only if step 2 said the branch needed pushing:
|
|
187
|
+
git push -u origin <branch> # first push
|
|
188
|
+
# OR
|
|
189
|
+
git push # subsequent push
|
|
190
|
+
|
|
191
|
+
# Then:
|
|
192
|
+
gh pr create \
|
|
193
|
+
--base staging \
|
|
194
|
+
--head <branch> \
|
|
195
|
+
--title "<title>" \
|
|
196
|
+
--reviewer "<comma,separated,logins>" \
|
|
197
|
+
--body "$(cat <<'EOF'
|
|
198
|
+
<body here>
|
|
199
|
+
EOF
|
|
200
|
+
)"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`gh pr create` picks up the current branch as `--head` automatically when you're on it, but pass `--head` explicitly to be safe (avoids surprises if HEAD has moved).
|
|
204
|
+
|
|
205
|
+
Return the PR URL from the command output so the user can click straight to it.
|
|
206
|
+
|
|
207
|
+
## Things to watch out for
|
|
208
|
+
|
|
209
|
+
- **Don't paste `<!-- CURSOR_SUMMARY -->` blocks** from anywhere into the new PR body. Auto-generated by the Cursor Bugbot reviewer — re-including them looks weird.
|
|
210
|
+
- **Don't include the HTML comment placeholders** from the PR template (`<!-- A brief description -->`) in your output — replace them with real content.
|
|
211
|
+
- **Keep the Semantic Versioning section.** This repo's PR template has it for a reason — the staging→main release PR re-uses the bump that landed on staging. Don't drop it from the body.
|
|
212
|
+
- **Don't bump `package.json` automatically.** Version bumps are landed deliberately (sometimes in their own PR, sometimes alongside a feature). If the user hasn't bumped, don't do it for them — flag it in the draft and let them decide. `version-check.yml` enforces the bump on the staging→main PR, not here.
|
|
213
|
+
- **`closes` vs `ref`**: `closes` auto-closes the Linear ticket on merge. For a feature → staging PR, `closes` is usually correct since this branch IS the work that closes the ticket. Preserve whichever the source commit used; default to `closes` only when the ref came from the branch name alone.
|
|
214
|
+
- **Don't fabricate Linear refs.** If the branch name and commits genuinely have no ticket, ask the user — don't guess from conversation context or memory of past tickets.
|
|
215
|
+
- **The author isn't a reviewer.** GitHub will reject `--reviewer <self>`. Always exclude `gh api user --jq '.login'` from the list.
|
|
216
|
+
- **Don't push without confirmation.** Pushing exposes work to the team and triggers CI (`tests.yml`). Bundle it with PR creation behind the user's go-ahead.
|
|
217
|
+
|
|
218
|
+
## Why this exists
|
|
219
|
+
|
|
220
|
+
Feature → staging PRs follow the same template as release PRs but with a much simpler input — a single branch, usually a single ticket. Doing it by hand consistently misses the Linear ref (especially when it lives in the branch name only), the testing plan defaults to the empty template checkboxes, the Semantic Versioning section gets dropped, and reviewers get inconsistent. Templating the body and harvesting refs from the branch name is mechanical work that's easy to get right in a script-shaped flow.
|
package/dist/index.d.cts
CHANGED
|
@@ -480,6 +480,7 @@ interface BaseActivity {
|
|
|
480
480
|
status: keyof typeof ActivityStatus;
|
|
481
481
|
featured: boolean;
|
|
482
482
|
pinned: boolean;
|
|
483
|
+
pinnedExplore: boolean;
|
|
483
484
|
giphyId: string | null;
|
|
484
485
|
imageId: string | null;
|
|
485
486
|
videoId: string | null;
|
|
@@ -4823,6 +4824,8 @@ interface ActivityCreateInputs {
|
|
|
4823
4824
|
message: string;
|
|
4824
4825
|
entities?: ActivityEntityInputs[] | null;
|
|
4825
4826
|
featured?: boolean;
|
|
4827
|
+
pinned?: boolean;
|
|
4828
|
+
pinnedExplore?: boolean;
|
|
4826
4829
|
imageId?: string | null;
|
|
4827
4830
|
videoId?: string | null;
|
|
4828
4831
|
eventId?: string | null;
|
|
@@ -4839,6 +4842,7 @@ interface ActivityUpdateInputs {
|
|
|
4839
4842
|
moderation?: keyof typeof ModerationStatus | null;
|
|
4840
4843
|
featured?: boolean;
|
|
4841
4844
|
pinned?: boolean;
|
|
4845
|
+
pinnedExplore?: boolean;
|
|
4842
4846
|
imageId?: string | null;
|
|
4843
4847
|
videoId?: string | null;
|
|
4844
4848
|
meetingId?: string | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -480,6 +480,7 @@ interface BaseActivity {
|
|
|
480
480
|
status: keyof typeof ActivityStatus;
|
|
481
481
|
featured: boolean;
|
|
482
482
|
pinned: boolean;
|
|
483
|
+
pinnedExplore: boolean;
|
|
483
484
|
giphyId: string | null;
|
|
484
485
|
imageId: string | null;
|
|
485
486
|
videoId: string | null;
|
|
@@ -4823,6 +4824,8 @@ interface ActivityCreateInputs {
|
|
|
4823
4824
|
message: string;
|
|
4824
4825
|
entities?: ActivityEntityInputs[] | null;
|
|
4825
4826
|
featured?: boolean;
|
|
4827
|
+
pinned?: boolean;
|
|
4828
|
+
pinnedExplore?: boolean;
|
|
4826
4829
|
imageId?: string | null;
|
|
4827
4830
|
videoId?: string | null;
|
|
4828
4831
|
eventId?: string | null;
|
|
@@ -4839,6 +4842,7 @@ interface ActivityUpdateInputs {
|
|
|
4839
4842
|
moderation?: keyof typeof ModerationStatus | null;
|
|
4840
4843
|
featured?: boolean;
|
|
4841
4844
|
pinned?: boolean;
|
|
4845
|
+
pinnedExplore?: boolean;
|
|
4842
4846
|
imageId?: string | null;
|
|
4843
4847
|
videoId?: string | null;
|
|
4844
4848
|
meetingId?: string | null;
|
package/openapi.json
CHANGED
|
@@ -93033,11 +93033,12 @@
|
|
|
93033
93033
|
"type": "string",
|
|
93034
93034
|
"enum": [
|
|
93035
93035
|
"admin",
|
|
93036
|
-
"
|
|
93036
|
+
"account",
|
|
93037
|
+
"thread",
|
|
93038
|
+
"content",
|
|
93037
93039
|
"activity",
|
|
93038
|
-
"
|
|
93039
|
-
"
|
|
93040
|
-
"content"
|
|
93040
|
+
"event",
|
|
93041
|
+
"activation"
|
|
93041
93042
|
]
|
|
93042
93043
|
},
|
|
93043
93044
|
"SupportTicketType": {
|
|
@@ -93897,6 +93898,18 @@
|
|
|
93897
93898
|
},
|
|
93898
93899
|
"passId": {
|
|
93899
93900
|
"type": "string"
|
|
93901
|
+
},
|
|
93902
|
+
"imageId": {
|
|
93903
|
+
"type": "string",
|
|
93904
|
+
"nullable": true
|
|
93905
|
+
},
|
|
93906
|
+
"image": {
|
|
93907
|
+
"allOf": [
|
|
93908
|
+
{
|
|
93909
|
+
"$ref": "#/components/schemas/BaseImage"
|
|
93910
|
+
}
|
|
93911
|
+
],
|
|
93912
|
+
"nullable": true
|
|
93900
93913
|
}
|
|
93901
93914
|
},
|
|
93902
93915
|
"required": [
|
|
@@ -93905,7 +93918,9 @@
|
|
|
93905
93918
|
"eventActivationId",
|
|
93906
93919
|
"eventActivation",
|
|
93907
93920
|
"earnedPoints",
|
|
93908
|
-
"passId"
|
|
93921
|
+
"passId",
|
|
93922
|
+
"imageId",
|
|
93923
|
+
"image"
|
|
93909
93924
|
]
|
|
93910
93925
|
},
|
|
93911
93926
|
"ActivationCompletion": {
|
|
@@ -93984,6 +93999,9 @@
|
|
|
93984
93999
|
],
|
|
93985
94000
|
"nullable": true
|
|
93986
94001
|
},
|
|
94002
|
+
"imageUpload": {
|
|
94003
|
+
"type": "boolean"
|
|
94004
|
+
},
|
|
93987
94005
|
"_count": {
|
|
93988
94006
|
"type": "object",
|
|
93989
94007
|
"properties": {
|
|
@@ -94008,6 +94026,7 @@
|
|
|
94008
94026
|
"accessLevel",
|
|
94009
94027
|
"sortOrder",
|
|
94010
94028
|
"survey",
|
|
94029
|
+
"imageUpload",
|
|
94011
94030
|
"_count"
|
|
94012
94031
|
]
|
|
94013
94032
|
},
|
|
@@ -94144,6 +94163,9 @@
|
|
|
94144
94163
|
"pinned": {
|
|
94145
94164
|
"type": "boolean"
|
|
94146
94165
|
},
|
|
94166
|
+
"pinnedExplore": {
|
|
94167
|
+
"type": "boolean"
|
|
94168
|
+
},
|
|
94147
94169
|
"giphyId": {
|
|
94148
94170
|
"type": "string",
|
|
94149
94171
|
"nullable": true
|
|
@@ -94217,6 +94239,7 @@
|
|
|
94217
94239
|
"status",
|
|
94218
94240
|
"featured",
|
|
94219
94241
|
"pinned",
|
|
94242
|
+
"pinnedExplore",
|
|
94220
94243
|
"giphyId",
|
|
94221
94244
|
"imageId",
|
|
94222
94245
|
"videoId",
|
|
@@ -112733,6 +112756,12 @@
|
|
|
112733
112756
|
"featured": {
|
|
112734
112757
|
"type": "boolean"
|
|
112735
112758
|
},
|
|
112759
|
+
"pinned": {
|
|
112760
|
+
"type": "boolean"
|
|
112761
|
+
},
|
|
112762
|
+
"pinnedExplore": {
|
|
112763
|
+
"type": "boolean"
|
|
112764
|
+
},
|
|
112736
112765
|
"imageId": {
|
|
112737
112766
|
"type": "string",
|
|
112738
112767
|
"nullable": true
|
|
@@ -112802,6 +112831,9 @@
|
|
|
112802
112831
|
"pinned": {
|
|
112803
112832
|
"type": "boolean"
|
|
112804
112833
|
},
|
|
112834
|
+
"pinnedExplore": {
|
|
112835
|
+
"type": "boolean"
|
|
112836
|
+
},
|
|
112805
112837
|
"imageId": {
|
|
112806
112838
|
"type": "string",
|
|
112807
112839
|
"nullable": true
|
|
@@ -113846,6 +113878,9 @@
|
|
|
113846
113878
|
}
|
|
113847
113879
|
],
|
|
113848
113880
|
"nullable": true
|
|
113881
|
+
},
|
|
113882
|
+
"imageUpload": {
|
|
113883
|
+
"type": "boolean"
|
|
113849
113884
|
}
|
|
113850
113885
|
},
|
|
113851
113886
|
"required": [
|
|
@@ -113949,6 +113984,9 @@
|
|
|
113949
113984
|
}
|
|
113950
113985
|
],
|
|
113951
113986
|
"nullable": true
|
|
113987
|
+
},
|
|
113988
|
+
"imageUpload": {
|
|
113989
|
+
"type": "boolean"
|
|
113952
113990
|
}
|
|
113953
113991
|
}
|
|
113954
113992
|
},
|