diff --git a/Dockerfile b/Dockerfile index 18af1834..9d06d9fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,7 +32,7 @@ COPY --from=client /root/client/dist/ /root/client/dist/ COPY --from=server /root/server/dist/ /root/server/dist/ COPY ./Makefile ./Makefile -RUN apt update && apt install make git -y +RUN apt update && apt install make git jo jq -y RUN make dist \ && rm -rf server client diff --git a/client/src/components/package-details/MinimalPackageTable.tsx b/client/src/components/package-details/MinimalPackageTable.tsx index 18d65955..bc91289e 100644 --- a/client/src/components/package-details/MinimalPackageTable.tsx +++ b/client/src/components/package-details/MinimalPackageTable.tsx @@ -22,8 +22,11 @@ const MinimalPackageTable: FC<{ packages: (PackageInfo | string)[] }> = ({ {packages.map((pkg, i) => ( ))} diff --git a/client/src/components/package-details/PackageDetailsHeader.tsx b/client/src/components/package-details/PackageDetailsHeader.tsx index bd8e98c1..84a5074e 100644 --- a/client/src/components/package-details/PackageDetailsHeader.tsx +++ b/client/src/components/package-details/PackageDetailsHeader.tsx @@ -11,7 +11,7 @@ const PackageDetailsHeader: FC<{ data: PackageInfo; isMobile: boolean }> = ( <> - {data.name} + {data.packageName} {!isMobile && } diff --git a/client/src/components/package-details/PackageDetailsMaintainer.tsx b/client/src/components/package-details/PackageDetailsMaintainer.tsx index 52e70839..d1bd5540 100644 --- a/client/src/components/package-details/PackageDetailsMaintainer.tsx +++ b/client/src/components/package-details/PackageDetailsMaintainer.tsx @@ -1,22 +1,24 @@ import { EmailIcon } from '@chakra-ui/icons' import { Icon, Link, Tooltip } from '@chakra-ui/react' -import { FC } from 'react' +import { FC, Fragment } from 'react' import { useTranslation } from 'react-i18next' -const PackageDetailsMaintainer: FC<{ text: string }> = ({ text }) => { +const PackageDetailsMaintainer: FC<{ text: string[] }> = ({ + text: maintainers, +}) => { const { t } = useTranslation() if ( - !text || - text === '-' || - text.toLowerCase() === 'orphan' || - text.toLowerCase() === 'orphaned' + !maintainers?.length || + maintainers[0] === '-' || + maintainers[0].toLowerCase() === 'orphan' || + maintainers[0].toLowerCase() === 'orphaned' ) { return <>{t('packageDetails.orphaned')} } - if (!['<', '>', '@'].every(symbol => text.includes(symbol))) { - return <>{text} + if (!['<', '>', '@'].every(symbol => maintainers.join().includes(symbol))) { + return <>{maintainers.join(', ')} } const shortenName = (name: string, splitBy: string): string => @@ -28,33 +30,53 @@ const PackageDetailsMaintainer: FC<{ text: string }> = ({ text }) => { '', ) - let name = text.split('<')[0].trim() - if (name.length > 15) { - if (name.includes(' ')) { - name = shortenName(name, ' ') - } else if (name.includes('-')) { - name = shortenName(name, '-') - } else { - name = name.substring(0, 12) + '..' + const maintainerInfos = maintainers.map(maintainer => { + let name = maintainer.split('<')[0].trim() + if (name.length > 15) { + if (name.includes(' ')) { + name = shortenName(name, ' ') + } else if (name.includes('-')) { + name = shortenName(name, '-') + } else { + name = name.substring(0, 12) + '..' + } } - } - const fullEmail = text.split('<')[1].split('>')[0].trim() - const shortEmail = - fullEmail.split('@')[0].length > 15 - ? fullEmail.split('@')[0].substring(0, 13) + - '[..]@' + - fullEmail.split('@')[1] - : fullEmail + const fullEmail = maintainer.split('<')[1]?.split('>')[0]?.trim() + return { + name, + fullEmail, + shortEmail: + fullEmail?.split('@')[0]?.length > 15 + ? fullEmail.split('@')[0]?.substring(0, 13) + + '[..]@' + + fullEmail.split('@')[1] + : fullEmail, + } + }) return ( <> - {name}, - - - {shortEmail} - - + {maintainerInfos.map((maintainer, idx) => ( + + {maintainer.name} + {maintainer.fullEmail && ( + + + {maintainer.shortEmail}{' '} + {' '} + + + )} +
+
+ ))} ) } diff --git a/client/src/components/package-details/PackageDetailsPage.tsx b/client/src/components/package-details/PackageDetailsPage.tsx index 7383fa7a..9349b815 100644 --- a/client/src/components/package-details/PackageDetailsPage.tsx +++ b/client/src/components/package-details/PackageDetailsPage.tsx @@ -25,15 +25,19 @@ const PackageDetailsPage: FC = ({ }) => ( <> - {data.name} - Pacstall + {data.packageName} - Pacstall - + @@ -48,13 +52,16 @@ const PackageDetailsPage: FC = ({ requiredByModal={requiredByModal} /> - - + + ) diff --git a/client/src/components/package-details/PackageDetailsTable.tsx b/client/src/components/package-details/PackageDetailsTable.tsx index 93fdf2e8..3701a4a4 100644 --- a/client/src/components/package-details/PackageDetailsTable.tsx +++ b/client/src/components/package-details/PackageDetailsTable.tsx @@ -47,13 +47,13 @@ const PackageDetailsTable: FC<{ - {data.name} + {data.packageName} @@ -61,7 +61,7 @@ const PackageDetailsTable: FC<{ - + @@ -114,7 +114,7 @@ const PackageDetailsTable: FC<{ {t('packageDetails.openInGithub')}{' '} {packages.map(pkg => ( diff --git a/client/src/components/packages/PackageTableRow.tsx b/client/src/components/packages/PackageTableRow.tsx index 56a97640..3f71d997 100644 --- a/client/src/components/packages/PackageTableRow.tsx +++ b/client/src/components/packages/PackageTableRow.tsx @@ -21,7 +21,11 @@ const PackageTableRow: FC<{ pkg: PackageInfo; disabled?: boolean }> = ({ const { t } = useTranslation() return ( - + @@ -71,7 +79,7 @@ const PackageTableRow: FC<{ pkg: PackageInfo; disabled?: boolean }> = ({ > = ({ display={useBreakpointValue({ base: 'none', md: 'table-cell' })} > - + diff --git a/client/src/hooks/useRandomPackage.ts b/client/src/hooks/useRandomPackage.ts index 30aada8e..dcd434c7 100644 --- a/client/src/hooks/useRandomPackage.ts +++ b/client/src/hooks/useRandomPackage.ts @@ -8,8 +8,7 @@ const pickRandomPackage = (packages: PackageInfo[]) => { const idx = Math.floor(Math.random() * packages.length) return ( packages[idx].packageName || - packages[idx].name.split('-').slice(0, -1).join('-') || - packages[idx].name + packages[idx].packageName.split('-').slice(0, -1).join('-') ) } diff --git a/client/src/types/package-info.ts b/client/src/types/package-info.ts index e94a8a5f..9309379e 100644 --- a/client/src/types/package-info.ts +++ b/client/src/types/package-info.ts @@ -1,16 +1,15 @@ export default interface PackageInfo { - name: string version: string packageName: string - maintainer: string + maintainers: string[] description: string - url: string + source: string[] runtimeDependencies: string[] buildDependencies: string[] optionalDependencies: string[] - breaks: string[] + conflicts: string[] gives: string - replace: string[] + replaces: string[] hash?: string ppa: string[] pacstallDependencies: string[] diff --git a/client/tsconfig.json b/client/tsconfig.json index 16318bd6..78809817 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -15,8 +15,8 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, - "strictNullChecks": false, + "strictNullChecks": false }, "include": ["**/*.tsx", "**/*.ts", "**/*.css"], - "exclude": ["node_modules"], + "exclude": ["node_modules"] } diff --git a/docker-compose.yml b/docker-compose.yml index 7cae65c7..909d2f75 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,6 +40,7 @@ services: PACSTALL_MATOMO_ENABLED: "true" MATOMO_DOMAIN: "http://matomo" MATOMO_SITE_ID: "1" + PACSTALL_PROGRAMS_GIT_BRANCH: "master" matomo: image: matomo diff --git a/server/Makefile b/server/Makefile index c3bc07a5..c80a59b7 100644 --- a/server/Makefile +++ b/server/Makefile @@ -25,7 +25,23 @@ test: PACSTALL_DISCORD_TAGS="" \ PACSTALL_MATOMO_ENABLED="false" \ PACSTALL_REPOLOGY_ENABLED="false" \ - GO_ENV=test go test -v ./... + PACSTALL_PROGRAMS_GIT_BRANCH="master" \ + GO_ENV=test go test -v types/pac/parser/parse_test.go + +test_internal: + PACSTALL_DATABASE_HOST=localhost \ + PACSTALL_DATABASE_PORT=3306 \ + PACSTALL_DATABASE_USER=root \ + PACSTALL_DATABASE_PASSWORD=changeme \ + PACSTALL_DATABASE_NAME=pacstall \ + PACSTALL_DISCORD_ENABLED=false \ + PACSTALL_DISCORD_TOKEN="" \ + PACSTALL_DISCORD_CHANNEL_ID="" \ + PACSTALL_DISCORD_TAGS="" \ + PACSTALL_MATOMO_ENABLED="false" \ + PACSTALL_REPOLOGY_ENABLED="false" \ + PACSTALL_PROGRAMS_GIT_BRANCH="master" \ + GO_ENV=test go test -v types/pac/parser/pacsh/internal/git_version_test.go run: (cd .. && docker compose up -d mariadb) @@ -41,13 +57,13 @@ run: PACSTALL_DISCORD_TAGS="" \ PACSTALL_MATOMO_ENABLED="false" \ PACSTALL_REPOLOGY_ENABLED="false" \ + PACSTALL_PROGRAMS_GIT_BRANCH="master" \ go run bin/webserver/main.go dist/webserver: $(shell find . -not \( -path ./tmp -prune \) -not \( -path ./dist -prune \) -type f) CGO_ENABLED=0 go build -o dist/webserver -ldflags "${LDFLAGS}" bin/webserver/main.go clean: [ -d ./dist ] && rm -r dist || : -test: - go test -v ./... + fmt: go fmt ./... diff --git a/server/config/env.go b/server/config/env.go index c6e9c5cc..a70e0865 100644 --- a/server/config/env.go +++ b/server/config/env.go @@ -13,6 +13,12 @@ var Discord = struct { Tags: getEnvString("PACSTALL_DISCORD_TAGS"), } +var PacstallPrograms = struct { + Branch string +}{ + Branch: getEnvString("PACSTALL_PROGRAMS_GIT_BRANCH"), +} + // Configuration for the database var Database = struct { Host string diff --git a/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.pacscript b/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.pacscript new file mode 100644 index 00000000..74d35f2b --- /dev/null +++ b/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.pacscript @@ -0,0 +1,35 @@ +# __ __________ ______ +# / \ / \_____ \ / __ \ +# \ \/\/ // ____/ > < +# \ // \/ -- \ +# \__/\ / \_______ \______ / +# \/ \/ \/ +maintainer=("wizard-28 ") + +pkgname="clipboard-bin" +gives="clipboard" +pkgver="0.9.0.1" +pkgdesc="Cut, copy, and paste anything in your terminal" +gives="${gives}" +conflicts=("${gives}" "${gives}-git" "${gives}-deb" "${gives}-app") +arch=("amd64" "arm64" "armhf" "ppc64el" "riscv64") +repology=("project: clipboard") +source=("https://github.com/Slackadays/Clipboard/releases/download/${pkgver}/${gives}-linux-${CARCH}.zip") +sha256sums_amd64=("5b90cd7299c1c0d679cfe8c1bd4e89e7fd70ebede2890d90a6f1da98a90e922b") +sha256sums_arm64=("07493b5e9954585160fc54314e23e4897652f06594f6ec7ceba66b32d7f72b82") +sha256sums_armhf=("7654d6f5176e554ed86d84f16924b2ec3d7a7e0000f24a43ee6772397b986dea") +sha256sums_ppc64el=("a7c2c689a777d57fe6638a469c408753d1b4d5d61c8fecd141a4781f54a24e7a") +sha256sums_riscv64=("e92f2c4eeeefd093d25f91f186c1c3ac572ea254369fe7028928246d431407c8") + + +package() { + cd "${_archive}" + if [[ ${CARCH} == "amd64" ]]; then + sudo install -Dm 755 "lib/libcbwayland.so" "${pkgdir}/usr/lib/libcbwayland.so" + fi + sudo install -Dm 755 "lib/libcbx11.so" "${pkgdir}/usr/lib/libcbx11.so" + + sudo install -Dm 755 "bin/cb" "${pkgdir}/usr/bin/cb" +} + +# vim:set ft=sh ts=2 sw=2 et: diff --git a/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.snapshot.json b/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.snapshot.json new file mode 100644 index 00000000..d5af1e11 --- /dev/null +++ b/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.snapshot.json @@ -0,0 +1,35 @@ +{ + "prettyName": "Clipboard", + "version": "0.9.0.1", + "latestVersion": null, + "packageName": "clipboard-bin", + "maintainers": [ + "wizard-28 \u003cwiz28@pm.me\u003e" + ], + "description": "Cut, copy, and paste anything in your terminal", + "source": [ + "https://github.com/Slackadays/Clipboard/releases/download/0.9.0.1/clipboard-linux-amd64.zip" + ], + "runtimeDependencies": [], + "buildDependencies": [], + "optionalDependencies": [], + "conflicts": [ + "clipboard", + "clipboard-git", + "clipboard-deb", + "clipboard-app" + ], + "breaks": [], + "gives": "clipboard", + "replaces": [], + "hash": null, + "ppa": [], + "pacstallDependencies": [], + "patch": [], + "repology": [ + "project: clipboard" + ], + "requiredBy": [], + "lastUpdatedAt": "0001-01-01T00:00:00Z", + "updateStatus": -1 +} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.pacscript b/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.pacscript new file mode 100644 index 00000000..c75b83d7 --- /dev/null +++ b/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.pacscript @@ -0,0 +1,38 @@ +# __ __________ ______ +# / \ / \_____ \ / __ \ +# \ \/\/ // ____/ > < +# \ // \/ -- \ +# \__/\ / \_______ \______ / +# \/ \/ \/ +maintainer=("wizard-28 ") + +pkgname="clipboard-bin" +gives="clipboard" +pkgver="0.9.0.1" +pkgdesc="Cut, copy, and paste anything in your terminal" +gives="${gives}" +conflicts=("${gives}" "${gives}-git" "${gives}-deb" "${gives}-app") +arch=("amd64" "arm64" "armhf" "ppc64el" "riscv64") +repology=("project: clipboard") +optdepends=("gnome-disk-utility" + "epiphany-browser: Default browser CMD if not set" + "gnome-control-center: Default wifi CMD if not set") +source=("https://github.com/Slackadays/Clipboard/releases/download/${pkgver}/${gives}-linux-${CARCH}.zip") +sha256sums_amd64=("5b90cd7299c1c0d679cfe8c1bd4e89e7fd70ebede2890d90a6f1da98a90e922b") +sha256sums_arm64=("07493b5e9954585160fc54314e23e4897652f06594f6ec7ceba66b32d7f72b82") +sha256sums_armhf=("7654d6f5176e554ed86d84f16924b2ec3d7a7e0000f24a43ee6772397b986dea") +sha256sums_ppc64el=("a7c2c689a777d57fe6638a469c408753d1b4d5d61c8fecd141a4781f54a24e7a") +sha256sums_riscv64=("e92f2c4eeeefd093d25f91f186c1c3ac572ea254369fe7028928246d431407c8") + + +package() { + cd "${_archive}" + if [[ ${CARCH} == "amd64" ]]; then + sudo install -Dm 755 "lib/libcbwayland.so" "${pkgdir}/usr/lib/libcbwayland.so" + fi + sudo install -Dm 755 "lib/libcbx11.so" "${pkgdir}/usr/lib/libcbx11.so" + + sudo install -Dm 755 "bin/cb" "${pkgdir}/usr/bin/cb" +} + +# vim:set ft=sh ts=2 sw=2 et: diff --git a/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.snapshot.json b/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.snapshot.json new file mode 100644 index 00000000..7e1c2111 --- /dev/null +++ b/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.snapshot.json @@ -0,0 +1,39 @@ +{ + "prettyName": "Clipboard", + "version": "0.9.0.1", + "latestVersion": null, + "packageName": "clipboard-bin", + "maintainers": [ + "wizard-28 \u003cwiz28@pm.me\u003e" + ], + "description": "Cut, copy, and paste anything in your terminal", + "source": [ + "https://github.com/Slackadays/Clipboard/releases/download/0.9.0.1/clipboard-linux-amd64.zip" + ], + "runtimeDependencies": [], + "buildDependencies": [], + "optionalDependencies": [ + "gnome-disk-utility", + "epiphany-browser: Default browser CMD if not set", + "gnome-control-center: Default wifi CMD if not set" + ], + "conflicts": [ + "clipboard", + "clipboard-git", + "clipboard-deb", + "clipboard-app" + ], + "breaks": [], + "gives": "clipboard", + "replaces": [], + "hash": null, + "ppa": [], + "pacstallDependencies": [], + "patch": [], + "repology": [ + "project: clipboard" + ], + "requiredBy": [], + "lastUpdatedAt": "0001-01-01T00:00:00Z", + "updateStatus": -1 +} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/font-downloader/font-downloader.pacscript b/server/fixtures/test-programs/packages/font-downloader/font-downloader.pacscript new file mode 100644 index 00000000..856042f5 --- /dev/null +++ b/server/fixtures/test-programs/packages/font-downloader/font-downloader.pacscript @@ -0,0 +1,21 @@ +pkgname="font-downloader" +gives="font-downloader" +pkgver="10.0.0" +makedepends=("meson" "libhandy-1-dev" "gettext") +# HACK: https://github.com/pacstall/pacstall/issues/727 +depends=("python3-gi" "libhandy-1-dev") +source=("https://github.com/GustavoPeredo/Font-Downloader/archive/refs/tags/v${pkgver}.zip") +repology=("project: fontdownloader") +pkgdesc="Install fonts from online sources" +sha256sums=("eeafd4ac9cb0d47fd0c1512e07805d0f7a639cdbbc688647249eaee8d1753e23") +maintainer=("সৌম্যদীপ ") + +build() { + cd "${_archive}" + meson --prefix=/usr build + ninja -C build +} +package() { + cd "${_archive}" + DESTDIR="${pkgdir}" ninja -C build install +} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/font-downloader/font-downloader.snapshot.json b/server/fixtures/test-programs/packages/font-downloader/font-downloader.snapshot.json new file mode 100644 index 00000000..dad185e4 --- /dev/null +++ b/server/fixtures/test-programs/packages/font-downloader/font-downloader.snapshot.json @@ -0,0 +1,37 @@ +{ + "prettyName": "Font Downloader", + "version": "10.0.0", + "latestVersion": null, + "packageName": "font-downloader", + "maintainers": [ + "সৌম্যদীপ \u003csoumyadeepghosh2004@zohomail.in\u003e" + ], + "description": "Install fonts from online sources", + "source": [ + "https://github.com/GustavoPeredo/Font-Downloader/archive/refs/tags/v10.0.0.zip" + ], + "runtimeDependencies": [ + "python3-gi", + "libhandy-1-dev" + ], + "buildDependencies": [ + "meson", + "libhandy-1-dev", + "gettext" + ], + "optionalDependencies": [], + "conflicts": [], + "breaks": [], + "gives": "font-downloader", + "replaces": [], + "hash": null, + "ppa": [], + "pacstallDependencies": [], + "patch": [], + "repology": [ + "project: fontdownloader" + ], + "requiredBy": [], + "lastUpdatedAt": "0001-01-01T00:00:00Z", + "updateStatus": -1 +} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.pacscript b/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.pacscript new file mode 100644 index 00000000..fab6124e --- /dev/null +++ b/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.pacscript @@ -0,0 +1,40 @@ +pkgname="rhino-setup-git" +source=("https://github.com/rhino-linux/rhino-setup.git") +pkgver="2023.3" +pkgrel="2" +makedepends=("libgtk-4-dev" "libadwaita-1-dev" "gettext" "desktop-file-utils" "rustc" "cargo" "meson" "ninja-build") +depends=("libgtk-4-dev" "libadwaita-1-dev" "gettext" "desktop-file-utils") +gives="rhino-setup" +replaces="${gives}-bin" +pkgdesc="Rhino Linux Setup Prompt" +maintainer=("Oren Klopfer ") +incompatible=("debian:*") + +build() { + cd "${_archive}" + sudo meson build +} + +package() { + cd "${_archive}" + sudo DESTDIR="${pkgdir}" ninja -C build install +} + +post_install() { + for i in "${homedir}" "/etc/skel"; do + if ! [[ -d "${i}/.config/autostart" ]]; then + mkdir -p "${i}/.config/autostart" + fi + if ! [[ -f "${i}/.config/autostart/rhino-setup.desktop" ]]; then + sudo ln -sf "/usr/local/share/applications/org.rhinolinux.RhinoSetup.desktop" "${i}/.config/autostart/rhino-setup.desktop" + fi + done +} + +post_remove() { + for i in "${homedir}" "/etc/skel"; do + if [[ -L "${i}/.config/autostart/rhino-setup.desktop" ]]; then + sudo rm -f "${i}/.config/autostart/rhino-setup.desktop" + fi + done +} diff --git a/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.snapshot.json b/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.snapshot.json new file mode 100644 index 00000000..0f698d94 --- /dev/null +++ b/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.snapshot.json @@ -0,0 +1,44 @@ +{ + "prettyName": "Rhino Setup", + "version": "2023.3", + "latestVersion": null, + "packageName": "rhino-setup-git", + "maintainers": [ + "Oren Klopfer \u003coren@taumoda.com\u003e" + ], + "description": "Rhino Linux Setup Prompt", + "source": [ + "https://github.com/rhino-linux/rhino-setup.git" + ], + "runtimeDependencies": [ + "libgtk-4-dev", + "libadwaita-1-dev", + "gettext", + "desktop-file-utils" + ], + "buildDependencies": [ + "libgtk-4-dev", + "libadwaita-1-dev", + "gettext", + "desktop-file-utils", + "rustc", + "cargo", + "meson", + "ninja-build" + ], + "optionalDependencies": [], + "conflicts": [], + "breaks": [], + "gives": "rhino-setup", + "replaces": [ + "rhino-setup-bin" + ], + "hash": null, + "ppa": [], + "pacstallDependencies": [], + "patch": [], + "repology": [], + "requiredBy": [], + "lastUpdatedAt": "0001-01-01T00:00:00Z", + "updateStatus": -1 +} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.pacscript b/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.pacscript index d6fcb81f..6ef871b4 100644 --- a/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.pacscript +++ b/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.pacscript @@ -1,9 +1,8 @@ -name="sample-valid-deb" -pkgname="sample-valid" +pkgname="sample-valid-deb" gives="sample-valid" repology=("project: sample-valid") pkgver="1.0.0" -url="https://example.com" +source="https://example.com" pkgdesc="Sample description" hash="10101010" arch=('amd64') @@ -15,6 +14,7 @@ ppa=("ppa1" "ppa2") patch=("patch1" "patch2") provides=("provides1" "provides2") incompatible=("incompatible1" "incompatible2") -maintainer="pacstall " +maintainer=("1234 ") breaks=("breaks1" "breaks2") -replace=("replaces1" "replaces2") +conflicts=("conflicts1" "conflicts2") +replaces=("replaces1" "replaces2") diff --git a/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.snapshot.json b/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.snapshot.json new file mode 100644 index 00000000..a660408a --- /dev/null +++ b/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.snapshot.json @@ -0,0 +1,57 @@ +{ + "prettyName": "Sample Valid", + "version": "1.0.0", + "latestVersion": null, + "packageName": "sample-valid-deb", + "maintainers": [ + "1234 \u003ctest@pacstall.dev\u003e" + ], + "description": "Sample description", + "source": [ + "https://example.com" + ], + "runtimeDependencies": [ + "dep1", + "dep2" + ], + "buildDependencies": [ + "go", + "gcc" + ], + "optionalDependencies": [ + "opt1", + "opt2" + ], + "conflicts": [ + "conflicts1", + "conflicts2" + ], + "breaks": [ + "breaks1", + "breaks2" + ], + "gives": "sample-valid", + "replaces": [ + "replaces1", + "replaces2" + ], + "hash": "10101010", + "ppa": [ + "ppa1", + "ppa2" + ], + "pacstallDependencies": [ + "pacdep1", + "pacdep2" + ], + "patch": [ + "patch1", + "patch2" + ], + "repology": [ + "project: sample-valid" + ], + "requiredBy": [], + "lastUpdatedAt": "0001-01-01T00:00:00Z", + "updateStatus": -1 +} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.pacscript b/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.pacscript index 6239dede..b75845f6 100644 --- a/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.pacscript +++ b/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.pacscript @@ -1,8 +1,7 @@ -name="sample-valid-deb" -pkgname="sample-valid" +pkgname="sample-valid-deb" gives="sample-valid" repology=("project: sample-valid") -url="https://example.com" +source=("https://example.com") pkgdesc="Sample description" hash="10101010" arch=('amd64') @@ -14,9 +13,10 @@ ppa=("ppa1" "ppa2") patch=("patch1" "patch2") provides=("provides1" "provides2") incompatible=("incompatible1" "incompatible2") -maintainer="pacstall " +maintainer=("pacstall ") breaks=("breaks1" "breaks2") -replace=("replaces1" "replaces2") +conflicts=("conflicts1" "conflicts2") +replaces=("replaces1" "replaces2") pkgver() { echo "1.2.3" diff --git a/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.snapshot.json b/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.snapshot.json new file mode 100644 index 00000000..eb11e970 --- /dev/null +++ b/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.snapshot.json @@ -0,0 +1,57 @@ +{ + "prettyName": "Sample Valid", + "version": "1.2.3", + "latestVersion": null, + "packageName": "sample-valid-deb", + "maintainers": [ + "pacstall \u003ctest@pacstall.dev\u003e" + ], + "description": "Sample description", + "source": [ + "https://example.com" + ], + "runtimeDependencies": [ + "dep1", + "dep2" + ], + "buildDependencies": [ + "go", + "gcc" + ], + "optionalDependencies": [ + "opt1", + "opt2" + ], + "conflicts": [ + "conflicts1", + "conflicts2" + ], + "breaks": [ + "breaks1", + "breaks2" + ], + "gives": "sample-valid", + "replaces": [ + "replaces1", + "replaces2" + ], + "hash": "10101010", + "ppa": [ + "ppa1", + "ppa2" + ], + "pacstallDependencies": [ + "pacdep1", + "pacdep2" + ], + "patch": [ + "patch1", + "patch2" + ], + "repology": [ + "project: sample-valid" + ], + "requiredBy": [], + "lastUpdatedAt": "0001-01-01T00:00:00Z", + "updateStatus": -1 +} \ No newline at end of file diff --git a/server/server/api/pacscripts/dependencies.go b/server/server/api/pacscripts/dependencies.go index 8f71ffdf..2aa2cc25 100644 --- a/server/server/api/pacscripts/dependencies.go +++ b/server/server/api/pacscripts/dependencies.go @@ -37,7 +37,7 @@ func GetPacscriptDependenciesHandle(w http.ResponseWriter, req *http.Request) { allPacscripts := pacstore.GetAll() pacpkg, err := array.FindBy(allPacscripts, func(s *pac.Script) bool { - return s.Name == name + return s.PackageName == name }) if err != nil { @@ -47,10 +47,10 @@ func GetPacscriptDependenciesHandle(w http.ResponseWriter, req *http.Request) { pacstallDependencies := make([]*pac.Script, 0) for _, pkg := range pacpkg.PacstallDependencies { - if found, err := array.FindBy(allPacscripts, func(pi *pac.Script) bool { return pkg == pi.Name }); err == nil { + if found, err := array.FindBy(allPacscripts, func(pi *pac.Script) bool { return pkg == pi.PackageName }); err == nil { pacstallDependencies = append(pacstallDependencies, found) } else { - log.Error("could not find pacstall dependency %s of package %s.\n", pkg, pacpkg.Name) + log.Error("could not find pacstall dependency %s of package %s.\n", pkg, pacpkg.PackageName) } } diff --git a/server/server/api/pacscripts/package.go b/server/server/api/pacscripts/package.go index e6d6ac09..c3fbfad8 100644 --- a/server/server/api/pacscripts/package.go +++ b/server/server/api/pacscripts/package.go @@ -24,7 +24,7 @@ func GetPacscriptHandle(w http.ResponseWriter, req *http.Request) { } pkg, err := array.FindBy(pacstore.GetAll(), func(s *pac.Script) bool { - return s.Name == name + return s.PackageName == name }) if err != nil { diff --git a/server/server/api/pacscripts/required_by.go b/server/server/api/pacscripts/required_by.go index ddbba5e0..ccf66a52 100644 --- a/server/server/api/pacscripts/required_by.go +++ b/server/server/api/pacscripts/required_by.go @@ -29,7 +29,7 @@ func GetPacscriptRequiredByHandle(w http.ResponseWriter, req *http.Request) { allPackages := pacstore.GetAll() found, err := array.FindBy(allPackages, func(p *pac.Script) bool { - return p.Name == name + return p.PackageName == name }) if err != nil { @@ -38,7 +38,7 @@ func GetPacscriptRequiredByHandle(w http.ResponseWriter, req *http.Request) { } requiredBy := array.Filter(allPackages, func(it *array.Iterator[*pac.Script]) bool { - return array.Contains(found.RequiredBy, array.Is(it.Value.Name)) + return array.Contains(found.RequiredBy, array.Is(it.Value.PackageName)) }) server.Json(w, requiredBy) diff --git a/server/server/api/repology/types.go b/server/server/api/repology/types.go index cd07872f..98d08fe1 100644 --- a/server/server/api/repology/types.go +++ b/server/server/api/repology/types.go @@ -19,7 +19,7 @@ type repologyPackage struct { Description string `json:"description"` Maintainer maintainerDetails `json:"maintainer"` Version string `json:"version"` - URL string `json:"url"` + URL *string `json:"url"` RecipeURL string `json:"recipeUrl"` PackageDetailsURL string `json:"packageDetailsUrl"` Type string `json:"type"` @@ -27,16 +27,21 @@ type repologyPackage struct { } func newRepologyPackage(p *pac.Script) repologyPackage { + var source *string = nil + if len(p.Source) > 0 { + source = &p.Source[0] + } + return repologyPackage{ - Name: p.Name, + Name: p.PackageName, VisibleName: p.PrettyName, Description: p.Description, Maintainer: getMaintainer(p), Version: p.Version, - URL: p.URL, + URL: source, Type: getType(p), - RecipeURL: fmt.Sprintf("https://raw.githubusercontent.com/pacstall/pacstall-programs/master/packages/%s/%s.%s", p.Name, p.Name, consts.PACSCRIPT_FILE_EXTENSION), - PackageDetailsURL: fmt.Sprintf("https://pacstall.dev/packages/%s", p.Name), + RecipeURL: fmt.Sprintf("https://raw.githubusercontent.com/pacstall/pacstall-programs/master/packages/%s/%s.%s", p.PackageName, p.PackageName, consts.PACSCRIPT_FILE_EXTENSION), + PackageDetailsURL: fmt.Sprintf("https://pacstall.dev/packages/%s", p.PackageName), Patches: p.Patch, } } @@ -49,13 +54,18 @@ var pacTypes = map[string]string{ } func getMaintainer(p *pac.Script) maintainerDetails { - if !strings.Contains(p.Maintainer, "<") { + maintainer := "" + if len(p.Maintainers) > 0 { + maintainer = p.Maintainers[0] + } + + if !strings.Contains(maintainer, "<") { return maintainerDetails{ - Name: &p.Maintainer, + Name: &maintainer, } } - parts := strings.Split(p.Maintainer, "<") + parts := strings.Split(maintainer, "<") name := strings.TrimSpace(parts[0]) email := strings.TrimSpace(strings.Replace(parts[1], ">", "", -1)) @@ -67,7 +77,7 @@ func getMaintainer(p *pac.Script) maintainerDetails { func getType(p *pac.Script) string { for suffix, kind := range pacTypes { - if strings.HasSuffix(p.Name, suffix) { + if strings.HasSuffix(p.PackageName, suffix) { return kind } } diff --git a/server/server/sitemap.go b/server/server/sitemap.go index 7690a118..bbabd82d 100644 --- a/server/server/sitemap.go +++ b/server/server/sitemap.go @@ -43,7 +43,7 @@ func generateDynamicSiteMap() []SitemapEntry { for idx, pkg := range packages { entries[idx] = SitemapEntry{ - Location: fmt.Sprintf("https://pacstall.dev/packages/%s/", pkg.Name), + Location: fmt.Sprintf("https://pacstall.dev/packages/%s/", pkg.PackageName), ChangeFrequency: "monthly", } } diff --git a/server/server/ssr/pacscript/package.go b/server/server/ssr/pacscript/package.go index a36b3532..57d656f6 100644 --- a/server/server/ssr/pacscript/package.go +++ b/server/server/ssr/pacscript/package.go @@ -3,6 +3,7 @@ package ssr import ( "fmt" "regexp" + "strings" "pacstall.dev/webserver/consts" r "pacstall.dev/webserver/server/ssr" @@ -18,7 +19,7 @@ func registerPacscriptSSRData() { name := groups[1] pkg, err := array.FindBy(pacstore.GetAll(), func(s *pac.Script) bool { - return s.Name == name + return s.PackageName == name }) if err != nil { @@ -26,7 +27,7 @@ func registerPacscriptSSRData() { } return r.IndexTemplateData{ - Title: fmt.Sprintf("%s - Pacstall", pkg.Name), + Title: fmt.Sprintf("%s - Pacstall", pkg.PackageName), Description: pkg.Description, Html: fmt.Sprintf(`

Pacstall - The AUR for Ubuntu

@@ -41,16 +42,15 @@ func registerPacscriptSSRData() {

Package: %s

Description: %s

-

Maintainer: %s

+

Maintainers: %s

Version: %s

-

URL

Source

Find similar packages here.

- `, pkg.Name, pkg.Name, pkg.Description, pkg.Maintainer, pkg.Version, pkg.URL, pkg.Name, pkg.Name, consts.PACSCRIPT_FILE_EXTENSION, pkg.PackageName), + `, pkg.PackageName, pkg.PackageName, pkg.Description, strings.Join(pkg.Maintainers, ", "), pkg.Version, pkg.PackageName, pkg.PackageName, consts.PACSCRIPT_FILE_EXTENSION, pkg.PackageName), } }, ) diff --git a/server/types/pac/pacstore/store.go b/server/types/pac/pacstore/store.go index c2947ea1..e38bf0fe 100644 --- a/server/types/pac/pacstore/store.go +++ b/server/types/pac/pacstore/store.go @@ -12,13 +12,17 @@ var loadedPacscripts []*pac.Script func FindByName(name string) (*pac.Script, error) { return array.FindBy(loadedPacscripts, func(p *pac.Script) bool { - return p.Name == name + return p.PackageName == name }) } func FindByMaintainer(maintainer string) (*pac.Script, error) { return array.FindBy(loadedPacscripts, func(p *pac.Script) bool { - return p.Maintainer == maintainer + _, err := array.FindBy(p.Maintainers, func(s string) bool { + return s == maintainer + }) + + return err != nil }) } diff --git a/server/types/pac/parser/git/lib.go b/server/types/pac/parser/git/lib.go index 56d2df55..0852cac7 100644 --- a/server/types/pac/parser/git/lib.go +++ b/server/types/pac/parser/git/lib.go @@ -3,11 +3,13 @@ package git import ( "os" "os/exec" + "strings" "github.com/joomcode/errorx" + "pacstall.dev/webserver/log" ) -func hardResetAndPull(path string) error { +func hardResetAndPull(path, branch string) error { cmd := exec.Command("git", "reset", "--hard", "HEAD") cmd.Dir = path if err := cmd.Run(); err != nil { @@ -33,20 +35,83 @@ func hardResetAndPull(path string) error { return err } + if err := checkoutBranch(path, branch); err != nil { + return err + } + + cmd = exec.Command("git", "pull") + cmd.Dir = path + if err := cmd.Run(); err != nil { + return err + } + return nil } -func clonePrograms(path, url string) error { +func clonePrograms(path, url, branch string) error { cmd := exec.Command("git", "clone", url, path) if err := cmd.Run(); err != nil { return errorx.Decorate(err, "failed to run git clone command") } + if err := checkoutBranch(path, branch); err != nil { + return err + } + + return nil +} + +func getCurrentBranch(path string) (string, error) { + cmd := exec.Command("git", "name-rev", "--name-only", "HEAD") + cmd.Dir = path + out, err := cmd.Output() + if err != nil { + return "", nil + } + + return strings.TrimSpace(string(out)), nil +} + +func checkoutBranch(path, branch string) error { + currentBranch, err := getCurrentBranch(path) + if err != nil { + return errorx.Decorate(err, "failed to read current branch name") + } + + if currentBranch != branch { + log.Warn("programs repository is on the wrong branch '%v'. checking out branch '%v'", currentBranch, branch) + } else { + log.Info("programs repository is using branch: %v", currentBranch) + return nil + } + + cmd := exec.Command("git", "checkout", branch) + cmd.Dir = path + out, err := cmd.Output() + if err != nil { + return errorx.Decorate(err, "failed to checkout git branch '%v'", branch) + } + + log.Info("got branch checkout output: %v", string(out)) + return nil } -func RefreshPrograms(path, url string) error { - if err := hardResetAndPull(path); err == nil { +// Returns the remote Git (short) commit hash +func GetRemoteCommitHash(url, branchOrTag string) (string, error) { + cmd := exec.Command("git", "ls-remote", url, branchOrTag) + + if bytes, err := cmd.Output(); err != nil { + return "", errorx.ExternalError.Wrap(err, "failed to fetch git commit hash from source '%v' branch/tag '%v'", url, branchOrTag) + } else if len(string(bytes)) < 8 { + return "", errorx.ExternalError.New("commit hash '%v' has less than 8 characters. source '%v' branch/tag '%v'", string(bytes), url, branchOrTag) + } else { + return strings.TrimSpace(string(bytes))[0:8], nil + } +} + +func RefreshPrograms(path, url, branch string) error { + if err := hardResetAndPull(path, branch); err == nil { return nil } @@ -54,7 +119,7 @@ func RefreshPrograms(path, url string) error { return errorx.Decorate(err, "failed to remove directory '%v'", path) } - if err := clonePrograms(path, url); err != nil { + if err := clonePrograms(path, url, branch); err != nil { return errorx.Decorate(err, "failed to clone repository '%v'", url) } diff --git a/server/types/pac/parser/last_updated.go b/server/types/pac/parser/last_updated.go index 026a3ea9..dfd9e3b8 100644 --- a/server/types/pac/parser/last_updated.go +++ b/server/types/pac/parser/last_updated.go @@ -11,6 +11,7 @@ import ( "github.com/joomcode/errorx" "pacstall.dev/webserver/config" "pacstall.dev/webserver/consts" + "pacstall.dev/webserver/log" "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" "pacstall.dev/webserver/types/pac/parser/pacsh" @@ -48,7 +49,7 @@ func getPackageLastUpdatedTuples() ([]packageLastUpdatedTuple, error) { lastUpdatedString := lines[i+1] // Unix time // Remove quotes - lastUpdatedString = lastUpdatedString[1 : len(lastUpdatedString) - 1] + lastUpdatedString = lastUpdatedString[1 : len(lastUpdatedString)-1] packageNameWithExtension := path.Base(packagePath) packageName := strings.TrimSuffix(packageNameWithExtension, "."+consts.PACSCRIPT_FILE_EXTENSION) @@ -83,21 +84,14 @@ func setLastUpdatedAt(packages []*pac.Script) error { return errorx.Decorate(err, "failed to get package last updated tuples") } - packages = array.Clone(packages) - packages = array.SortBy(packages, func(s1, s2 *pac.Script) bool { - return s1.Name < s2.Name - }) - - lastUpdatedTuples = array.SortBy(lastUpdatedTuples, func(t1, t2 packageLastUpdatedTuple) bool { - return t1.packageName < t2.packageName - }) - - if len(lastUpdatedTuples) != len(packages) { - return errorx.AssertionFailed.New("expected %v package last updated tuples but got %v", len(packages), len(lastUpdatedTuples)) - } - - for i := 0; i < len(packages); i++ { - packages[i].LastUpdatedAt = lastUpdatedTuples[i].lastUpdated + for _, pkg := range packages { + if tuple, err := array.FindBy(lastUpdatedTuples, func(tuple packageLastUpdatedTuple) bool { + return tuple.packageName == pkg.PackageName + }); err == nil { + pkg.LastUpdatedAt = tuple.lastUpdated + } else { + log.Warn("failed to set 'LastUpdatedAt' for package %#v. err: %+v", pkg, err) + } } return nil diff --git a/server/types/pac/parser/pacscript.go b/server/types/pac/parser/pacscript.go index 2c96dff6..616ff5b0 100644 --- a/server/types/pac/parser/pacscript.go +++ b/server/types/pac/parser/pacscript.go @@ -30,34 +30,25 @@ func buildCustomFormatScript(header []byte) []byte { // TODO: remove after `preinstall` gets implemented script := removeDebianCheck(string(header)) + "\n" - categoryToken := `++++` - subcategoryToken := `+ +++` - - script = script + "echo ''\n" - - for _, bashName := range pacsh.PacstallCVars { + script += "echo ''\n" + for _, bashName := range pacsh.PacscriptVars { // If the variable is a function, then we replace it with the output of the function script += fmt.Sprintf(` if [[ "$(declare -F -p %v)" ]]; then %v=$(%v) fi `, bashName, bashName, bashName) - script += fmt.Sprintf("echo \"%s $%v\"", categoryToken, bashName) + "\n" } - for _, bashName := range pacsh.PacstallCArrays { - script += fmt.Sprintf("echo \"%s $%v\"", categoryToken, bashName) + "\n" - } + script = script + "\njo -p -- " - mapsPartialScript := make([]string, 0) - for _, bashName := range pacsh.PacstallCMaps { - partial := "echo " + categoryToken + "\n" - - partial += fmt.Sprintf("printf '%s%%s\\n' \"${%v[@]}\"\n", subcategoryToken, bashName) - mapsPartialScript = append(mapsPartialScript, partial) + for _, bashName := range pacsh.PacscriptVars { + script += fmt.Sprintf("-s %v=\"$%v\" ", bashName, bashName) } - script += strings.Join(mapsPartialScript, "\n") + for _, bashName := range pacsh.PacscriptArrays { + script += fmt.Sprintf("%v=$(jo -a ${%v[@]}) ", bashName, bashName) + } return []byte(script) } @@ -70,8 +61,8 @@ func computeRequiredBy(script *pac.Script, scripts []*pac.Script) { script.RequiredBy = make([]string, 0) for _, otherScript := range scripts { otherScriptDependencies := array.Map(otherScript.PacstallDependencies, pickBeforeColon) - if array.Contains(otherScriptDependencies, array.Is(script.Name)) { - script.RequiredBy = append(script.RequiredBy, otherScript.Name) + if array.Contains(otherScriptDependencies, array.Is(script.PackageName)) { + script.RequiredBy = append(script.RequiredBy, otherScript.PackageName) } } } diff --git a/server/types/pac/parser/pacsh/exec_sh.go b/server/types/pac/parser/pacsh/exec_sh.go index 8731c9c0..e268df76 100644 --- a/server/types/pac/parser/pacsh/exec_sh.go +++ b/server/types/pac/parser/pacsh/exec_sh.go @@ -17,7 +17,9 @@ func execBash(cwd, filename string, content []byte) (stdout []byte, err error) { } defer removeFile(tmpPath) - stdout, err = execCommand("bash", tmpPath).Output() + command := execCommand("bash", tmpPath) + command.Env = append(command.Env, "CARCH=amd64") + stdout, err = command.Output() if err != nil { bytes, _ := os.ReadFile(tmpPath) log.Debug("Failed to execute '%v'. %v\n%v", tmpPath, err, string(bytes)) diff --git a/server/types/pac/parser/pacsh/internal/git_version.go b/server/types/pac/parser/pacsh/internal/git_version.go new file mode 100644 index 00000000..ef0ffa10 --- /dev/null +++ b/server/types/pac/parser/pacsh/internal/git_version.go @@ -0,0 +1,143 @@ +package internal + +import ( + "fmt" + "strings" + "time" + + "github.com/joomcode/errorx" + "pacstall.dev/webserver/types/pac/parser/git" + "pacstall.dev/webserver/types/pac/parser/parallelism/timeout" +) + +type GitSourceInfo struct { + Urls []string + Branch string + Tag string + Commit string +} + +type GitSources struct { + sources []string + getCommitHash func(url string, ref string) (string, error) +} + +func NewGitSources(sources []string) GitSources { + return GitSources{ + sources: sources, + getCommitHash: func(url, ref string) (string, error) { + version, err := timeout.Run(fmt.Sprintf("commit-hash/%v/%v", url, ref), func() (string, error) { + version, err := git.GetRemoteCommitHash(url, ref) + return version, err + }, 3*time.Second) + return version, err + }, + } +} + +func NewTestGitSources(sources []string) GitSources { + return GitSources{ + sources: sources, + getCommitHash: func(url, target string) (string, error) { + return "a0b1c2d3", nil + }, + } +} + +func ExtractGitSourceInformation(source string) GitSourceInfo { + sourceUrl := "" + if parts := strings.Split(source, "::"); len(parts) > 1 { + sourceUrl = parts[1] + } else { + sourceUrl = parts[0] + } + + commit := "" + tag := "" + branch := "" + if strings.Contains(sourceUrl, "#branch=") { + parts := strings.Split(sourceUrl, "#branch=") + sourceUrl = parts[0] + branch = parts[1] + } else if strings.Contains(sourceUrl, "#tag=") { + parts := strings.Split(sourceUrl, "#tag=") + sourceUrl = parts[0] + tag = parts[1] + } else if strings.Contains(sourceUrl, "#commit=") { + parts := strings.Split(sourceUrl, "#commit=") + sourceUrl = parts[0] + commit = parts[1] + } + + urls := []string{} + if strings.Contains(sourceUrl, "git+https://") { + httpsUrl := strings.ReplaceAll(sourceUrl, "git+https://", "https://") + gitUrl := strings.ReplaceAll(sourceUrl, "git+https://", "git://") + urls = append(urls, httpsUrl, gitUrl) + } else { + urls = append(urls, sourceUrl) + } + + return GitSourceInfo{ + Urls: urls, + Tag: tag, + Branch: branch, + Commit: commit, + } +} + +func (s GitSources) TryGetCommitHashFromAnySource(urls []string, ref string) (string, error) { + errors := []error{} + + for _, url := range urls { + out, err := s.getCommitHash(url, ref) + if err == nil { + return out, nil + } + + errors = append(errors, err) + } + + return "", errorx.DecorateMany("failed to get commit hash from any source", errors...) +} + +func (s GitSources) ParseGitPackageVersion() (string, error) { + if len([]string(s.sources)) == 0 { + // Nothing to parse + return "", nil + } + + primarySource := []string(s.sources)[0] + // Only keep the url part of "mycoolname::git+https://github.com/me/project.git#branch=coolfeature" + sourceInfo := ExtractGitSourceInformation(primarySource) + + var calculateCommit func() (string, error) = nil + + if sourceInfo.Commit != "" { + calculateCommit = func() (string, error) { + if len(sourceInfo.Commit) < 8 { + return "", errorx.AssertionFailed.New("expected commit hash '%v' to have more than 8 characters", sourceInfo.Commit) + } + + return sourceInfo.Commit[0:8], nil + } + } else if sourceInfo.Tag != "" { // the following if branches look similar and could be merged but let's keep them this way for now. + calculateCommit = func() (string, error) { + out, err := s.TryGetCommitHashFromAnySource(sourceInfo.Urls, sourceInfo.Tag) + return out, err + } + } else if sourceInfo.Branch != "" { + calculateCommit = func() (string, error) { + out, err := s.TryGetCommitHashFromAnySource(sourceInfo.Urls, sourceInfo.Branch) + return out, err + } + } else { + calculateCommit = func() (string, error) { + out, err := s.TryGetCommitHashFromAnySource(sourceInfo.Urls, "HEAD") + return out, err + } + } + + commitHash, err := calculateCommit() + return commitHash, err +} diff --git a/server/types/pac/parser/pacsh/internal/git_version_test.go b/server/types/pac/parser/pacsh/internal/git_version_test.go new file mode 100644 index 00000000..6e3d7f2f --- /dev/null +++ b/server/types/pac/parser/pacsh/internal/git_version_test.go @@ -0,0 +1,117 @@ +package internal_test + +import ( + "testing" + + "pacstall.dev/webserver/types/pac/parser/pacsh/internal" +) + +func assertGitSourceInfoEquals(t *testing.T, expected, actual internal.GitSourceInfo) { + t.Helper() + + if len(expected.Urls) != len(actual.Urls) { + t.Errorf("expected url %+v but got %+v", expected.Urls, actual.Urls) + } + + for idx, expectedUrl := range expected.Urls { + if expectedUrl != actual.Urls[idx] { + t.Errorf("expected url %+v but got %+v", expected.Urls, actual.Urls) + return + } + } + + if expected.Branch != actual.Branch { + t.Errorf("expected branch '%v' but got '%v'", expected.Branch, actual.Branch) + } + + if expected.Tag != actual.Tag { + t.Errorf("expected tag '%v' but got '%v'", expected.Tag, actual.Tag) + } + + if expected.Commit != actual.Commit { + t.Errorf("expected commit '%v' but got '%v'", expected.Commit, actual.Commit) + } +} + +func Test_ExtractGitSourceInformation_Simple(t *testing.T) { + actual := internal.ExtractGitSourceInformation("git://git.deluge-torrent.org/deluge.git") + expected := internal.GitSourceInfo{ + Urls: []string{"git://git.deluge-torrent.org/deluge.git"}, + } + + assertGitSourceInfoEquals(t, expected, actual) +} + +func Test_ExtractGitSourceInformation_Tag(t *testing.T) { + actual := internal.ExtractGitSourceInformation("git://git.deluge-torrent.org/deluge.git#tag=example_tag") + expected := internal.GitSourceInfo{ + Urls: []string{"git://git.deluge-torrent.org/deluge.git"}, + Tag: "example_tag", + } + + assertGitSourceInfoEquals(t, expected, actual) +} + +func Test_ExtractGitSourceInformation_Branch(t *testing.T) { + actual := internal.ExtractGitSourceInformation("git://git.deluge-torrent.org/deluge.git#branch=develop") + expected := internal.GitSourceInfo{ + Urls: []string{"git://git.deluge-torrent.org/deluge.git"}, + Branch: "develop", + } + + assertGitSourceInfoEquals(t, expected, actual) +} + +func Test_ExtractGitSourceInformation_Commit(t *testing.T) { + actual := internal.ExtractGitSourceInformation("git://git.deluge-torrent.org/deluge.git#commit=a0b1b2d3e4") + expected := internal.GitSourceInfo{ + Urls: []string{"git://git.deluge-torrent.org/deluge.git"}, + Commit: "a0b1b2d3e4", + } + + assertGitSourceInfoEquals(t, expected, actual) +} + +func Test_ExtractGitSourceInformation_GitPlusHttps(t *testing.T) { + actual := internal.ExtractGitSourceInformation("git+https://git.deluge-torrent.org/deluge.git#commit=a0b1b2d3e4") + expected := internal.GitSourceInfo{ + Urls: []string{"https://git.deluge-torrent.org/deluge.git", "git://git.deluge-torrent.org/deluge.git"}, + Commit: "a0b1b2d3e4", + } + + assertGitSourceInfoEquals(t, expected, actual) +} + +func Test_ExtractGitSourceInformation_WithNamePrefix(t *testing.T) { + actual := internal.ExtractGitSourceInformation("someName::git+https://git.deluge-torrent.org/deluge.git#commit=a0b1b2d3e4") + expected := internal.GitSourceInfo{ + Urls: []string{"https://git.deluge-torrent.org/deluge.git", "git://git.deluge-torrent.org/deluge.git"}, + Commit: "a0b1b2d3e4", + } + + assertGitSourceInfoEquals(t, expected, actual) +} + +func Test_ParseGitPackageVersion(t *testing.T) { + pkgver, err := internal.NewTestGitSources([]string{"git://git.deluge-torrent.org/deluge.git#commit=testing123"}).ParseGitPackageVersion() + if err != nil { + t.Error(err) + return + } + + if pkgver != "testing1" { + t.Errorf("expected 'testing1' but got '%v'", pkgver) + } +} + +func Test_ParseGitPackageVersion_NoCommit(t *testing.T) { + pkgver, err := internal.NewTestGitSources([]string{"git://git.deluge-torrent.org/deluge.git#branch=testing123"}).ParseGitPackageVersion() + if err != nil { + t.Error(err) + return + } + + if pkgver != "a0b1c2d3" { + t.Errorf("expected 'a0b1c2d3' but got '%v'", pkgver) + } +} diff --git a/server/types/pac/parser/pacsh/parse_pac_output.go b/server/types/pac/parser/pacsh/parse_pac_output.go index 47e09e01..d6d590c8 100644 --- a/server/types/pac/parser/pacsh/parse_pac_output.go +++ b/server/types/pac/parser/pacsh/parse_pac_output.go @@ -1,112 +1,178 @@ package pacsh import ( + "encoding/json" "strings" "github.com/joomcode/errorx" "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" + "pacstall.dev/webserver/types/pac/parser/pacsh/internal" ) var ParsePacOutput = parseOutput -var PacstallCVars []string = []string{"name", "pkgname", "maintainer", "pkgdesc", "url", "gives", "hash", "pkgver"} -var PacstallCArrays []string = []string{} -var PacstallCMaps []string = []string{"depends", "breaks", "replace", "makedepends", "optdepends", "pacdeps", "patch", "ppa", "repology"} - -const ( - nameIdx = iota - pkgnameIdx - maintainerIdx - descriptionIdx - urlIdx - givesIdx - hashIdx - versionIdx - dependsIdx - breaksIdx - replaceIdx - buildDependsIdx - optdependsIdx - pacdepsIdx - patchIdx - ppaIdx - repologyIdx -) +var PacscriptVars []string = []string{"pkgname", "pkgdesc", "gives", "hash", "pkgver"} +var PacscriptArrays []string = []string{"source", "arch", "maintainer", "depends", "conflicts", "breaks", "replaces", "makedepends", "optdepends", "pacdeps", "patch", "ppa", "repology"} + +type Stringable struct { + Data string +} + +func (s *Stringable) UnmarshalJSON(data []byte) error { + s.Data = string(data) + s.Data = strings.ReplaceAll(s.Data, "\"", "") + return nil +} + +func (s *Stringable) String() string { + return s.Data +} + +type StringableArrayWithComments struct { + Data []Stringable +} + +func (s StringableArrayWithComments) toStringArray() []string { + return array.SwitchMapPtr(s.Data, func(it *array.PtrIterator[Stringable]) string { + val := *it.Value + return val.String() + }) +} + +func (s *StringableArrayWithComments) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &s.Data) + if err != nil { + return err + } + + out := make([]string, 0) + item := "" + hasComments := false + for _, word := range s.Data { + strWord := word.String() + wordHasComment := strings.HasSuffix(strWord, ":") + + if wordHasComment { + hasComments = true + if item != "" { + out = append(out, strings.TrimSpace(item)) + } + item = strWord + } else if !hasComments { + out = append(out, strWord) + } else { + item += " " + strWord + } -func parseSubcategory(category string) []string { - subcategories := strings.Split(category, "+ +++") - for i, subcategory := range subcategories { - subcategories[i] = strings.TrimSpace(subcategory) } - return array.Filter(subcategories, func(it *array.Iterator[string]) bool { - return len(it.Value) > 0 + if item != "" { + out = append(out, strings.TrimSpace(item)) + } + + s.Data = array.SwitchMap(out, func(it *array.Iterator[string]) Stringable { + return Stringable{it.Value} }) + + return nil +} + +type pacscriptJsonStructure struct { + Pkgname string `json:"pkgname"` + Pkgdesc string `json:"pkgdesc"` + Gives *string `json:"gives"` + Hash *string `json:"hash"` + Pkgver *string `json:"pkgver"` + Source StringableArrayWithComments `json:"source"` + Maintainer StringableArrayWithComments `json:"maintainer"` + Depends StringableArrayWithComments `json:"depends"` + Conflicts StringableArrayWithComments `json:"conflicts"` + Arch StringableArrayWithComments `json:"arch"` + Breaks StringableArrayWithComments `json:"breaks"` + Replaces StringableArrayWithComments `json:"replaces"` + Makedepends StringableArrayWithComments `json:"makedepends"` + Optdepends StringableArrayWithComments `json:"optdepends"` + Pacdeps StringableArrayWithComments `json:"pacdeps"` + Patch StringableArrayWithComments `json:"patch"` + Ppa StringableArrayWithComments `json:"ppa"` + Repology StringableArrayWithComments `json:"repology"` } +var _GIT_VERSION = "git" +var _EMPTI_STR = "" + func parseOutput(data []byte) (out pac.Script, err error) { - content := string(data) - - categories := array.Map(strings.Split(content, "++++"), func(it *array.Iterator[string]) string { return strings.TrimSpace(it.Value) })[1:] - name := categories[nameIdx] - packageName := categories[pkgnameIdx] - maintainer := categories[maintainerIdx] - description := categories[descriptionIdx] - url := categories[urlIdx] - gives := categories[givesIdx] - hash := categories[hashIdx] - version := categories[versionIdx] - runtimeDependencies := categories[dependsIdx] - breaks := categories[breaksIdx] - replace := categories[replaceIdx] - buildDependencies := categories[buildDependsIdx] - optionalDependencies := categories[optdependsIdx] - pacstallDependencies := categories[pacdepsIdx] - patch := categories[patchIdx] - ppa := categories[ppaIdx] - repology := categories[repologyIdx] - - hashPtr := &hash - if hash == "" { - hashPtr = nil + // remove prefixes if any + runeIndex := strings.IndexRune(string(data), '{') + if runeIndex >= 0 { + str := string(data) + str = str[runeIndex:] + data = []byte(str) } - if len(strings.TrimSpace(version)) == 0 { - if strings.HasSuffix(name, "-git") { - version = "git" + var parsedContent pacscriptJsonStructure + err = json.Unmarshal(data, &parsedContent) + if err != nil { + return out, errorx.IllegalFormat.Wrap(err, "failed to deserialize json content '%v'", string(data)) + } + if parsedContent.Pkgver == nil { + if strings.HasSuffix(parsedContent.Pkgname, "-git") { + parsedContent.Pkgver = &_GIT_VERSION } else { - return out, errorx.IllegalArgument.New("expected version to be non-empty but got: %v", version) + return out, errorx.IllegalArgument.New("expected version to be non-empty") } } + if parsedContent.Gives == nil { + parsedContent.Gives = &_EMPTI_STR + } + out = pac.Script{ - Name: name, - PackageName: packageName, - Maintainer: maintainer, - Description: description, - URL: url, - Gives: gives, - Hash: hashPtr, - Version: version, - RuntimeDependencies: parseSubcategory(runtimeDependencies), - Breaks: parseSubcategory(breaks), - Replace: parseSubcategory(replace), - BuildDependencies: parseSubcategory(buildDependencies), - OptionalDependencies: parseSubcategory(optionalDependencies), - PacstallDependencies: parseSubcategory(pacstallDependencies), - PPA: parseSubcategory(ppa), - Patch: parseSubcategory(patch), + PackageName: parsedContent.Pkgname, + Maintainers: parseMaintainers(parsedContent.Maintainer.toStringArray()), + Description: parsedContent.Pkgdesc, + Source: parsedContent.Source.toStringArray(), + Gives: *parsedContent.Gives, + Hash: parsedContent.Hash, + Version: *parsedContent.Pkgver, + RuntimeDependencies: parsedContent.Depends.toStringArray(), + BuildDependencies: parsedContent.Makedepends.toStringArray(), + OptionalDependencies: parsedContent.Optdepends.toStringArray(), + Conflicts: parsedContent.Conflicts.toStringArray(), + Replaces: parsedContent.Replaces.toStringArray(), + Breaks: parsedContent.Breaks.toStringArray(), + PacstallDependencies: parsedContent.Pacdeps.toStringArray(), + PPA: parsedContent.Ppa.toStringArray(), + Patch: parsedContent.Patch.toStringArray(), RequiredBy: make([]string, 0), - Repology: parseSubcategory(repology), + Repology: parsedContent.Repology.toStringArray(), LatestVersion: nil, UpdateStatus: pac.UpdateStatus.Unknown, } - if out.PackageName == "" { - out.PackageName = out.Name + if pkgver, err := internal.NewGitSources(out.Source).ParseGitPackageVersion(); err == nil && pkgver != "" { + out.Version = pkgver + } + + if out.Hash != nil && len(*out.Hash) == 0 { + out.Hash = nil } out.PrettyName = getPrettyName(out) return } + +func parseMaintainers(maintainers []string) []string { + maintainersSplitByLA := strings.Split(strings.Join(maintainers, " "), ">") + + out := []string{} + for _, maintainer := range maintainersSplitByLA { + if len(maintainer) == 0 { + continue + } + out = append(out, strings.TrimSpace(maintainer)+">") + } + + return out +} diff --git a/server/types/pac/parser/pacsh/pretty-name.go b/server/types/pac/parser/pacsh/pretty-name.go index 216df132..1bed2dd1 100644 --- a/server/types/pac/parser/pacsh/pretty-name.go +++ b/server/types/pac/parser/pacsh/pretty-name.go @@ -17,7 +17,7 @@ func getPrettyName(p pac.Script) string { name := "" if name == "" { - name = p.Name + name = p.PackageName } for suffix := range pacTypes { @@ -31,11 +31,16 @@ func getPrettyName(p pac.Script) string { func titleCase(s string) string { title := "" - for _, word := range strings.Split(s, "-") { + words := strings.Split(s, "-") + + for _, word := range words { if title != "" { title += " " } + if len(word) == 0 { + continue + } title += strings.ToUpper(word[:1]) + strings.ToLower(word[1:]) } diff --git a/server/types/pac/parser/parallelism/timeout/timeout.go b/server/types/pac/parser/parallelism/timeout/timeout.go new file mode 100644 index 00000000..b881bf28 --- /dev/null +++ b/server/types/pac/parser/parallelism/timeout/timeout.go @@ -0,0 +1,38 @@ +package timeout + +import ( + "time" + + "github.com/joomcode/errorx" +) + +type result[T interface{}] struct { + value T + err error +} + +func Run[T interface{}](timeoutName string, handle func() (T, error), duration time.Duration) (T, error) { + var zero T + errChan := make(chan error) + resultChan := make(chan result[T]) + + go func() { + value, err := handle() + resultChan <- result[T]{ + value: value, + err: err, + } + }() + + go func() { + time.Sleep(duration) + errChan <- errorx.TimeoutElapsed.New("operation %v has timed out", timeoutName) + }() + + select { + case it := <-resultChan: + return it.value, it.err + case err := <-errChan: + return zero, err + } +} diff --git a/server/types/pac/parser/parse.go b/server/types/pac/parser/parse.go index 18028437..e93a226d 100644 --- a/server/types/pac/parser/parse.go +++ b/server/types/pac/parser/parse.go @@ -24,7 +24,7 @@ import ( const PACKAGE_LIST_FILE_NAME = "./packagelist" func ParseAll() error { - if err := git.RefreshPrograms(config.GitClonePath, config.GitURL); err != nil { + if err := git.RefreshPrograms(config.GitClonePath, config.GitURL, config.PacstallPrograms.Branch); err != nil { return errorx.Decorate(err, "could not update repository 'pacstall-programs'") } @@ -43,7 +43,7 @@ func ParseAll() error { } array.SortBy(loadedPacscripts, func(s1, s2 *pac.Script) bool { - return s1.Name < s2.Name + return s1.PackageName < s2.PackageName }) if err := setLastUpdatedAt(loadedPacscripts); err != nil { @@ -79,9 +79,6 @@ func parsePacscriptFiles(names []string) ([]*pac.Script, error) { log.Info("parsing pacscripts...") outChan := batch.Run(int(config.MaxOpenFiles), names, func(pacName string) (*pac.Script, error) { out, err := ParsePacscriptFile(config.GitClonePath, pacName) - if err != nil { - log.Warn("failed to parse %v. err: %v", pacName, err) - } if config.Repology.Enabled { if err := repology.Sync(&out); err != nil { diff --git a/server/types/pac/parser/parse_test.go b/server/types/pac/parser/parse_test.go index 45315e36..89fde1c4 100644 --- a/server/types/pac/parser/parse_test.go +++ b/server/types/pac/parser/parse_test.go @@ -1,6 +1,8 @@ package parser_test import ( + "encoding/json" + "fmt" "os" "path" "testing" @@ -23,34 +25,45 @@ var TEST_PROGRAMS_DIR = path.Join(FIXTURES_DIR, "test-programs") func assertEquals(t *testing.T, what string, expected interface{}, actual interface{}) { if actual != expected { - t.Errorf("expected %v '%v', got '%v'", what, expected, actual) + t.Errorf("pacscript.%v: expected '%#v', got '%#v'", what, expected, actual) } } func assertArrayEquals(t *testing.T, what string, expected []string, actual []string) { + if len(actual) == len(expected) && len(actual) == 0 { + return + } + if len(actual) != len(expected) { - t.Errorf("expected %v '%v', got '%v'", what, expected, actual) + t.Errorf("pacscript.%v expected len '%v', got len '%v' (expected '%#v', got '%#v')", what, len(expected), len(actual), expected, actual) + return } for idx := range expected { if expected[idx] != actual[idx] { - t.Errorf("expected %v '%v', got '%v'", what, expected, actual) + t.Errorf("pacscript.%v[%v] expected '%#v', got '%#v'", what, idx, expected, actual) } } } func assertPacscriptEquals(t *testing.T, expected pac.Script, actual pac.Script) { - assertEquals(t, "name", expected.Name, actual.Name) assertEquals(t, "package name", expected.PackageName, actual.PackageName) - assertEquals(t, "maintainer", expected.Maintainer, actual.Maintainer) + assertArrayEquals(t, "maintainers", expected.Maintainers, actual.Maintainers) assertEquals(t, "description", expected.Description, actual.Description) assertEquals(t, "gives", expected.Gives, actual.Gives) - assertEquals(t, "hash", *expected.Hash, *actual.Hash) + if expected.Hash != nil && actual.Hash == nil { + t.Errorf("expected hash '%v', got nil", *expected.Hash) + } else if expected.Hash == nil && actual.Hash != nil { + t.Errorf("expected hash nil, got %v", *actual.Hash) + } else if expected.Hash != nil && actual.Hash != nil { + assertEquals(t, "hash", *expected.Hash, *actual.Hash) + } assertEquals(t, "version", expected.Version, actual.Version) assertArrayEquals(t, "breaks", expected.Breaks, actual.Breaks) - assertArrayEquals(t, "replace", expected.Replace, actual.Replace) + assertArrayEquals(t, "conflicts", expected.Conflicts, actual.Conflicts) + assertArrayEquals(t, "replaces", expected.Replaces, actual.Replaces) assertEquals(t, "pretty name", expected.PrettyName, actual.PrettyName) - assertEquals(t, "url", expected.URL, actual.URL) + assertArrayEquals(t, "sources", expected.Source, actual.Source) assertArrayEquals(t, "runtime dependencies", expected.RuntimeDependencies, actual.RuntimeDependencies) assertArrayEquals(t, "build dependencies", expected.BuildDependencies, actual.BuildDependencies) assertArrayEquals(t, "optional dependencies", expected.OptionalDependencies, actual.OptionalDependencies) @@ -62,80 +75,68 @@ func assertPacscriptEquals(t *testing.T, expected pac.Script, actual pac.Script) assertEquals(t, "update status", expected.UpdateStatus, actual.UpdateStatus) } -func Test_ParsePacscriptFile_Valid(t *testing.T) { +func loadSnapshot(snapshotPath string) (*pac.Script, error) { + bytes, err := os.ReadFile(snapshotPath) + if err != nil { + return nil, err + } + + var out pac.Script + if err := json.Unmarshal(bytes, &out); err != nil { + return nil, err + } + + return &out, nil +} + +func assertPacscriptMatchesSnapshot(t *testing.T, pkgname string) { + t.Helper() + if pacsh.CreateTempDirectory("./tmp") != nil { t.Errorf("failed to create temp directory") return } - actual, err := parser.ParsePacscriptFile(TEST_PROGRAMS_DIR, "sample-valid-deb") + actual, err := parser.ParsePacscriptFile(TEST_PROGRAMS_DIR, pkgname) if err != nil { t.Errorf("expected no error, got %v", err) return } - hash := "10101010" - expected := pac.Script{ - Name: "sample-valid-deb", - PackageName: "sample-valid", - Maintainer: "pacstall ", - Description: "Sample description", - Gives: "sample-valid", - Hash: &hash, - Version: "1.0.0", - Breaks: []string{"breaks1", "breaks2"}, - Replace: []string{"replaces1", "replaces2"}, - PrettyName: "Sample Valid", - URL: "https://example.com", - RuntimeDependencies: []string{"dep1", "dep2"}, - BuildDependencies: []string{"go", "gcc"}, - OptionalDependencies: []string{"opt1", "opt2"}, - PacstallDependencies: []string{"pacdep1", "pacdep2"}, - PPA: []string{"ppa1", "ppa2"}, - Patch: []string{"patch1", "patch2"}, - RequiredBy: []string{}, - Repology: []string{"project: sample-valid"}, - UpdateStatus: pac.UpdateStatus.Unknown, - } + snapshotPath := path.Join(TEST_PROGRAMS_DIR, "packages", pkgname, fmt.Sprintf("%v.snapshot.json", pkgname)) + expected, err := loadSnapshot(snapshotPath) + if err != nil { + bytes, err := json.Marshal(actual) + if err != nil { + t.Errorf("failed to serialize snapshot. %v", err) + return + } - assertPacscriptEquals(t, expected, actual) -} + if err := os.WriteFile(snapshotPath, bytes, 0644); err != nil { + t.Errorf("failed to write snapshot. %v", err) + return + } -func Test_ParsePacscriptFile_WithPkgverFunc_Valid(t *testing.T) { - if pacsh.CreateTempDirectory("./tmp") != nil { - t.Errorf("failed to create temp directory") + t.Errorf("missing snapshot. a new one has been generated. rerun tests") return } - actual, err := parser.ParsePacscriptFile(TEST_PROGRAMS_DIR, "sample-valid-with-pkgver-func-deb") + assertPacscriptEquals(t, *expected, actual) +} + +func Test_PacscriptSnapshots(t *testing.T) { + dirEntries, err := os.ReadDir(path.Join(TEST_PROGRAMS_DIR, "packages")) if err != nil { - t.Errorf("expected no error, got %v", err) + t.Errorf("failed to read test packages. %v", err) return } - hash := "10101010" - expected := pac.Script{ - Name: "sample-valid-deb", - PackageName: "sample-valid", - Maintainer: "pacstall ", - Description: "Sample description", - Gives: "sample-valid", - Hash: &hash, - Version: "1.2.3", - Breaks: []string{"breaks1", "breaks2"}, - Replace: []string{"replaces1", "replaces2"}, - PrettyName: "Sample Valid", - URL: "https://example.com", - RuntimeDependencies: []string{"dep1", "dep2"}, - BuildDependencies: []string{"go", "gcc"}, - OptionalDependencies: []string{"opt1", "opt2"}, - PacstallDependencies: []string{"pacdep1", "pacdep2"}, - PPA: []string{"ppa1", "ppa2"}, - Patch: []string{"patch1", "patch2"}, - RequiredBy: []string{}, - Repology: []string{"project: sample-valid"}, - UpdateStatus: pac.UpdateStatus.Unknown, - } + for _, dirEntry := range dirEntries { + if !dirEntry.IsDir() { + continue + } - assertPacscriptEquals(t, expected, actual) + t.Logf("==> Running snapshot test for file: %v", dirEntry.Name()) + assertPacscriptMatchesSnapshot(t, dirEntry.Name()) + } } diff --git a/server/types/pac/parser/search.go b/server/types/pac/parser/search.go index 179091af..4e8c5776 100644 --- a/server/types/pac/parser/search.go +++ b/server/types/pac/parser/search.go @@ -36,15 +36,14 @@ func FilterPackages(packages []*pac.Script, filter, filterBy string) []*pac.Scri switch filterBy { case "name": return filterByFunc(func(pi *pac.Script) bool { - return strings.Contains(pi.Name, filter) || - strings.Contains(pi.PackageName, filter) || + return strings.Contains(pi.PackageName, filter) || strings.Contains(pi.Gives, filter) || strings.Contains(pi.Description, filter) }) case "maintainer": return filterByFunc(func(pi *pac.Script) bool { - return strings.Contains(pi.Maintainer, filter) + return strings.Contains(strings.Join(pi.Maintainers, ", "), filter) }) default: return packages @@ -62,22 +61,22 @@ func SortPackages(packages []*pac.Script, sortType, sortBy string) []*pac.Script case "name": if strings.Compare(sortType, "asc") == 0 { out = array.SortBy(out, func(a, b *pac.Script) bool { - return strings.Compare(a.Name, b.Name) < 0 + return strings.Compare(a.PackageName, b.PackageName) < 0 }) } else { out = array.SortBy(out, func(a, b *pac.Script) bool { - return strings.Compare(a.Name, b.Name) > 0 + return strings.Compare(a.PackageName, b.PackageName) > 0 }) } case "maintainer": if strings.Compare(sortType, "asc") == 0 { out = array.SortBy(out, func(a, b *pac.Script) bool { - return strings.Compare(a.Maintainer, b.Maintainer) < 0 + return strings.Compare(strings.Join(a.Maintainers, ","), strings.Join(b.Maintainers, ",")) < 0 }) } else { out = array.SortBy(out, func(a, b *pac.Script) bool { - return strings.Compare(a.Maintainer, b.Maintainer) > 0 + return strings.Compare(strings.Join(a.Maintainers, ","), strings.Join(b.Maintainers, ",")) > 0 }) } diff --git a/server/types/pac/script.go b/server/types/pac/script.go index 50f93d75..b97f12da 100644 --- a/server/types/pac/script.go +++ b/server/types/pac/script.go @@ -21,20 +21,20 @@ var UpdateStatus = updateStatus{ type UpdateStatusValue = int type Script struct { - Name string `json:"name"` PrettyName string `json:"prettyName"` Version string `json:"version"` LatestVersion *string `json:"latestVersion"` PackageName string `json:"packageName"` - Maintainer string `json:"maintainer"` + Maintainers []string `json:"maintainers"` Description string `json:"description"` - URL string `json:"url"` + Source []string `json:"source"` RuntimeDependencies []string `json:"runtimeDependencies"` BuildDependencies []string `json:"buildDependencies"` OptionalDependencies []string `json:"optionalDependencies"` + Conflicts []string `json:"conflicts"` Breaks []string `json:"breaks"` Gives string `json:"gives"` - Replace []string `json:"replace"` + Replaces []string `json:"replaces"` Hash *string `json:"hash"` PPA []string `json:"ppa"` PacstallDependencies []string `json:"pacstallDependencies"`

= ({ fontWeight={useColorModeValue('700', '500')} > {disabled === true ? ( - {pkg.name} + {pkg.packageName} ) : ( = ({ 'pink.600', 'pink.400', )} - to={`/packages/${pkg.name}`} + to={`/packages/${pkg.packageName}`} > - {pkg.name} + {pkg.packageName} )} @@ -49,20 +53,24 @@ const PackageTableRow: FC<{ pkg: PackageInfo; disabled?: boolean }> = ({ + maintainer.split('<')[0].trim(), + ) + .join(', '), }, ) : t('packageSearch.maintainerTooltip.noMaintainer') } > - {(pkg.maintainer || t('packageDetails.orphaned')) - .split('<')[0] - .trim()} + {pkg.maintainers + .map(maintainer => maintainer.split('<')[0].trim()) + .join(', ') || t('packageDetails.orphaned')}