feat(build): fetch Railpack versions dynamically from GitHub releases#4070
feat(build): fetch Railpack versions dynamically from GitHub releases#4070s24407-pj wants to merge 8 commits into
Conversation
Replace the hardcoded RAILPACK_VERSIONS array with a tRPC query that fetches releases from the GitHub API. Versions are cached for 24 hours and only fetched when the Railpack build type is selected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ection - Change Zod schema default from hardcoded "0.15.4" to null so Zod does not silently override the form field before the fetched versions arrive - Set form value to latest fetched version when no version is selected - Strip v prefix from manually entered versions before saving - Fix form reset loop by splitting useEffect into separate concerns Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Related Documentation 1 document(s) may need updating based on files changed in this PR: Dokploy's Space README
|
| getRailpackVersions: protectedProcedure.query(async () => { | ||
| const res = await fetch( | ||
| "https://api.github.com/repos/railwayapp/railpack/releases", | ||
| { headers: { Accept: "application/vnd.github+json" } }, | ||
| ); | ||
| if (!res.ok) { | ||
| throw new TRPCError({ | ||
| code: "INTERNAL_SERVER_ERROR", | ||
| message: "Failed to fetch Railpack versions from GitHub", | ||
| }); | ||
| } | ||
| const releases = (await res.json()) as Array<{ tag_name: string }>; | ||
| return releases.map((r) => r.tag_name.replace(/^v/, "")); | ||
| }), |
There was a problem hiding this comment.
No server-side caching — GitHub rate limits will be hit quickly
The endpoint makes a fresh GitHub API call on every invocation. GitHub's unauthenticated rate limit is 60 requests/hour per IP address. Because the Dokploy server makes these calls (all users share one IP), a busy instance with multiple users opening the Railpack build tab can exhaust this limit very quickly, causing every subsequent call to fail with a 403/429.
The client-side staleTime: 1000 * 60 * 60 * 24 only helps within a single user's session — it won't help when different users open the page, or when the server restarts and all client caches are cold.
A simple fix is to add a server-side in-memory cache (e.g. a module-level variable with a timestamp) or use a Cache-Control response header from GitHub to drive conditional requests. At minimum, a GITHUB_TOKEN environment variable should be optionally read and forwarded so authenticated rate limits (5 000 req/hour) can be used:
const res = await fetch(
"https://api.github.com/repos/railwayapp/railpack/releases",
{
headers: {
Accept: "application/vnd.github+json",
...(process.env.GITHUB_TOKEN
? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` }
: {}),
},
},
);| railpackVersion: | ||
| data.buildType === BuildType.railpack | ||
| ? data.railpackVersion || "0.15.4" | ||
| ? (data.railpackVersion || railpackVersions?.[0] || "").replace(/^v/, "") | ||
| : null, |
There was a problem hiding this comment.
Empty string saved as version if API is still loading or failed
If the user switches to Railpack and submits before getRailpackVersions resolves (or if the GitHub API request fails entirely), data.railpackVersion is null (the schema default) and railpackVersions?.[0] is undefined. The expression evaluates to "".replace(/^v/, "") → "", which silently saves an empty string as the build version, causing the next build to fail without a clear error.
The Save button should be disabled while versions are loading, or a validation should reject an empty version:
| railpackVersion: | |
| data.buildType === BuildType.railpack | |
| ? data.railpackVersion || "0.15.4" | |
| ? (data.railpackVersion || railpackVersions?.[0] || "").replace(/^v/, "") | |
| : null, | |
| ? (data.railpackVersion || railpackVersions?.[0] || null)?.replace(/^v/, "") ?? null |
Pairing this with disabling the Save button while isLoadingRailpackVersions is true would fully close the gap.
| const { data: railpackVersions, isLoading: isLoadingRailpackVersions } = | ||
| api.application.getRailpackVersions.useQuery(undefined, { | ||
| enabled: buildType === BuildType.railpack, | ||
| staleTime: 1000 * 60 * 60 * 24, // 24 hours | ||
| }); |
There was a problem hiding this comment.
No error state handled for
getRailpackVersions
isError is not destructured from the query. When the GitHub API call fails (rate-limited, network error, etc.), isLoadingRailpackVersions flips back to false but railpackVersions remains undefined. The SelectTrigger becomes enabled again, the dropdown renders no options, and there is no error message anywhere — the user sees a blank selector with no explanation.
At minimum, destructure isError and show an error message:
const {
data: railpackVersions,
isLoading: isLoadingRailpackVersions,
isError: isErrorRailpackVersions,
} = api.application.getRailpackVersions.useQuery(undefined, {
enabled: buildType === BuildType.railpack,
staleTime: 1000 * 60 * 60 * 24,
});Then in the SelectTrigger (or just below the Select), show feedback when isErrorRailpackVersions is true so users understand they can still enter a version manually.
| const res = await fetch( | ||
| "https://api.github.com/repos/railwayapp/railpack/releases", | ||
| { headers: { Accept: "application/vnd.github+json" } }, |
There was a problem hiding this comment.
GitHub API pagination — only first 30 releases returned
The /releases endpoint defaults to per_page=30. Railpack already has more than 20 releases listed in the old hardcoded array, so future releases beyond 30 would be silently omitted. Adding ?per_page=100 would cover the foreseeable future without requiring pagination logic.
| const res = await fetch( | |
| "https://api.github.com/repos/railwayapp/railpack/releases", | |
| { headers: { Accept: "application/vnd.github+json" } }, | |
| "https://api.github.com/repos/railwayapp/railpack/releases?per_page=100", |
|
#4022 won't be necessary once this PR gets merged |
Add validation to ensure that a railpack version is provided when the build type is set to railpack. Update the submission logic to normalize the railpack version and disable the submit button if the version is missing. This enhances user experience by preventing submission with invalid data.
Made-with: Cursor
Summary
RAILPACK_VERSIONSarray and replaces it with a tRPC query (getRailpackVersions) that fetches releases directly from the GitHub APIvprefix from manually entered versions before saving to prevent build failures"0.15.4"instead of the actual latest version🤖 Generated with Claude Code
Greptile Summary
This PR replaces the hardcoded
RAILPACK_VERSIONSarray with a live tRPC query that fetches releases from the GitHub API, adds client-side 24-hour caching, strips thevprefix on save, and handles the error and loading states in the UI. The core approach is sound, though the server-side rate-limit concern raised in prior review threads remains unaddressed.Confidence Score: 3/5
Needs attention before merging — an unresolved P1 (no server-side caching means all users share the 60 req/hr GitHub rate limit) and missing per_page cap can make the feature unreliable on busy instances.
Previous-thread P1 issues (server-side rate-limit exhaustion, GitHub pagination capped at 30 results) are still open in this revision. With multiple unresolved P1s the score is pulled below the 4/5 ceiling to 3/5.
apps/dokploy/server/api/routers/application.ts — getRailpackVersions endpoint needs server-side caching and per_page=100 on the GitHub request.
Reviews (2): Last reviewed commit: "[autofix.ci] apply automated fixes" | Re-trigger Greptile