Skip to content

Commit 2299639

Browse files
committed
Merge remote-tracking branch 'upstream/master' into fixpysend
# Conflicts: # packages/components/src/components/SendOperations.js # packages/components/test/components/__snapshots__/SendOperations.test.js.snap # packages/templates/clients/websocket/test/integration-test/__snapshots__/integration.test.js.javascript.snap
2 parents 237585f + d87ed49 commit 2299639

29 files changed

Lines changed: 635 additions & 93 deletions

File tree

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
---
2+
name: migrate-component
3+
description: Promote a duplicated React/JSX template-local component into the shared @asyncapi/generator-components package. Use this skill whenever the user asks to "migrate", "move", "promote", "extract", "share", or "consolidate" a component into generator-components (or shared components / the components package). Also trigger for phrases like "it's used in multiple templates now, let's share it" or "avoid duplication across clients". Do not fire for unrelated React refactors or for moving code between apps/.
4+
---
5+
6+
# Migrate Component to @asyncapi/generator-components
7+
8+
You are migrating a duplicated React/JSX template-local component out of `packages/templates/clients/<protocol>/<lang>[/<framework>]/components/` (the `<framework>` segment is present for stack-specific templates like `java/quarkus`, omitted for single-stack languages like `python`) into the shared `packages/components/src/components/` package.
9+
10+
> **Important:** Always edit `src/` files. The `lib/` directory is generated at publish time by Babel — never edit files there.
11+
12+
## Invocation
13+
14+
The user has named the component to migrate, e.g. "migrate HandleError". If they did NOT name one, ask which component.
15+
16+
## Preconditions (fast gate)
17+
18+
### 1. Two-template threshold check
19+
20+
Use the **Glob tool** with pattern `packages/templates/clients/**/components/<Component>.js` to find all template-local copies of this component.
21+
22+
Decide based on the result count:
23+
24+
| Result count | Action |
25+
|---|---|
26+
| **0** | Stop and report "no template files found" — nothing to promote. |
27+
| **1** | Report "only one template uses this component; CLAUDE.md section 4.5 recommends 2+ before promotion" and **prompt the user via `AskUserQuestion`**: continue anyway? |
28+
| **2+** | Continue automatically — threshold satisfied. |
29+
30+
The 1-template case is the only judgment call — do not stop unilaterally; let the user make the call.
31+
32+
### 2. No naming collision
33+
34+
Use the **Glob tool** with pattern `packages/components/src/components/<Component>.js` — if it returns a result, stop and report that the component already exists in the shared package.
35+
36+
## Research & design (produce the migration plan)
37+
38+
Before touching any files, work through these three steps in order. Each produces an artifact the execution steps consume directly:
39+
40+
1. **The template files** — the canonical list of files to migrate from templates into the shared package.
41+
2. **The props table** — file × props × render-shape × indent/newLines.
42+
3. **The union signature** — the shared component's prop list, with required/optional/defaults.
43+
44+
### 1. Catalog the template files
45+
46+
The Glob results from precondition 1 are the canonical list — call it **"the template files"** in subsequent steps. Every later step (read sources, delete files, update imports) refers back to these exact paths. Echo the list back to the user as a fenced block so it stays visible; do not re-run Glob.
47+
48+
### 2. Read every copy and tabulate
49+
50+
`Read` every path in **the template files** — one read per file, no skipping. For each, record one row in a markdown table:
51+
52+
| File | Props (with defaults) | Render shape | Indent | newLines |
53+
|---|---|---|---|---|
54+
| `…/javascript/.../<Component>.js` | `{ methodName, methodParams = ['msg'] }` | `<Text>…</Text>` | 2 | 1 |
55+
| `…/python/.../<Component>.js` | `{ methodName, methodParams = ['self','msg'], preExecutionCode }` | `<Text>…</Text>` | 4 | 2 |
56+
| `…/dart/.../<Component>.js` | `{ methodName }` | `<MethodGenerator … />` | 2 | 1 |
57+
58+
This table is the source of truth for the next two research steps. Print it back to the user.
59+
60+
### 3. Derive the prop signature (union rule)
61+
62+
The shared component's props are **the union of props across the template files**:
63+
64+
- Prop in **every** template file → required (no default).
65+
- Prop in **some** template files → optional, with a default matching the omitting file's current behavior.
66+
- Prop with different defaults per language → keep required; push the per-language value into the consuming template's JSX usage.
67+
68+
**Worked example** (using the table above):
69+
70+
```js
71+
// Per-copy props:
72+
// javascript → { methodName, methodParams = ['msg'] }
73+
// python → { methodName, methodParams = ['self','msg'], preExecutionCode }
74+
// dart → { methodName }
75+
//
76+
// Union → { methodName, methodParams, preExecutionCode }
77+
// methodName → required (in every copy)
78+
// methodParams → optional, ['msg'] (JS/Dart omit; Python overrides at the call site)
79+
// preExecutionCode → optional, '' (only Python uses it; default is a no-op for JS/Dart)
80+
81+
export function <Component>({
82+
methodName,
83+
methodParams = ['msg'],
84+
preExecutionCode = '',
85+
}) { /**/ }
86+
```
87+
88+
Python's consuming template then renders `<<Component> methodName='onMessage' methodParams={['self','msg']} preExecutionCode='…' />`; JS/Dart pass only what they need.
89+
90+
**Choose the abstraction shape** based on the table and signature:
91+
92+
- **Method-shaped** — delegates to `MethodGenerator` with a `websocket<X>Config` map. Use when per-language differences are mostly method-body strings (the config object holds the language-specific logic; the component itself is thin). See `packages/components/src/components/RegisterErrorHandler.js`.
93+
- **Structural** — config object keyed by language returning `Text` code blocks. Use when the rendering structure itself varies across languages (different indentation, different block shapes, framework sub-keys). See `packages/components/src/components/QueryParamsVariables.js`.
94+
95+
## Execution steps
96+
97+
Execute strictly in this order; each step must succeed before the next.
98+
99+
### 1. Author the shared component
100+
101+
Create `packages/components/src/components/<Component>.js` using **the signature and abstraction shape from research step 3**.
102+
103+
Two things to enforce (everything else — named export, body code, imports, EOF newline — copy from the canonical example for your chosen shape):
104+
105+
- **JSDoc**`@typedef` for the `Language` union, `@param` for **every prop in the research step 3 union** (matching required/optional/defaults exactly), `@returns {JSX.Element}`, and a `@example` block. This is what `jsdoc2md` publishes to `apps/generator/docs/api_components.md` in step 9; missing or malformed tags produce an empty diff there.
106+
- **Validate constrained props** — for `language`/`framework` (or any prop with a supported set), throw using helpers from `packages/components/src/utils/ErrorHandling.js`. Use `unsupportedLanguage(language, supportedList)` for the `language` prop; use `unsupportedFramework(language, framework, supportedList)` when the component also accepts a `framework` prop (e.g. `java/quarkus`). See `QueryParamsVariables.js` for the full pattern.
107+
108+
### 2. Export it
109+
110+
Edit `packages/components/src/index.js`: append one line `export { <Component> } from './components/<Component>';`.
111+
112+
### 3. Write the test
113+
114+
Create `packages/components/test/components/<Component>.test.js`. Test cases come **directly** from the artifacts of research steps 2 and 3:
115+
116+
- **One snapshot test per row in research step 2's table** (i.e. per `(language, framework?)` pair). Pass the per-language props from that row.
117+
- **One variant test per optional prop in research step 3's union** — proves the prop is wired through, not hardcoded.
118+
- **One negative test**: omit all optional props and confirm no `undefined` leaks in the snapshot.
119+
120+
### 4. Generate the snapshot
121+
122+
```bash
123+
npm run components:test -- -u
124+
```
125+
126+
Output: `packages/components/test/components/__snapshots__/<Component>.test.js.snap`. Open it and sanity-check that the rendered output looks like what each language template emits today. The real correctness check happens in **step 8**`git diff` on the regenerated per-client integration snapshots.
127+
128+
### 5. Delete the template files
129+
130+
For each path in **"the template files"** (research step 1):
131+
132+
- `git rm <path>`
133+
- If a sibling test exists at `packages/templates/clients/<protocol>/<lang>[/<framework>]/test/components/<Component>.test.js` (same `<lang>[/<framework>]` segments as the source file — e.g. `java/quarkus/test/components/…`): `git rm` it and its `.snap`.
134+
135+
Do **not** re-run Glob — the list from research step 1 is canonical.
136+
137+
### 6. Update each consuming template
138+
139+
For each path in **"the template files"** (research step 1), find the file that imported it. Use the **Grep tool** with pattern `from './<Component>'` scoped to that file's template root — the directory immediately above `components/`, which is `<lang>/` for single-stack languages or `<lang>/<framework>/` for stack-specific ones (e.g. `packages/templates/clients/websocket/python/` or `packages/templates/clients/websocket/java/quarkus/`).
140+
141+
In each match:
142+
143+
- Remove the local import line.
144+
- Add `<Component>` to the existing `@asyncapi/generator-components` import (match the file's existing order convention; alphabetical not required).
145+
- At each `<<Component> />` JSX usage, pass **the props from that language's row in research step 2's table** plus `language='<lang>'`. The rendered output must be identical to what the deleted local file emitted — that's what keeps the integration snapshot churn minimal in step 7.
146+
147+
**Example** (using the worked example from research step 3):
148+
149+
```jsx
150+
// JS template — methodParams default matches the table, so only pass required props.
151+
<<Component> language='javascript' methodName='onMessage' />
152+
153+
// Python template — override per-language values from the table.
154+
<<Component>
155+
language='python'
156+
methodName='on_message'
157+
methodParams={['self', 'msg']}
158+
preExecutionCode={'...'}
159+
/>
160+
161+
// Dart template — only methodName per the table.
162+
<<Component> language='dart' methodName='onMessage' />
163+
```
164+
165+
### 7. Regenerate integration snapshots
166+
167+
Run from the integration-test package — much faster than repo root since it skips the rest of the pipeline:
168+
169+
```bash
170+
cd packages/templates/clients/<protocol>/test/integration-test && npm run test:update
171+
```
172+
173+
Or to update a single client: `cd packages/templates/clients/<protocol>/test/integration-test && npm run test:<lang>:update`
174+
175+
Rebuilds `__snapshots__/integration.test.js.<lang>.snap` (one per client).
176+
177+
### 8. Diff the regenerated snapshots
178+
179+
`git diff` is the migration's correctness check — the new snapshots vs. the committed ones tell you whether the migration changed any rendered output:
180+
181+
```bash
182+
git diff packages/templates/clients/<protocol>/test/integration-test/__snapshots__/
183+
```
184+
185+
Expect modest whitespace/indent churn. Large semantic diffs (different method names, missing lines, body content changes) mean step 1 (component implementation) or step 6 (consumer props) is wrong → fix the offending step, then re-run step 4 (component snapshot) and steps 7–8 (integration snapshots + diff). Skip steps 2/3/5/6 unless they're what you're fixing.
186+
187+
### 9. Regenerate the API docs
188+
189+
`apps/generator/docs/api_components.md` is a committed `jsdoc2md` artifact and CLAUDE.md section 2.4 requires it be regenerated in the same PR as any public-signature change:
190+
191+
```bash
192+
turbo run docs --filter=@asyncapi/generator-components
193+
```
194+
195+
Then `git diff apps/generator/docs/api_components.md` and commit alongside the source changes. Empty diff = JSDoc tags in step 1 are missing or malformed → fix and rerun.
196+
197+
### 10. Run all tests and lint
198+
199+
From repo root:
200+
201+
- `npm run components:test` — passes.
202+
- `npm run templates:test` — passes (the integration snapshot is now in sync).
203+
- `npm run lint` — passes.
204+
205+
## Reference materials
206+
207+
- Canonical method-shaped example: `packages/components/src/components/RegisterErrorHandler.js`.
208+
- Canonical structural example: `packages/components/src/components/QueryParamsVariables.js`.
209+
- Test idioms: `packages/components/test/components/RegisterErrorHandler.test.js`.
210+
- Error handling utilities: `packages/components/src/utils/ErrorHandling.js`.

.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,21 @@ on:
99
pull_request_target:
1010
types:
1111
- synchronize
12-
- edited
12+
- edited # zizmor: ignore[dangerous-triggers] needed as pull_request token is read-only
13+
14+
permissions: {}
1315

1416
jobs:
1517
remove-ready-label:
18+
name: Remove ready-to-merge label
1619
runs-on: ubuntu-latest
20+
permissions:
21+
pull-requests: write # required to remove labels and post comments on PR issues
1722
steps:
1823
- name: Remove label
19-
uses: actions/github-script@v7
24+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
2025
with:
21-
github-token: ${{ secrets.GH_TOKEN }}
26+
github-token: ${{ github.token }}
2227
script: |
2328
const labelToRemove = 'ready-to-merge';
2429
const labels = context.payload.pull_request.labels;

.github/workflows/automerge.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
name: Automerge PRs from bots
55

66
on:
7-
pull_request_target:
7+
pull_request_target: # Needed as GH_TOKEN_BOT_EVE needed for approval.
88
types:
99
- opened
10-
- synchronize
10+
- synchronize # zizmor: ignore[dangerous-triggers]
11+
12+
permissions: {}
1113

1214
jobs:
1315
autoapprove-for-bot:
1416
name: Autoapprove PR comming from a bot
1517
if: >
1618
contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]"]'), github.event.pull_request.user.login) &&
17-
contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]"]'), github.actor) &&
1819
!contains(github.event.pull_request.labels.*.name, 'released')
1920
runs-on: ubuntu-latest
2021
steps:
@@ -24,7 +25,7 @@ jobs:
2425
github-token: "${{ secrets.GH_TOKEN_BOT_EVE }}"
2526

2627
- name: Label autoapproved
27-
uses: actions/github-script@v7
28+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
2829
with:
2930
github-token: ${{ secrets.GH_TOKEN }}
3031
script: |

.github/workflows/update-docs-on-docs-commits.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
if: startsWith(github.event.commits[0].message, 'docs:')
2525
steps:
2626
- name: Checkout repo
27-
uses: actions/checkout@v4
27+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
2828
with:
2929
persist-credentials: false
3030
- name: Determine what node version to use
@@ -34,7 +34,7 @@ jobs:
3434
node-version: ${{ vars.NODE_VERSION }}
3535
id: lockversion
3636
- name: Use Node.js
37-
uses: actions/setup-node@v4
37+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
3838
with:
3939
node-version: "${{ steps.lockversion.outputs.version }}"
4040
cache: 'npm'

.github/workflows/welcome-first-time-contrib.yml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,32 @@
44
name: Welcome first time contributors
55

66
on:
7-
pull_request_target:
7+
pull_request:
88
types:
99
- opened
1010
issues:
1111
types:
1212
- opened
1313

14+
permissions:
15+
issues: read # Required to check if the issue is the user's first contribution
16+
pull-requests: read # Required to check if the pull request is the user's first contribution
17+
1418
jobs:
1519
welcome:
1620
name: Post welcome message
17-
if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.actor) }}
21+
if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.actor) }} # zizmor: ignore[obfuscation]
1822
runs-on: ubuntu-latest
23+
permissions:
24+
contents: read # Required to read repository data for checking if it's the user's first contribution
25+
issues: write # Required to post welcome message on issues
26+
pull-requests: write # Required to post welcome message on pull requests
1927
steps:
20-
- uses: actions/github-script@v7
28+
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
2129
with:
22-
github-token: ${{ secrets.GITHUB_TOKEN }}
30+
github-token: ${{ github.token }}
2331
script: |
24-
const issueMessage = `Welcome to AsyncAPI. Thanks a lot for reporting your first issue. Please check out our [contributors guide](https://github.com/asyncapi/community/blob/master/CONTRIBUTING.md) and the instructions about a [basic recommended setup](https://github.com/asyncapi/community/blob/master/git-workflow.md) useful for opening a pull request.<br />Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115).`;
32+
const issueMessage = `Welcome to AsyncAPI. Thanks a lot for reporting your first issue. Please check out our [contributors guide](https://github.com/asyncapi/community/blob/master/CONTRIBUTING.md) and the instructions about a [basic recommended setup](https://github.com/asyncapi/community/blob/master/docs/010-contribution-guidelines/git-workflow.md) useful for opening a pull request.<br />Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115).`;
2533
const prMessage = `Welcome to AsyncAPI. Thanks a lot for creating your first pull request. Please check out our [contributors guide](https://github.com/asyncapi/community/blob/master/CONTRIBUTING.md) useful for opening a pull request.<br />Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115).`;
2634
if (!issueMessage && !prMessage) {
2735
throw new Error('Action must have at least one of issue-message or pr-message set');

0 commit comments

Comments
 (0)