diff --git a/client/src/components/package-details/MinimalPackageTable.tsx b/client/src/components/package-details/MinimalPackageTable.tsx index a15a0024..63dfb4c2 100644 --- a/client/src/components/package-details/MinimalPackageTable.tsx +++ b/client/src/components/package-details/MinimalPackageTable.tsx @@ -4,12 +4,13 @@ import { ArchDistroString } from '../../types/package-info' import MinimalPackageTableRow from './MinimalPackageTableRow' import { useTranslation } from 'react-i18next' -const MinimalPackageTable: FC<{ packages: (ArchDistroString | string)[] }> = ({ - packages, -}) => { +const MinimalPackageTable: FC<{ + packages: (ArchDistroString | string)[] + type: string +}> = ({ packages, type }) => { const { t } = useTranslation() return ( - +
@@ -21,9 +22,13 @@ const MinimalPackageTable: FC<{ packages: (ArchDistroString | string)[] }> = ({ {packages.map((pkg, i) => ( ))} diff --git a/client/src/components/package-details/MinimalPackageTableRow.tsx b/client/src/components/package-details/MinimalPackageTableRow.tsx index 7a053376..7bfa6dab 100644 --- a/client/src/components/package-details/MinimalPackageTableRow.tsx +++ b/client/src/components/package-details/MinimalPackageTableRow.tsx @@ -10,22 +10,11 @@ import { FC } from 'react' import { useTranslation } from 'react-i18next' import { Link as Rlink } from 'react-router-dom' -const getDescription = (nameWithDescription: string): string | null => { - return nameWithDescription?.includes(':') - ? nameWithDescription.split(':')[1].trim() - : null -} - -const getName = (nameWithDescription: string): string => { - return nameWithDescription?.includes(':') - ? nameWithDescription.split(':')[0] - : nameWithDescription -} - -const MinimalPackageTableRow: FC<{ pkg: string; external: boolean }> = ({ - pkg, - external, -}) => { +const MinimalPackageTableRow: FC<{ + pkg: string + description: string + external: boolean +}> = ({ pkg, description, external }) => { const { t } = useTranslation() return ( @@ -34,13 +23,13 @@ const MinimalPackageTableRow: FC<{ pkg: string; external: boolean }> = ({ {external ? ( - <>{getName(pkg)} + <>{pkg} ) : ( = ({ 'pink.600', 'pink.400', )} - to={`/packages/${getName(pkg)}`} + to={`/packages/${pkg}`} > - {getName(pkg)} + {pkg} )} diff --git a/client/src/components/package-details/PackageDependenciesModal.tsx b/client/src/components/package-details/PackageDependenciesModal.tsx index fa9bfa10..2086c0d9 100644 --- a/client/src/components/package-details/PackageDependenciesModal.tsx +++ b/client/src/components/package-details/PackageDependenciesModal.tsx @@ -51,6 +51,7 @@ const PackageDependenciesModal: FC<{ name: string } & UseDisclosureProps> = ({ packages={ data!.runtimeDependencies || [] } + type='runtimeDependencies' /> )} @@ -64,6 +65,7 @@ const PackageDependenciesModal: FC<{ name: string } & UseDisclosureProps> = ({ )} @@ -79,6 +81,7 @@ const PackageDependenciesModal: FC<{ name: string } & UseDisclosureProps> = ({ packages={ data!.optionalDependencies || [] } + type='optionalDependencies' /> )} @@ -94,6 +97,7 @@ const PackageDependenciesModal: FC<{ name: string } & UseDisclosureProps> = ({ packages={ data!.pacstallDependencies || [] } + type='pacstallDependencies' /> )} diff --git a/client/src/components/package-details/PackageDetailsHeader.tsx b/client/src/components/package-details/PackageDetailsHeader.tsx index 84a5074e..e78fb352 100644 --- a/client/src/components/package-details/PackageDetailsHeader.tsx +++ b/client/src/components/package-details/PackageDetailsHeader.tsx @@ -1,7 +1,6 @@ import { HStack, Heading, Text } from '@chakra-ui/react' import { FC } from 'react' import PackageInfo from '../../types/package-info' -import InstallNowButton from './InstallNowButton' const PackageDetailsHeader: FC<{ data: PackageInfo; isMobile: boolean }> = ( { data }, @@ -13,7 +12,6 @@ const PackageDetailsHeader: FC<{ data: PackageInfo; isMobile: boolean }> = ( {data.packageName} - {!isMobile && } {data.description} diff --git a/client/src/components/package-details/PackageDetailsPage.tsx b/client/src/components/package-details/PackageDetailsPage.tsx index d00aa32b..5c7ce68a 100644 --- a/client/src/components/package-details/PackageDetailsPage.tsx +++ b/client/src/components/package-details/PackageDetailsPage.tsx @@ -52,7 +52,13 @@ const PackageDetailsPage: FC = ({ requiredByModal={requiredByModal} /> 1 + ? data.baseIndex === 0 + ? `${data.packageBase}:pkgbase` + : `${data.packageBase}:${data.packageName}` + : data.packageName + } prettyName={data.packageName} isMobile={isMobile} /> diff --git a/client/src/components/package-details/PackageDetailsTable.tsx b/client/src/components/package-details/PackageDetailsTable.tsx index 3701a4a4..ecfdfe71 100644 --- a/client/src/components/package-details/PackageDetailsTable.tsx +++ b/client/src/components/package-details/PackageDetailsTable.tsx @@ -114,7 +114,7 @@ const PackageDetailsTable: FC<{ {t('packageDetails.openInGithub')}{' '} = ({ const { data, loading, error } = usePackageRequiredBy(name) const { t } = useTranslation() - const ComputedPackageList = () => + const ComputedPackageList = () => ( + + ) return ( diff --git a/client/src/components/packages/PackageTableRow.tsx b/client/src/components/packages/PackageTableRow.tsx index da198715..6e4c0abe 100644 --- a/client/src/components/packages/PackageTableRow.tsx +++ b/client/src/components/packages/PackageTableRow.tsx @@ -90,7 +90,16 @@ const PackageTableRow: FC<{ pkg: PackageInfo; disabled?: boolean }> = ({ display={useBreakpointValue({ base: 'none', md: 'table-cell' })} > - + 1 + ? pkg.baseIndex === 0 + ? `${pkg.packageBase}:pkgbase` + : `${pkg.packageBase}:${pkg.packageName}` + : pkg.packageName + } + /> diff --git a/client/src/hooks/usePackageDependencies.ts b/client/src/hooks/usePackageDependencies.ts index dcfa19d6..76632b65 100644 --- a/client/src/hooks/usePackageDependencies.ts +++ b/client/src/hooks/usePackageDependencies.ts @@ -9,20 +9,50 @@ const usePackageDependencies = (name: string) => { const [error, setError] = useState(false) useEffect(() => { - setError(false) - setLoading(true) - axios - .get( - serverConfig.host + `/api/packages/${name}/dependencies`, - ) - .then(result => { + const fetchDependencies = async () => { + setError(false) + setLoading(true) + + try { + const result = await axios.get( + `${serverConfig.host}/api/packages/${name}/dependencies`, + ) + + const pacdeps = result.data.pacstallDependencies || [] + + const descriptionDeps = await Promise.all( + pacdeps.map(async dep => { + try { + const depResult = await axios.get( + `${serverConfig.host}/api/packages/${dep.value}`, + ) + return { + ...dep, + description: depResult.data.description, + } + } catch (e) { + console.error( + `Failed to fetch description for ${dep.value}`, + e, + ) + return { ...dep, description: null } + } + }), + ) + + setData({ + ...result.data, + pacstallDependencies: descriptionDeps, + }) setLoading(false) - setData(result.data) - }) - .catch(() => { + } catch (e) { + console.error('Error fetching package dependencies:', e) setError(true) setLoading(false) - }) + } + } + + fetchDependencies() }, [name]) return { diff --git a/client/src/types/package-info.ts b/client/src/types/package-info.ts index ea9b3292..bcf2a52d 100644 --- a/client/src/types/package-info.ts +++ b/client/src/types/package-info.ts @@ -1,8 +1,12 @@ export default interface PackageInfo { packageName: string prettyName: string + packageBase: string + baseIndex: int + baseTotal: int description: string version: string + sourceVersion: string release: string epoch: string latestVersion?: string diff --git a/server/types/pac/parser/last_updated.go b/server/types/pac/parser/last_updated.go index adf1bb8d..2cf4236b 100644 --- a/server/types/pac/parser/last_updated.go +++ b/server/types/pac/parser/last_updated.go @@ -85,7 +85,7 @@ func setLastUpdatedAt(packages []*pac.Script) error { for _, pkg := range packages { if tuple, err := array.FindBy(lastUpdatedTuples, func(tuple packageLastUpdatedTuple) bool { - return tuple.packageName == pkg.PackageName + return tuple.packageName == pkg.PackageBase }); err == nil { pkg.LastUpdatedAt = tuple.lastUpdated } else { diff --git a/server/types/pac/parser/pacsh/git_version.go b/server/types/pac/parser/pacsh/git_version.go index c40d00f9..e5ee12df 100644 --- a/server/types/pac/parser/pacsh/git_version.go +++ b/server/types/pac/parser/pacsh/git_version.go @@ -18,8 +18,10 @@ func ApplyGitVersion(p *pac.Script) error { } if p.Epoch != "" { + p.SourceVersion = version p.Version = p.Epoch + ":" + version + "-" + p.Release } else { + p.SourceVersion = version p.Version = version + "-" + p.Release } diff --git a/server/types/pac/parser/pacsh/parse_pac_output.go b/server/types/pac/parser/pacsh/parse_pac_output.go index 21fd336f..91aa5f3a 100644 --- a/server/types/pac/parser/pacsh/parse_pac_output.go +++ b/server/types/pac/parser/pacsh/parse_pac_output.go @@ -5,15 +5,17 @@ import ( "pacstall.dev/webserver/types/pac" ) -func ParsePacOutput(data []byte) (*pac.Script, error) { +func ParsePacOutput(data []byte) ([]*pac.Script, error) { + var scripts []*pac.Script out, err := srcinfo.Parse(string(data)) if err != nil { return nil, err } - ps := pac.FromSrcInfo(*out) + scripts = pac.FromSrcInfo(*out) + for idx := range scripts { + scripts[idx].PrettyName = getPrettyName(scripts[idx]) + } - ps.PrettyName = getPrettyName(ps) - - return ps, nil + return scripts, nil } diff --git a/server/types/pac/parser/parse.go b/server/types/pac/parser/parse.go index 26eca283..e6fa9de5 100644 --- a/server/types/pac/parser/parse.go +++ b/server/types/pac/parser/parse.go @@ -66,7 +66,7 @@ func ParseAll() error { })) pacstore.Update(loadedPacscripts) - log.Info("successfully loaded %v (%v / %v) packages", types.Percent(float64(len(loadedPacscripts))/float64(len(pkgList))), len(loadedPacscripts), len(pkgList)) + log.Info("successfully loaded %v packages from %v pacscripts", len(loadedPacscripts), len(pkgList)) return nil } @@ -79,11 +79,19 @@ func readKnownPacscriptNames() ([]string, error) { } names := strings.Split(strings.TrimSpace(string(bytes)), "\n") - for idx := range names { - names[idx] = strings.TrimSpace(names[idx]) - } + var filteredNames []string + + for idx := range names { + names[idx] = strings.TrimSpace(names[idx]) + + if strings.HasSuffix(names[idx], ":pkgbase") { + filteredNames = append(filteredNames, strings.TrimSuffix(names[idx], ":pkgbase")) + } else if !strings.Contains(names[idx], ":") { + filteredNames = append(filteredNames, names[idx]) + } + } - return names, nil + return filteredNames, nil } func parsePacscriptFiles(names []string) ([]*pac.Script, error) { @@ -92,19 +100,26 @@ func parsePacscriptFiles(names []string) ([]*pac.Script, error) { } log.Info("parsing pacscripts...") - outChan := batch.Run(int(config.MaxOpenFiles), names, func(pacName string) (*pac.Script, error) { + outChan := batch.Run(int(config.MaxOpenFiles), names, func(pacName string) ([]*pac.Script, error) { out, err := ParsePacscriptFile(config.GitClonePath, pacName) if config.Repology.Enabled { - if err := repology.Sync(out); err != nil { - log.Debug("failed to sync %v with repology. Error: %+v", pacName, err) - } + for _, script := range out { + if err := repology.Sync(script); err != nil { + log.Debug("failed to sync %v with repology. Error: %+v", pacName, err) + } + } } return out, err }) + results := channels.ToSlice(outChan) + var allScripts []*pac.Script + for _, scripts := range results { + allScripts = append(allScripts, scripts...) + } - return channels.ToSlice(outChan), nil + return allScripts, nil } func readPacscriptFile(rootDir, name string) (scriptBytes []byte, fileName string, err error) { @@ -118,7 +133,7 @@ func readPacscriptFile(rootDir, name string) (scriptBytes []byte, fileName strin return scriptBytes, consts.SRCINFO_FILE_EXTENSION, nil } -func ParsePacscriptFile(programsDirPath, name string) (*pac.Script, error) { +func ParsePacscriptFile(programsDirPath, name string) ([]*pac.Script, error) { srcInfoData, _, err := readPacscriptFile(programsDirPath, name) if err != nil { return nil, errorx.Decorate(err, "failed to read pacscript '%v'", name) diff --git a/server/types/pac/parser/search.go b/server/types/pac/parser/search.go index f1b99407..63252601 100644 --- a/server/types/pac/parser/search.go +++ b/server/types/pac/parser/search.go @@ -37,6 +37,7 @@ func FilterPackages(packages []*pac.Script, filter, filterBy string) []*pac.Scri case "name": return filterByFunc(func(pi *pac.Script) bool { return strings.Contains(pi.PackageName, filter) || + strings.Contains(pi.PackageBase, filter) || func(ps []pac.ArchDistroString, filter string) bool { return array.Any(ps, func(it pac.ArchDistroString) bool { return strings.Contains(it.Value, filter) diff --git a/server/types/pac/script.go b/server/types/pac/script.go index dd53b5a1..e48524c1 100644 --- a/server/types/pac/script.go +++ b/server/types/pac/script.go @@ -45,8 +45,12 @@ func (a ArchDistroString) Equals(b types.Equaller) bool { type Script struct { PackageName string `json:"packageName"` PrettyName string `json:"prettyName"` + PackageBase string `json:"packageBase"` + BaseIndex int `json:"baseIndex"` + BaseTotal int `json:"baseTotal"` Description string `json:"description"` Version string `json:"version"` + SourceVersion string `json:"sourceVersion"` Release string `json:"release"` Epoch string `json:"epoch"` LatestVersion *string `json:"latestVersion"` @@ -99,51 +103,137 @@ func (p *Script) Type() types.PackageTypeName { return types.PackageTypeSuffixToPackageTypeName["-git"] } -func FromSrcInfo(info srcinfo.Srcinfo) *Script { - return &Script{ - PackageName: info.Packages[0].Pkgname, // needs to be looped for every pkgname within pkgbase - PrettyName: "", - Description: info.Pkgdesc, - Version: info.Version(), - Release: info.Pkgrel, - Epoch: info.Epoch, - LatestVersion: nil, - Homepage: info.URL, - Priority: info.Priority, - Architectures: info.Arch, - License: orEmptyArray(info.License), - Gives: toArchDistroStrings(info.Gives), - RuntimeDependencies: toArchDistroStrings(info.Depends), - CheckDependencies: toArchDistroStrings(info.CheckDepends), - BuildDependencies: toArchDistroStrings(info.MakeDepends), - OptionalDependencies: toArchDistroStrings(info.OptDepends), - PacstallDependencies: toArchDistroStrings(info.Pacdeps), - CheckConflicts: toArchDistroStrings(info.CheckConflicts), - BuildConflicts: toArchDistroStrings(info.MakeConflicts), - Conflicts: toArchDistroStrings(info.Conflicts), - Provides: toArchDistroStrings(info.Provides), - Breaks: toArchDistroStrings(info.Breaks), - Replaces: toArchDistroStrings(info.Replaces), - Enhances: toArchDistroStrings(info.Enhances), - Recommends: toArchDistroStrings(info.Recommends), - Suggests: toArchDistroStrings(info.Suggests), - Mask: orEmptyArray(info.Mask), - Compatible: orEmptyArray(info.Compatible), - Incompatible: orEmptyArray(info.Incompatible), - Maintainers: info.Maintainer, - Source: toSourceStrings(info.Source), - NoExtract: orEmptyArray(info.NoExtract), - NoSubmodules: orEmptyArray(info.NoSubmodules), - Md5Sums: toArchDistroStrings(info.MD5Sums), - Sha1Sums: toArchDistroStrings(info.SHA1Sums), - Sha224Sums: toArchDistroStrings(info.SHA224Sums), - Sha256Sums: toArchDistroStrings(info.SHA256Sums), - Sha384Sums: toArchDistroStrings(info.SHA384Sums), - Sha512Sums: toArchDistroStrings(info.SHA512Sums), - Backup: orEmptyArray(info.Backup), - Repology: info.Repology, - RequiredBy: []string{}, - UpdateStatus: UpdateStatus.Unknown, +func FromSrcInfo(info srcinfo.Srcinfo) []*Script { + var scripts []*Script + if len(info.Packages) > 1 { + scripts = append(scripts, &Script{ + PackageName: info.Pkgbase, + PrettyName: "", + PackageBase: info.Pkgbase, + BaseIndex: 0, + BaseTotal: len(info.Packages), + Description: info.Pkgdesc, + Version: info.Version(), + SourceVersion: info.Pkgver, + Release: info.Pkgrel, + Epoch: info.Epoch, + LatestVersion: nil, + Homepage: info.URL, + Priority: info.Priority, + Architectures: info.Arch, + License: orEmptyArray(info.License), + Gives: toArchDistroStrings(info.Gives), + RuntimeDependencies: toArchDistroStrings(info.Depends), + CheckDependencies: toArchDistroStrings(info.CheckDepends), + BuildDependencies: toArchDistroStrings(info.MakeDepends), + OptionalDependencies: toArchDistroStrings(info.OptDepends), + PacstallDependencies: toArchDistroStrings(info.Pacdeps), + CheckConflicts: toArchDistroStrings(info.CheckConflicts), + BuildConflicts: toArchDistroStrings(info.MakeConflicts), + Conflicts: toArchDistroStrings(info.Conflicts), + Provides: toArchDistroStrings(info.Provides), + Breaks: toArchDistroStrings(info.Breaks), + Replaces: toArchDistroStrings(info.Replaces), + Enhances: toArchDistroStrings(info.Enhances), + Recommends: toArchDistroStrings(info.Recommends), + Suggests: toArchDistroStrings(info.Suggests), + Mask: orEmptyArray(info.Mask), + Compatible: orEmptyArray(info.Compatible), + Incompatible: orEmptyArray(info.Incompatible), + Maintainers: info.Maintainer, + Source: toSourceStrings(info.Source), + NoExtract: orEmptyArray(info.NoExtract), + NoSubmodules: orEmptyArray(info.NoSubmodules), + Md5Sums: toArchDistroStrings(info.MD5Sums), + Sha1Sums: toArchDistroStrings(info.SHA1Sums), + Sha224Sums: toArchDistroStrings(info.SHA224Sums), + Sha256Sums: toArchDistroStrings(info.SHA256Sums), + Sha384Sums: toArchDistroStrings(info.SHA384Sums), + Sha512Sums: toArchDistroStrings(info.SHA512Sums), + Backup: orEmptyArray(info.Backup), + Repology: info.Repology, + RequiredBy: []string{}, + UpdateStatus: UpdateStatus.Unknown, + }) + } + for i, pkg := range info.Packages { + scripts = append(scripts, &Script{ + PackageName: pkg.Pkgname, + PrettyName: "", + PackageBase: info.Pkgbase, + BaseIndex: i + 1, + BaseTotal: len(info.Packages), + Description: fallback[string, string](pkg.Pkgdesc, info.Pkgdesc, nil), + Version: info.Version(), + SourceVersion: info.Pkgver, + Release: info.Pkgrel, + Epoch: info.Epoch, + LatestVersion: nil, + Homepage: info.URL, + Priority: fallback[string, string](pkg.Priority, info.Priority, nil), + Architectures: info.Arch, + License: fallback(pkg.License, info.License, orEmptyArray), + Gives: fallback(pkg.Gives, info.Gives, toArchDistroStrings), + RuntimeDependencies: fallback(pkg.Depends, info.Depends, toArchDistroStrings), + CheckDependencies: fallback(pkg.CheckDepends, info.CheckDepends, toArchDistroStrings), + BuildDependencies: toArchDistroStrings(info.MakeDepends), + OptionalDependencies: fallback(pkg.OptDepends, info.OptDepends, toArchDistroStrings), + PacstallDependencies: fallback(pkg.Pacdeps, info.Pacdeps, toArchDistroStrings), + CheckConflicts: fallback(pkg.CheckConflicts, info.CheckConflicts, toArchDistroStrings), + BuildConflicts: toArchDistroStrings(info.MakeConflicts), + Conflicts: fallback(pkg.Conflicts, info.Conflicts, toArchDistroStrings), + Provides: fallback(pkg.Provides, info.Provides, toArchDistroStrings), + Breaks: fallback(pkg.Breaks, info.Breaks, toArchDistroStrings), + Replaces: fallback(pkg.Replaces, info.Replaces, toArchDistroStrings), + Enhances: fallback(pkg.Enhances, info.Enhances, toArchDistroStrings), + Recommends: fallback(pkg.Recommends, info.Recommends, toArchDistroStrings), + Suggests: fallback(pkg.Suggests, info.Suggests, toArchDistroStrings), + Mask: orEmptyArray(info.Mask), + Compatible: orEmptyArray(info.Compatible), + Incompatible: orEmptyArray(info.Incompatible), + Maintainers: info.Maintainer, + Source: toSourceStrings(info.Source), + NoExtract: orEmptyArray(info.NoExtract), + NoSubmodules: orEmptyArray(info.NoSubmodules), + Md5Sums: toArchDistroStrings(info.MD5Sums), + Sha1Sums: toArchDistroStrings(info.SHA1Sums), + Sha224Sums: toArchDistroStrings(info.SHA224Sums), + Sha256Sums: toArchDistroStrings(info.SHA256Sums), + Sha384Sums: toArchDistroStrings(info.SHA384Sums), + Sha512Sums: toArchDistroStrings(info.SHA512Sums), + Backup: fallback(pkg.Backup, info.Backup, orEmptyArray), + Repology: fallback(pkg.Repology, info.Repology, orEmptyArray), + RequiredBy: []string{}, + UpdateStatus: UpdateStatus.Unknown, + }) + } + + return scripts +} + +func fallback[T any, R any](pkgValue, infoValue T, transform func(T) R) R { + if isNonEmpty(pkgValue) { + if transform != nil { + return transform(pkgValue) + } + return any(pkgValue).(R) + } + if transform != nil { + return transform(infoValue) + } + return any(infoValue).(R) +} + +func isNonEmpty[T any](value T) bool { + switch v := any(value).(type) { + case string: + return v != "" + case []string: + return len(v) > 0 + case []srcinfo.ArchDistroString: + return len(v) > 0 + default: + return true } }
{t('packageDetails.requiredByModal.name')}