Skip to content

Commit d0437bf

Browse files
authored
Merge pull request #240 from udecode/codex/raw-convex-auth-schema-rerun
2 parents 08cd986 + 73ea408 commit d0437bf

19 files changed

Lines changed: 656 additions & 12 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"kitcn": patch
3+
---
4+
5+
## Patches
6+
7+
- Fix raw Convex auth reruns so added Better Auth plugins refresh the generated schema without `--overwrite`.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Changeset Auto Release Checkbox
2+
3+
on:
4+
pull_request:
5+
types: [opened, reopened, synchronize, edited]
6+
pull_request_target:
7+
types: [opened, reopened, synchronize, edited]
8+
9+
permissions:
10+
contents: read
11+
pull-requests: write
12+
13+
jobs:
14+
sync-auto-release-checkbox:
15+
name: Sync auto-release checkbox
16+
runs-on: ubuntu-latest
17+
if: >-
18+
github.repository == 'udecode/kitcn' &&
19+
(
20+
(github.event_name == 'pull_request' &&
21+
github.event.pull_request.head.repo.full_name == github.repository) ||
22+
(github.event_name == 'pull_request_target' &&
23+
github.event.pull_request.head.repo.full_name != github.repository)
24+
)
25+
26+
steps:
27+
- name: 📥 Checkout workflow helper
28+
uses: actions/checkout@v4
29+
with:
30+
ref: >-
31+
${{
32+
github.event_name == 'pull_request'
33+
&& github.event.pull_request.head.sha
34+
|| github.event.repository.default_branch
35+
}}
36+
persist-credentials: false
37+
38+
- name: 🔁 Sync auto-release checkbox
39+
uses: actions/github-script@v7
40+
with:
41+
script: |
42+
const { pathToFileURL } = await import('node:url');
43+
const helperUrl = pathToFileURL(
44+
`${process.env.GITHUB_WORKSPACE}/tooling/auto-release-pr.mjs`
45+
).href;
46+
const {
47+
getChangesetReleaseType,
48+
shouldManageAutoReleaseBlock,
49+
upsertAutoReleaseBlock,
50+
} = await import(helperUrl);
51+
52+
const pullRequest = context.payload.pull_request;
53+
const files = await github.paginate(github.rest.pulls.listFiles, {
54+
owner: context.repo.owner,
55+
repo: context.repo.repo,
56+
pull_number: pullRequest.number,
57+
per_page: 100,
58+
});
59+
60+
const hasChangeset = shouldManageAutoReleaseBlock({
61+
files,
62+
title: pullRequest.title ?? '',
63+
});
64+
const releaseType = hasChangeset
65+
? getChangesetReleaseType(files)
66+
: null;
67+
const currentBody = pullRequest.body ?? '';
68+
const nextBody = upsertAutoReleaseBlock(currentBody, {
69+
defaultChecked: releaseType === 'patch',
70+
hasChangeset,
71+
});
72+
73+
if (nextBody === currentBody) {
74+
core.info('PR body already matches auto-release policy.');
75+
return;
76+
}
77+
78+
await github.rest.pulls.update({
79+
owner: context.repo.owner,
80+
repo: context.repo.repo,
81+
pull_number: pullRequest.number,
82+
body: nextBody,
83+
});

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ jobs:
1818
default:
1919
name: CI
2020
runs-on: ubuntu-latest
21+
if: >-
22+
github.event_name != 'pull_request' ||
23+
github.event.pull_request.title != '[Release] Version packages'
2124
2225
steps:
2326
- name: Checkout

.github/workflows/release.yml

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,85 @@ jobs:
3131
# @link https://github.com/actions/checkout#fetch-all-history-for-all-tags-and-branches
3232
fetch-depth: 0
3333

34+
- name: 🔎 Detect auto-release opt-in
35+
id: auto_release
36+
uses: actions/github-script@v7
37+
with:
38+
script: |
39+
const { pathToFileURL } = await import('node:url');
40+
const helperUrl = pathToFileURL(
41+
`${process.env.GITHUB_WORKSPACE}/tooling/auto-release-pr.mjs`
42+
).href;
43+
const { hasChangesetFile, isAutoReleaseChecked } = await import(helperUrl);
44+
45+
const owner = context.repo.owner;
46+
const repo = context.repo.repo;
47+
const pullNumbers = new Set();
48+
49+
const { data: associatedPullRequests } =
50+
await github.rest.repos.listPullRequestsAssociatedWithCommit({
51+
owner,
52+
repo,
53+
commit_sha: context.sha,
54+
});
55+
56+
for (const pullRequest of associatedPullRequests) {
57+
pullNumbers.add(pullRequest.number);
58+
}
59+
60+
const headCommitMessage = context.payload.head_commit?.message ?? '';
61+
for (const match of headCommitMessage.matchAll(/\(#(\d+)\)|#(\d+)/g)) {
62+
const pullNumber = Number(match[1] ?? match[2]);
63+
64+
if (pullNumber) pullNumbers.add(pullNumber);
65+
}
66+
67+
for (const pullNumber of pullNumbers) {
68+
let pullRequest;
69+
70+
try {
71+
const { data } = await github.rest.pulls.get({
72+
owner,
73+
repo,
74+
pull_number: pullNumber,
75+
});
76+
77+
pullRequest = data;
78+
} catch (error) {
79+
core.warning(`Could not read PR #${pullNumber}: ${error.message}`);
80+
continue;
81+
}
82+
83+
if (!pullRequest.merged_at) {
84+
core.info(`Skipping PR #${pullRequest.number}; it is not merged.`);
85+
continue;
86+
}
87+
88+
if (!isAutoReleaseChecked(pullRequest.body ?? '')) {
89+
core.info(`Skipping PR #${pullRequest.number}; auto-release is unchecked.`);
90+
continue;
91+
}
92+
93+
const files = await github.paginate(github.rest.pulls.listFiles, {
94+
owner,
95+
repo,
96+
pull_number: pullRequest.number,
97+
per_page: 100,
98+
});
99+
100+
if (!hasChangesetFile(files)) {
101+
core.info(`Skipping PR #${pullRequest.number}; no changeset file found.`);
102+
continue;
103+
}
104+
105+
core.setOutput('enabled', 'true');
106+
core.setOutput('source_pr', String(pullRequest.number));
107+
core.notice(`Auto-release enabled by PR #${pullRequest.number}.`);
108+
return;
109+
}
110+
111+
core.setOutput('enabled', 'false');
112+
34113
- uses: oven-sh/setup-bun@v2
35114
name: Install bun
36115
with:
@@ -69,5 +148,20 @@ jobs:
69148
# See https://github.com/changesets/action/issues/147
70149
HOME: ${{ github.workspace }}
71150
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
72-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
151+
GITHUB_TOKEN: ${{ secrets.API_TOKEN_GITHUB || secrets.GITHUB_TOKEN }}
73152
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
153+
154+
- name: 🤖 Merge Version Packages PR
155+
if: ${{ steps.auto_release.outputs.enabled == 'true' && steps.changesets.outputs.pullRequestNumber != '' }}
156+
env:
157+
GH_TOKEN: ${{ secrets.API_TOKEN_GITHUB }}
158+
RELEASE_PR: ${{ steps.changesets.outputs.pullRequestNumber }}
159+
SOURCE_PR: ${{ steps.auto_release.outputs.source_pr }}
160+
run: |
161+
if [[ -z "${GH_TOKEN}" ]]; then
162+
echo "API_TOKEN_GITHUB is required so the merged release PR can trigger publish workflows."
163+
exit 1
164+
fi
165+
166+
gh pr comment "$RELEASE_PR" --body "Merging because PR #${SOURCE_PR} checked auto release."
167+
gh pr merge "$RELEASE_PR" --squash --delete-branch --admin

docs/solutions/integration-issues/auth-schema-reconcile-via-add-command-20260323.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ symptoms:
1111
- docs tell users to run `@better-auth/cli generate` by hand after changing auth plugins or auth fields
1212
- managed auth schema files drift from the current auth definition
1313
- raw Convex and kitcn auth paths use different generated files but need the same refresh behavior
14+
- `kitcn add auth --preset convex --yes` reports the generated raw Convex auth schema in "Skipped files" after auth plugins change
1415
module: auth-cli
1516
resolved: 2026-03-23
17+
last_updated: 2026-04-23
1618
---
1719

1820
# Auth schema reconciliation belongs to add auth
@@ -40,10 +42,16 @@ So rerunning `kitcn add auth` could patch routes and other files, but
4042
it could not refresh the managed auth schema file from the current auth
4143
options.
4244

45+
A later raw Convex failure exposed the apply-layer half of the same ownership
46+
contract. Reconciliation computed fresh `authSchema.ts` content, but scaffold
47+
files with a template id require explicit overwrite by default. In
48+
non-interactive `--yes` mode, the generated schema was skipped unless the user
49+
deleted the file or passed `--overwrite`.
50+
4351
## Solution
4452

45-
Teach the registry planner a generic scaffold-file reconciliation seam, then use
46-
it for auth.
53+
Teach the registry planner a generic scaffold-file reconciliation hook, then
54+
use it for auth.
4755

4856
Auth now:
4957

@@ -58,18 +66,39 @@ Mode-specific output stays intact:
5866
- raw Convex adoption refreshes `<functionsDir>/authSchema.ts` with
5967
`export const authSchema = ...`
6068

69+
Generated auth schema files also carry managed-update policy through the
70+
scaffold planner:
71+
72+
```ts
73+
nextFiles[index] = {
74+
...nextFiles[index]!,
75+
content,
76+
requiresExplicitOverwrite: false,
77+
};
78+
```
79+
80+
That lets `kitcn add auth --preset convex --yes` refresh generated schema
81+
content while preserving user-owned auth runtime, config, and client files.
82+
6183
## Verification
6284

6385
- `bun test packages/kitcn/src/auth/create-schema-orm.test.ts packages/kitcn/src/auth/create-schema.test.ts packages/kitcn/src/cli/registry/index.test.ts packages/kitcn/src/cli/registry/planner.test.ts packages/kitcn/src/cli/registry/items/auth/reconcile-auth-schema.test.ts`
6486
- `bun --cwd packages/kitcn build`
6587
- `bun --cwd packages/kitcn typecheck`
6688
- `bun lint:fix`
89+
- `bun test packages/kitcn/src/cli/cli.commands.ts -t "regenerates raw convex auth schema"`
90+
- `bun run test:cli`
91+
- `bun test packages/kitcn/src/cli/registry/planner.test.ts packages/kitcn/src/cli/registry/items/auth/reconcile-auth-schema.test.ts`
92+
- `bun typecheck`
6793

6894
## Prevention
6995

70-
1. Managed scaffold files need a reconciliation seam, not one-off external CLI
96+
1. Managed scaffold files need a reconciliation hook, not one-off external CLI
7197
instructions.
7298
2. Keep one public verb per capability. Installing and refreshing auth both
7399
belong to `add auth`.
74100
3. If docs require users to hand-run a generator for a file the CLI already
75101
owns, the architecture is lying.
102+
4. Preserve user-authored scaffold files and auto-refresh generated schema
103+
files with separate apply policies. `--yes` should skip user edits, not skip
104+
managed schema regeneration.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
title: Scenario stale-port cleanup must not kill unrelated listeners
3+
date: 2026-04-25
4+
category: test-failures
5+
module: scenarios
6+
problem_type: test_failure
7+
component: tooling
8+
symptoms:
9+
- "`bun run scenario:test -- all` dies with SIGKILL after the first scenario"
10+
- "A single scenario like `bun run scenario:test -- expo` passes"
11+
- "Running `expo` then `expo-auth` in one process dies between scenarios"
12+
root_cause: logic_error
13+
resolution_type: code_fix
14+
severity: high
15+
tags:
16+
- scenarios
17+
- runtime
18+
- cleanup
19+
- lsof
20+
- sigkill
21+
---
22+
23+
# Scenario stale-port cleanup must not kill unrelated listeners
24+
25+
## Problem
26+
27+
The aggregate scenario runtime gate can kill itself between scenarios when
28+
stale prepared apps point at shared local ports. The bug hides in cleanup, so
29+
single-scenario proof can pass while `scenario:test -- all` dies.
30+
31+
## Symptoms
32+
33+
- `bun run scenario:test -- all` exits with SIGKILL immediately after the
34+
first scenario reaches ready.
35+
- `bun run scenario:test -- expo` and `bun run scenario:test -- expo-auth`
36+
both pass on their own.
37+
- A two-scenario repro logs `AFTER expo`, then dies before `expo-auth` starts.
38+
39+
## What Didn't Work
40+
41+
- Treating the failure as memory pressure was too vague. The machine had other
42+
stale processes, but the first scenario passed reliably on its own.
43+
- Rerunning the full gate without isolating the transition only repeated the
44+
SIGKILL.
45+
- Looking only at dev server shutdown missed the stale prepared-app cleanup
46+
that runs before the next scenario is prepared.
47+
48+
## Solution
49+
50+
Constrain stale-port cleanup to processes that are actually owned by the
51+
prepared scenario project.
52+
53+
Before this fix, `stopLocalConvexBackendForProject()` read a port from the old
54+
project's `.env.local`, ran `lsof -ti tcp:<port>`, and sent `kill -9` to every
55+
listener. That was too broad because scenario ports are shared across prepared
56+
apps.
57+
58+
The fixed flow checks each candidate listener's cwd and only kills it when the
59+
process cwd is inside the target scenario project:
60+
61+
```ts
62+
export const isProcessOwnedByProject = (pid: string, projectDir: string) => {
63+
const result = Bun.spawnSync({
64+
cmd: ["lsof", "-a", "-p", pid, "-d", "cwd", "-Fn"],
65+
stdin: "ignore",
66+
stdout: "pipe",
67+
stderr: "ignore",
68+
});
69+
70+
const resolvedProjectDir = path.resolve(projectDir);
71+
return result.stdout
72+
.toString()
73+
.split(/\r?\n/)
74+
.filter((line) => line.startsWith("n"))
75+
.some((line) => {
76+
const cwd = path.resolve(line.slice(1));
77+
return (
78+
cwd === resolvedProjectDir ||
79+
cwd.startsWith(`${resolvedProjectDir}${path.sep}`)
80+
);
81+
});
82+
};
83+
```
84+
85+
Add a regression with an unrelated child listener on the stale port. Cleanup
86+
must leave that process alive.
87+
88+
## Why This Works
89+
90+
The cleanup intent is to stop stale processes from the prepared scenario app,
91+
not to own the whole machine's port table. Filtering by cwd preserves that
92+
intent while avoiding a broad `kill -9` against any process that happens to use
93+
the same localhost port.
94+
95+
## Prevention
96+
97+
- Never kill by port alone in scenario tooling.
98+
- For temp-app cleanup, prove both sides: stale project-owned listeners are
99+
eligible, unrelated listeners are not.
100+
- When `scenario:test -- all` fails but individual scenarios pass, debug the
101+
transition between scenarios before blaming the scenario itself.
102+
103+
## Related Issues
104+
105+
- [Scenario dev needed a Vite frontend split and React 18-safe client build](../integration-issues/scenario-vite-dev-split-and-react18-runtime-20260322.md)

fixtures/next-auth/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"convex": "1.35.1",
1010
"hono": "4.12.9",
1111
"kitcn": "workspace:*",
12-
"lucide-react": "^1.8.0",
12+
"lucide-react": "^1.11.0",
1313
"next": "16.1.7",
1414
"next-themes": "^0.4.6",
1515
"react": "^19.2.4",

0 commit comments

Comments
 (0)