-
Notifications
You must be signed in to change notification settings - Fork 30
Update readme and changelog #1239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 4 commits
08a7bda
b0e98ab
aa8596e
2fca400
2d6ab70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,14 +3,18 @@ import { ExperimentalApi, UpdatePackageListingResponse } from "../../api"; | |
| import * as Sentry from "@sentry/react"; | ||
| import { useState } from "react"; | ||
| import { Control } from "react-hook-form/dist/types"; | ||
| import { UseFormSetValue } from "react-hook-form"; | ||
|
|
||
| type Status = undefined | "SUBMITTING" | "SUCCESS" | "ERROR"; | ||
| export type PackageListingUpdateFormValues = { | ||
| categories: { value: string; label: string }[]; | ||
| readme?: { fileName: string; content: string }; | ||
| changelog?: { fileName: string; content: string }; | ||
| }; | ||
|
|
||
| export type PackageListingUpdateForm = { | ||
| onSubmit: () => Promise<void>; | ||
| setValue: UseFormSetValue<PackageListingUpdateFormValues>; | ||
| control: Control<PackageListingUpdateFormValues>; | ||
| error?: string; | ||
| status: Status; | ||
|
|
@@ -20,7 +24,7 @@ export const usePackageListingUpdateForm = ( | |
| packageListingId: string, | ||
| onSuccess: (result: UpdatePackageListingResponse) => void | ||
| ): PackageListingUpdateForm => { | ||
| const { handleSubmit, control } = useForm<PackageListingUpdateFormValues>(); | ||
| const { handleSubmit, control, setValue } = useForm<PackageListingUpdateFormValues>(); | ||
| const [status, setStatus] = useState<Status>(undefined); | ||
| const [error, setError] = useState<string | undefined>(undefined); | ||
|
|
||
|
|
@@ -31,7 +35,11 @@ export const usePackageListingUpdateForm = ( | |
| try { | ||
| const result = await ExperimentalApi.updatePackageListing({ | ||
| packageListingId: packageListingId, | ||
| data: { categories: data.categories.map((x) => x.value) }, | ||
| data: { | ||
| categories: data.categories.map((x) => x.value), | ||
| readme: data.readme?.content, | ||
| changelog: data.changelog?.content, | ||
| }, | ||
|
Comment on lines
+38
to
+42
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify whether `categories` is optional end-to-end and whether form state guarantees it.
set -euo pipefail
echo "1) Serializer requirements:"
rg -n -C3 'class PackageListingUpdateRequestSerializer|categories\s*=|readme\s*=|changelog\s*=' \
django/thunderstore/community/api/experimental/serializers.py
echo
echo "2) Frontend API contract:"
rg -n -C3 'updatePackageListing|UpdatePackageListingResponse|categories\??|readme\??|changelog\??' \
builder/src/api -g '*.ts'
echo
echo "3) Form defaults / category initialization:"
rg -n -C4 'useForm<PackageListingUpdateFormValues>|defaultValues|setValue\("categories"|name:\s*"categories"|canUpdateCategories' \
builder/src/components/PackageManagement -g '*.ts' -g '*.tsx'Repository: thunderstore-io/Thunderstore Length of output: 7120 🏁 Script executed: #!/bin/bash
# Check PackageListingUpdateFormValues type definition and full onSubmit logic
echo "1) PackageListingUpdateFormValues type definition:"
rg -n -C8 'type PackageListingUpdateFormValues|interface PackageListingUpdateFormValues' \
builder/src/components/PackageManagement/
echo
echo "2) Full usePackageListingUpdateForm hook with context:"
head -100 builder/src/components/PackageManagement/hooks.ts | cat -n
echo
echo "3) CategoriesSelect component to confirm conditional rendering:"
rg -n -B5 -A10 'export.*CategoriesSelect|function CategoriesSelect' \
builder/src/components/PackageManagement/Repository: thunderstore-io/Thunderstore Length of output: 5808 Guard Line 39's 🤖 Prompt for AI Agents |
||
| }); | ||
| onSuccess(result); | ||
| setStatus("SUCCESS"); | ||
|
|
@@ -44,6 +52,7 @@ export const usePackageListingUpdateForm = ( | |
|
|
||
| return { | ||
| onSubmit, | ||
| setValue, | ||
| control, | ||
| error, | ||
| status, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| from rest_framework.generics import GenericAPIView | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from rest_framework.permissions import IsAuthenticated | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from rest_framework.response import Response | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from thunderstore.cache.tasks import invalidate_cache_on_commit_async | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from thunderstore.cache.enums import CacheBustCondition | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from thunderstore.api.cyberstorm.services.package_listing import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| approve_package_listing, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,6 +47,18 @@ def post(self, request, *args, **kwargs): | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| listing=listing, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if "readme" in request_serializer.validated_data: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readme = request_serializer.validated_data["readme"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| listing.package.latest.readme_override = readme | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| listing.package.latest.save() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| invalidate_cache_on_commit_async(CacheBustCondition.any_package_updated) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if "changelog" in request_serializer.validated_data: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| changelog = request_serializer.validated_data["changelog"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| listing.package.latest.changelog_override = changelog | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| listing.package.latest.save() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| invalidate_cache_on_commit_async(CacheBustCondition.any_package_updated) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+50
to
+60
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard nullable
Proposed fix- if "readme" in request_serializer.validated_data:
- readme = request_serializer.validated_data["readme"]
- listing.package.latest.readme_override = readme
- listing.package.latest.save()
- invalidate_cache_on_commit_async(CacheBustCondition.any_package_updated)
-
- if "changelog" in request_serializer.validated_data:
- changelog = request_serializer.validated_data["changelog"]
- listing.package.latest.changelog_override = changelog
- listing.package.latest.save()
- invalidate_cache_on_commit_async(CacheBustCondition.any_package_updated)
+ latest = listing.package.latest
+ if latest is None:
+ raise serializers.ValidationError("Package has no latest version")
+
+ update_fields = []
+ if "readme" in request_serializer.validated_data:
+ latest.readme_override = request_serializer.validated_data["readme"]
+ update_fields.append("readme_override")
+ if "changelog" in request_serializer.validated_data:
+ latest.changelog_override = request_serializer.validated_data["changelog"]
+ update_fields.append("changelog_override")
+
+ if update_fields:
+ latest.save(update_fields=update_fields)
+ invalidate_cache_on_commit_async(CacheBustCondition.any_package_updated)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serializer = self.serializer_class(instance=listing) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response(serializer.data, status=status.HTTP_200_OK) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -85,7 +85,10 @@ def get(self, *args, **kwargs): | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| def retrieve(self, request, *args, **kwargs): | ||||||||||||||||||||||
| instance = self.get_object() | ||||||||||||||||||||||
| serializer = self.get_serializer({"markdown": instance.changelog}) | ||||||||||||||||||||||
| if instance.changelog_override is not None: | ||||||||||||||||||||||
| serializer = self.get_serializer({"markdown": instance.changelog_override}) | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| serializer = self.get_serializer({"markdown": instance.changelog}) | ||||||||||||||||||||||
|
Comment on lines
+88
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty string overrides will be used instead of falling back to the original changelog. The serializer allows Fix: Check for both None and empty string: if instance.changelog_override:
serializer = self.get_serializer({"markdown": instance.changelog_override})
else:
serializer = self.get_serializer({"markdown": instance.changelog})
Suggested change
Spotted by Graphite |
||||||||||||||||||||||
| return Response(serializer.data) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -105,5 +108,8 @@ def get(self, *args, **kwargs): | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| def retrieve(self, request, *args, **kwargs): | ||||||||||||||||||||||
| instance = self.get_object() | ||||||||||||||||||||||
| serializer = self.get_serializer({"markdown": instance.readme}) | ||||||||||||||||||||||
| if instance.readme_override is not None: | ||||||||||||||||||||||
| serializer = self.get_serializer({"markdown": instance.readme_override}) | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| serializer = self.get_serializer({"markdown": instance.readme}) | ||||||||||||||||||||||
| return Response(serializer.data) | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # Generated by Django 3.1.7 on 2026-03-13 20:11 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('repository', '0064_add_namespaces_for_existing_teams'), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name='packageversion', | ||
| name='changelog_override', | ||
| field=models.TextField(blank=True, null=True), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='packageversion', | ||
| name='readme_override', | ||
| field=models.TextField(blank=True, null=True), | ||
| ), | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File upload controls are not keyboard-accessible.
Using
display: "none"on the file inputs (Line 81 and Line 93) removes the focusable control; keyboard users may not be able to trigger uploads reliably.Also applies to: 89-95
🤖 Prompt for AI Agents