Skip to content

Commit 75c819c

Browse files
committed
Merge remote-tracking branch 'origin/main' into codegen-bot/integrate-shadcn-input-group-1762992679
2 parents 9caf059 + f0be95a commit 75c819c

20 files changed

Lines changed: 1139 additions & 30 deletions

.cursor/rules/versioning-with-npm.mdc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ You are an expert release manager for a Yarn 4 monorepo who uses the npm CLI for
88
- Treat new components and minor fixes as patch releases when they are additive and low-risk.
99
- Reserve minor/major only for notable feature waves or breaking changes.
1010

11-
Note: While the repo supports Changesets for broader release coordination, this rule documents the npm CLI flow for quick iterations.
11+
Note: The primary release workflow uses Changesets (`yarn changeset` → `yarn changeset version` → automatic CI/CD publish). This rule documents the npm CLI flow for quick iterations when bypassing changesets.
1212

1313
## What Counts As “Small”
1414
- Additive components (new UI or form wrappers) without breaking changes
@@ -47,7 +47,8 @@ Guidelines:
4747

4848
## Open PR and Merge
4949
- Push your branch and open a PR.
50-
- When the PR merges into `main`, GitHub CI publishes the package. No manual tagging or `npm publish` needed.
50+
- When the PR merges into `main`, GitHub CI automatically publishes the package using npm trusted publishers (OIDC). No manual tagging or `npm publish` needed, and no npm tokens required.
51+
- **Note:** The release workflow uses [npm trusted publishers](https://docs.npmjs.com/trusted-publishers) for secure, tokenless publishing. Ensure the trusted publisher is configured on npmjs.com for the package.
5152

5253
## Minor / Major (When Needed)
5354
- Minor: larger feature sets or notable additions across multiple components

.github/workflows/release.yml

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,56 @@ on:
77

88
concurrency: ${{ github.workflow }}-${{ github.ref }}
99

10+
permissions:
11+
id-token: write # Required for OIDC trusted publishing
12+
contents: write # Required for pushing git tags after publishing
13+
1014
jobs:
1115
release:
1216
name: Release
1317
runs-on: ubuntu-latest
1418
steps:
1519
- name: Checkout Repo
16-
uses: actions/checkout@v3
20+
uses: actions/checkout@v4
1721

18-
- name: Setup Node.js 20.x
19-
uses: actions/setup-node@v3
22+
- name: Setup Node.js
23+
uses: actions/setup-node@v4
2024
with:
21-
node-version: 20.x
25+
node-version: '22'
26+
registry-url: 'https://registry.npmjs.org'
27+
# registry-url enables OIDC authentication for npm publish
28+
29+
- name: Install npm 11.5.1+ for trusted publishing
30+
run: npm install -g npm@latest
31+
# Trusted publishing requires npm CLI 11.5.1 or later
2232

2333
- name: Enable Corepack
2434
run: corepack enable
2535

2636
- name: Install Correct Yarn Version
27-
run: corepack prepare yarn@4.4.1 --activate
37+
run: corepack prepare yarn@4.9.1 --activate
2838

2939
- name: Install Dependencies
30-
run: yarn
40+
run: yarn install --immutable
41+
42+
- name: Verify npm and OIDC setup
43+
run: |
44+
echo "npm version: $(npm --version)"
45+
echo "Node version: $(node --version)"
46+
# Check if we're in a GitHub Actions OIDC environment
47+
if [ -n "$ACTIONS_ID_TOKEN_REQUEST_URL" ]; then
48+
echo "✓ OIDC environment detected"
49+
else
50+
echo "⚠ OIDC environment not detected"
51+
fi
52+
# Verify npm version meets trusted publishing requirement (11.5.1+)
53+
NPM_VERSION=$(npm --version | cut -d. -f1,2)
54+
REQUIRED_VERSION="11.5"
55+
if [ "$(printf '%s\n' "$REQUIRED_VERSION" "$NPM_VERSION" | sort -V | head -n1)" = "$REQUIRED_VERSION" ]; then
56+
echo "✓ npm version meets trusted publishing requirement (11.5.1+)"
57+
else
58+
echo "⚠ npm version may be too old for trusted publishing (requires 11.5.1+)"
59+
fi
3160
3261
- name: Create Release Pull Request or Publish to npm
3362
id: changesets
@@ -37,4 +66,6 @@ jobs:
3766
publish: yarn release
3867
env:
3968
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
69+
# No NPM_TOKEN needed - using trusted publishing via OIDC
70+
# The registry-url in setup-node@v4 enables OIDC authentication
71+
# npm CLI 11.5.1+ automatically detects OIDC and uses it for publish

AGENTS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@
3636
- PRs: clear description, linked issues, screenshots or Storybook links, notes on testing.
3737
- Required checks: `yarn lint` passes; build succeeds; tests updated/added.
3838
- Versioning: when changing published package(s), add a Changeset (`yarn changeset`) before merge.
39+
- Publishing: Releases are automatically published via CI/CD using [npm trusted publishers](https://docs.npmjs.com/trusted-publishers) (OIDC). No npm tokens required. See README.md for setup instructions.
3940

4041
## Security & Configuration
4142
- Node `22.9.0` (`.nvmrc`) and Yarn 4 (`packageManager`).
4243
- Do not commit secrets. Keep large artifacts out of VCS (`dist`, `node_modules`).
44+
- Publishing uses npm trusted publishers (OIDC) - no long-lived tokens needed.
4345
- PR previews for Storybook are published via GitHub Pages; verify links in PR comments.
4446

4547
## Cursor Rules Review
@@ -48,7 +50,7 @@
4850
- `.cursor/rules/form-component-patterns.mdc`: Remix Hook Form + Zod wrappers, errors, server actions.
4951
- `.cursor/rules/storybook-testing.mdc`: Storybook play tests, router stub decorator, local/CI flows.
5052
- `.cursor/rules/monorepo-organization.mdc`: Imports/exports, package boundaries, Turbo/Vite/TS paths.
51-
- `.cursor/rules/versioning-with-npm.mdc`: npm CLI version bumps (patch-first), CI publishes on merge.
53+
- `.cursor/rules/versioning-with-npm.mdc`: npm CLI version bumps for quick iterations (patch-first). Primary workflow uses Changesets with automatic CI/CD publishing via npm trusted publishers.
5254

5355
## Agent OS
5456

README.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,134 @@ The PR preview is deployed to the `gh-pages` branch in a directory structure lik
128128
```
129129
/pr-preview/pr-[PR_NUMBER]/
130130
```
131+
132+
## Publishing
133+
134+
Releases can be published either automatically via CI/CD (using npm trusted publishers) or manually from the command line.
135+
136+
### Automatic Publishing (CI/CD)
137+
138+
When you merge changes to `main` with version updates, the GitHub Actions workflow will automatically publish to npm using [npm trusted publishers](https://docs.npmjs.com/trusted-publishers). This uses OIDC authentication and doesn't require npm tokens.
139+
140+
**Setup required:** Configure trusted publishers on npmjs.com for the `@lambdacurry/forms` package (see setup instructions below).
141+
142+
#### Setting Up Trusted Publishers
143+
144+
1. Go to your package on npmjs.com: https://www.npmjs.com/package/@lambdacurry/forms
145+
2. Navigate to **Settings****Trusted Publisher** section
146+
3. Click **"Select your publisher"****GitHub Actions**
147+
4. Configure the following:
148+
- **Organization or user**: `lambda-curry` (or your GitHub username)
149+
- **Repository**: `forms`
150+
- **Workflow filename**: `release.yml` (must match exactly, including `.yml` extension)
151+
5. Click **Save**
152+
153+
The workflow file must exist at `.github/workflows/release.yml` in your repository. Once configured, publishes from the `main` branch will use OIDC authentication automatically.
154+
155+
### Manual Publishing
156+
157+
You can also publish manually from the command line when needed.
158+
159+
### Prerequisites
160+
161+
1. **Ensure you're logged into npm:**
162+
```bash
163+
npm login
164+
```
165+
You must be logged in as a user with publish permissions for the `@lambdacurry` organization.
166+
167+
2. **Verify your npm credentials:**
168+
```bash
169+
npm whoami
170+
```
171+
172+
3. **Ensure you're on the `main` branch and up to date:**
173+
```bash
174+
git checkout main
175+
git pull origin main
176+
```
177+
178+
### Release Process
179+
180+
#### Step 1: Create Changesets (if needed)
181+
182+
If you have changes that need to be documented in the changelog, create a changeset:
183+
184+
```bash
185+
yarn changeset
186+
```
187+
188+
Follow the prompts to:
189+
- Select which packages to include
190+
- Choose the version bump type (patch, minor, major)
191+
- Write a summary of the changes
192+
193+
#### Step 2: Version Packages
194+
195+
This updates package versions and generates the changelog:
196+
197+
```bash
198+
yarn changeset version
199+
```
200+
201+
This will:
202+
- Update `packages/components/package.json` with the new version
203+
- Update `packages/components/CHANGELOG.md` with the new entries
204+
- Remove the consumed changeset files
205+
206+
#### Step 3: Build and Test
207+
208+
Before publishing, ensure everything builds and tests pass:
209+
210+
```bash
211+
yarn build
212+
yarn test
213+
```
214+
215+
#### Step 4: Publish to npm
216+
217+
Publish the package to npm using changesets:
218+
219+
```bash
220+
yarn release
221+
```
222+
223+
This command runs `changeset publish`, which:
224+
- Runs `yarn build` (via `prepublishOnly` hook in package.json)
225+
- Publishes `@lambdacurry/forms` to npm (uses npm under the hood)
226+
- Creates git tags for the release
227+
- Requires you to be logged into npm (`npm login`)
228+
229+
**Note:** `changeset publish` uses npm CLI internally, so you must be authenticated with npm. The changeset system handles versioning, changelog generation, and publishing all in one workflow.
230+
231+
#### Step 5: Commit and Push
232+
233+
After successful publishing, commit the version changes and push:
234+
235+
```bash
236+
git add .
237+
git commit -m "chore(release): publish vX.Y.Z"
238+
git push origin main
239+
```
240+
241+
### Alternative: Direct npm Publish (Without Changesets)
242+
243+
If you need to publish without using changesets (e.g., for a hotfix), you can use npm directly:
244+
245+
```bash
246+
# From the packages/components directory
247+
cd packages/components
248+
npm version patch -m "chore: bump version to %s"
249+
cd ../..
250+
yarn install # Update yarn.lock
251+
yarn workspace @lambdacurry/forms build
252+
npm publish --workspace=packages/components
253+
```
254+
255+
**Note:** This bypasses the changeset workflow, so you'll need to manually update the CHANGELOG.md if you want to document the release.
256+
257+
### Troubleshooting
258+
259+
- **"Not logged in" error**: Run `npm login` and verify with `npm whoami`
260+
- **"Permission denied"**: Ensure your npm user has publish permissions for `@lambdacurry` organization
261+
- **Build fails**: Fix build errors before publishing. The `prepublishOnly` hook will prevent publishing if the build fails

apps/docs/src/lib/storybook/react-router-stub.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useMemo } from 'react';
12
import type { Decorator } from '@storybook/react-vite';
23
import type { ComponentType } from 'react';
34
import {
@@ -30,27 +31,40 @@ interface RemixStubOptions {
3031
export const withReactRouterStubDecorator = (options: RemixStubOptions): Decorator => {
3132
const { routes, initialPath = '/' } = options;
3233

34+
// We define the Stub component outside the return function to ensure it's not recreated
35+
// on every render of the Story component itself.
36+
const CachedStub: ComponentType<{ initialEntries?: string[] }> | null = null;
37+
const lastMappedRoutes: StubRouteObject[] | null = null;
38+
3339
return (Story, context) => {
3440
// Map routes to include the Story component if no Component is provided
35-
const mappedRoutes = routes.map((route) => ({
36-
...route,
37-
Component: route.Component ?? (() => <Story {...context.args} />),
38-
}));
41+
const mappedRoutes = useMemo(
42+
() =>
43+
routes.map((route) => ({
44+
...route,
45+
Component: route.Component ?? Story,
46+
})),
47+
[Story],
48+
);
3949

4050
// Get the base path (without existing query params from options)
41-
const basePath = initialPath.split('?')[0];
51+
const basePath = useMemo(() => initialPath.split('?')[0], []);
4252

4353
// Get the current search string from the actual browser window, if available
4454
// If not available, use a default search string with parameters needed for the data table
4555
const currentWindowSearch = typeof window !== 'undefined' ? window.location.search : '?page=0&pageSize=10';
4656

4757
// Combine them for the initial entry
48-
const actualInitialPath = `${basePath}${currentWindowSearch}`;
58+
const actualInitialPath = useMemo(() => `${basePath}${currentWindowSearch}`, [basePath, currentWindowSearch]);
4959

5060
// Use React Router's official createRoutesStub
51-
const Stub = createRoutesStub(mappedRoutes);
61+
// We memoize the Stub component to prevent unnecessary remounts of the entire story
62+
// when the decorator re-renders.
63+
const Stub = useMemo(() => createRoutesStub(mappedRoutes), [mappedRoutes]);
64+
65+
const initialEntries = useMemo(() => [actualInitialPath], [actualInitialPath]);
5266

53-
return <Stub initialEntries={[actualInitialPath]} />;
67+
return <Stub initialEntries={initialEntries} />;
5468
};
5569
};
5670

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { userEvent, within, screen, waitFor } from '@storybook/test';
2+
3+
/**
4+
* A robust helper to select an option from a Radix-based Select/Combobox.
5+
* Handles portals, animations, and pointer-event blockers.
6+
*/
7+
export async function selectRadixOption(
8+
canvasElement: HTMLElement,
9+
options: {
10+
triggerRole?: 'combobox' | 'button';
11+
triggerName: string | RegExp;
12+
optionName: string | RegExp;
13+
optionTestId?: string;
14+
},
15+
) {
16+
const canvas = within(canvasElement);
17+
const { triggerRole = 'combobox', triggerName, optionName, optionTestId } = options;
18+
19+
// 1. Find and click the trigger within the component canvas
20+
const trigger = await canvas.findByRole(triggerRole, { name: triggerName });
21+
if (!trigger) throw new Error(`Trigger with role ${triggerRole} and name ${triggerName} not found`);
22+
23+
await userEvent.click(trigger);
24+
25+
// 2. Wait for the listbox to appear in the document body (Portal)
26+
// We use a slightly longer timeout for CI stability.
27+
const listbox = await screen.findByRole('listbox', {}, { timeout: 3000 });
28+
if (!listbox) throw new Error('Radix listbox (portal) not found after clicking trigger');
29+
30+
// 3. Find the option specifically WITHIN the listbox
31+
let option: HTMLElement | null = null;
32+
if (optionTestId) {
33+
option = await within(listbox).findByTestId(optionTestId);
34+
} else {
35+
option = await within(listbox).findByRole('option', { name: optionName });
36+
}
37+
38+
if (!option) throw new Error(`Option ${optionName || optionTestId} not found in listbox`);
39+
40+
// 4. Click the option
41+
// pointerEventsCheck: 0 is used to bypass Radix's temporary pointer-event locks during animations
42+
await userEvent.click(option, { pointerEventsCheck: 0 });
43+
44+
// 5. Verify the dropdown closed (optional but ensures stability)
45+
await waitFor(() => {
46+
const listbox = screen.queryByRole('listbox');
47+
if (listbox) throw new Error('Listbox still visible');
48+
});
49+
}

apps/docs/src/remix-hook-form/calendar-with-month-year-select.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ const ControlledCalendarWithFormExample = () => {
8686
});
8787

8888
const [dropdown, setDropdown] = React.useState<'dropdown' | 'dropdown-months' | 'dropdown-years'>('dropdown');
89-
const [date, setDate] = React.useState<Date | undefined>();
89+
const [date, setDate] = React.useState<Date | undefined>(new Date(2025, 5, 12));
9090

9191
const dropdownOptions = [
9292
{ label: 'Month and Year', value: 'dropdown' },

0 commit comments

Comments
 (0)