diff --git a/.chocolatey/.gitignore b/.chocolatey/.gitignore deleted file mode 100644 index a7aeaaa13d..0000000000 --- a/.chocolatey/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.nupkg diff --git a/.chocolatey/podman-desktop/podman-desktop.nuspec b/.chocolatey/podman-desktop/podman-desktop.nuspec deleted file mode 100644 index e39f4471f8..0000000000 --- a/.chocolatey/podman-desktop/podman-desktop.nuspec +++ /dev/null @@ -1,45 +0,0 @@ - - - - - podman-desktop - 1.20.2 - Podman Desktop - Florent Benoit - https://github.com/containers - https://github.com/containers/podman-desktop/blob/main/LICENSE - http://www.podman-desktop.io/ - https://cdn.jsdelivr.net/gh/containers/podman-desktop@v0.9.1/buildResources/icon.png - false - -## Containers and Kubernetes for application developers - -### Build, run and manage containers: -- Build images from Containerfile or Dockerfile. -- Pull images from remote registries. -- Start, Stop, Restart containers and pods. -- Easily get a terminal in your container. -- Inspect container logs. -- Push images to OCI registries. -- Deploy and test images on Kubernetes. - -### Multiple configuration options -- Manage OCI registries; add, edit, or delete registries. -- Configure your proxy settings (work in progress.) -- Configure CPU, memory, and disk of Podman machines (work in progress.) -- Handle multiple container engines at the same time (Podman, Docker, Lima...) - -### You can also bring new features with Podman Desktop plug-ins or Docker Desktop extensions - - Manage Podman and other container engines from a single UI and tray. - https://github.com/containers/podman-desktop/releases/expanded_assets/v1.20.2 - 2022 Red Hat, Inc - podman desktop podman-machine containers ui - https://github.com/containers/podman-desktop/tree/main/.chocolatey - https://github.com/containers/podman-desktop - https://podman-desktop.io/docs/intro - - - - - diff --git a/.chocolatey/podman-desktop/tools/chocolateyInstall.ps1 b/.chocolatey/podman-desktop/tools/chocolateyInstall.ps1 deleted file mode 100644 index a81b1feafe..0000000000 --- a/.chocolatey/podman-desktop/tools/chocolateyInstall.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -$ErrorActionPreference = 'Stop' - -$packageArgs = @{ - packageName = 'podman-desktop' - fileType = 'exe' - softwareName = 'PodmanDesktop' - - url64bit = 'https://github.com/podman-desktop/podman-desktop/releases/download/v1.20.2/podman-desktop-1.20.2-setup.exe' - checksumType = 'sha256' - checksum64 = 'f6a2e9a48a1685515fa6d401834afe2a535cd928999ccf9da07218c595a5926f' - - silentArgs = '/S' - validExitCodes = @(0) -} - -Install-ChocolateyPackage @packageArgs diff --git a/.chocolatey/podman-desktop/tools/chocolateyUninstall.ps1 b/.chocolatey/podman-desktop/tools/chocolateyUninstall.ps1 deleted file mode 100644 index 14d7e82975..0000000000 --- a/.chocolatey/podman-desktop/tools/chocolateyUninstall.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -$ErrorActionPreference = 'Stop'; - -$packageName = 'podman-desktop' - -$uninstalled = $false -[array]$key = Get-UninstallRegistryKey -SoftwareName 'PodmanDesktop' - -if ($key.Count -eq 1) { - $key | % { - $packageArgs = @{ - packageName = $packageName - fileType = 'EXE' - silentArgs = '/S' - validExitCodes = @(0) - file = "$($_.UninstallString.Trim('"'))" - } - - Uninstall-ChocolateyPackage @packageArgs - } -} elseif ($key.Count -eq 0) { - Write-Warning "$packageName has already been uninstalled by other means." -} elseif ($key.Count -gt 1) { - Write-Warning "$key.Count matches found!" - Write-Warning "To prevent accidental data loss, no programs will be uninstalled." - Write-Warning "Please alert package maintainer the following keys were matched:" - $key | % {Write-Warning "- $_.DisplayName"} -} diff --git a/.chocolatey/podman-desktop/update.ps1 b/.chocolatey/podman-desktop/update.ps1 deleted file mode 100644 index be1a42589c..0000000000 --- a/.chocolatey/podman-desktop/update.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -import-module au - -$version = $env:VERSION -$releases = 'https://github.com/containers/podman-desktop/releases/expanded_assets/v' + $version - -function global:au_SearchReplace { - @{ - ".\tools\chocolateyInstall.ps1" = @{ - "(?i)(^\s*url64bit\s*=\s*)('.*')" = "`$1'$($Latest.URL64)'" - "(?i)(^\s*checksum64\s*=\s*)('.*')" = "`$1'$($Latest.Checksum64)'" - } - ".\podman-desktop.nuspec" = @{ - "\.+" = "$($Latest.Version)" - "\.+" = "$($Latest.ReleaseNotes)" - } - } -} - -function global:au_GetLatest { - $download_page = Invoke-WebRequest -Uri $releases - - $url64 = $download_page.links | ? href -match '-setup.exe$' | % href | select -First 1 - $version = (Split-Path ( Split-Path $url64 ) -Leaf).Substring(1) - - @{ - URL64 = 'https://github.com' + $url64 - Version = $version - ReleaseNotes = $releases - } -} - -update -ChecksumFor 64 diff --git a/.clomonitor.yml b/.clomonitor.yml deleted file mode 100644 index 4944f89af4..0000000000 --- a/.clomonitor.yml +++ /dev/null @@ -1,21 +0,0 @@ -# -# Copyright (C) 2025 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -# Checks exemptions -exemptions: - - check: artifacthub_badge # Check identifier (see https://github.com/cncf/clomonitor/blob/main/docs/checks.md#exemptions) - reason: "Podman Desktop is a desktop application and does not offer Cloud Native packages." diff --git a/.devcontainer/.parent/Containerfile b/.devcontainer/.parent/Containerfile deleted file mode 100644 index dd84414874..0000000000 --- a/.devcontainer/.parent/Containerfile +++ /dev/null @@ -1,60 +0,0 @@ -FROM quay.io/fedora/fedora:37 - -# install Node.js + yarn -ENV NODE_VERSION 16.18.1 -RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz" && \ - tar -xzf "node-v$NODE_VERSION-linux-x64.tar.gz" -C /usr/local --strip-components=1 && \ - rm "node-v$NODE_VERSION-linux-x64.tar.gz" && \ - npm install -g yarn - -RUN dnf -y update && \ - yum -y reinstall shadow-utils && \ - yum install -y git \ - # dependencies for Podman Desktop - nss \ - atk \ - at-spi2-atk \ - cups-libs \ - gtk3 \ - # for remote Display - fluxbox \ - tigervnc-server \ - xorg-x11-fonts-Type1 \ - novnc \ - supervisor \ - xdpyinfo \ - # for podman - podman \ - fuse-overlayfs --exclude container-selinux \ - xterm && \ - rm -rf /var/cache /var/log/dnf* /var/log/yum.* -COPY entrypoint.sh /entrypoint.sh -COPY supervisord.conf /etc/supervisord.conf -COPY fluxbox /usr/share/fluxbox/init -RUN useradd -u 1000 podman-desktop && echo podman-desktop:10000:5000 > /etc/subuid && echo podman-desktop:10000:5000 > /etc/subgid - -# Disable telemetry -RUN mkdir -p /home/podman-desktop/.local/share/containers/podman-desktop/configuration && \ - echo '{"telemetry.enabled": false, "telemetry.check": true}' > /home/podman-desktop/.local/share/containers/podman-desktop/configuration/settings.json - -# expose port for VNC -EXPOSE 8080 - -# initialize conf files -ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/containers.conf -ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/podman-containers.conf /home/podman-desktop/.config/containers/containers.conf - -# set permissions -RUN chown podman-desktop:podman-desktop -R /home/podman-desktop && chmod 644 /etc/containers/containers.conf && \ - mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers /var/lib/shared/vfs-images /var/lib/shared/vfs-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock; touch /var/lib/shared/vfs-images/images.lock; touch /var/lib/shared/vfs-layers/layers.lock && \ - mkdir -p /run/user/1000 && chown podman-desktop:podman-desktop /run/user/1000 - - -ENV _CONTAINERS_USERNS_CONFIGURED="" - -# socket path for podman -ENV XDG_RUNTIME_DIR=/run/user/1000 - -ENTRYPOINT [ "/entrypoint.sh" ] -USER podman-desktop -CMD ["tail", "-f", "/dev/null"] \ No newline at end of file diff --git a/.devcontainer/.parent/entrypoint.sh b/.devcontainer/.parent/entrypoint.sh deleted file mode 100755 index 66722d7fb2..0000000000 --- a/.devcontainer/.parent/entrypoint.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh - -# -# Copyright (C) 2022 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -# Ensure $HOME exists when starting -if [ ! -d "${HOME}" ]; then - mkdir -p "${HOME}" -fi - -# Setup $PS1 for a consistent and reasonable prompt -if [ -w "${HOME}" ] && [ ! -f "${HOME}"/.bashrc ]; then - echo "PS1='\s-\v \w \$ '" > "${HOME}"/.bashrc -fi - -# Add current (arbitrary) user to /etc/passwd and /etc/group -if ! whoami > /dev/null 2>&1; then - if [ -w /etc/passwd ]; then - echo "update passwd file" - echo "${USER_NAME:-user}:x:$(id -u):0:${USER_NAME:-user} user:${HOME}:/bin/bash" >> /etc/passwd - echo "${USER_NAME:-user}:x:$(id -u):" >> /etc/group - fi -fi -/usr/bin/supervisord -c /etc/supervisord.conf diff --git a/.devcontainer/.parent/fluxbox b/.devcontainer/.parent/fluxbox deleted file mode 100644 index d182474012..0000000000 --- a/.devcontainer/.parent/fluxbox +++ /dev/null @@ -1,13 +0,0 @@ -! If you're looking for settings to configure, they won't be saved here until -! you change something in the fluxbox configuration menu. - -session.menuFile: ~/.fluxbox/menu -session.keyFile: ~/.fluxbox/keys -! Change the default theme -session.styleFile: /usr/share/fluxbox/styles/bora_blue -session.configVersion: 13 -session.screen0.toolbar.widthPercent: 100 -session.screen0.strftimeFormat: %d %b, %a %02k:%M:%S -session.screen0.toolbar.tools: prevworkspace, workspacename, nextworkspace, clock, prevwindow, nextwindow, iconbar, systemtray -! disable toolbar -session.screen0.toolbar.visible: false diff --git a/.devcontainer/.parent/supervisord.conf b/.devcontainer/.parent/supervisord.conf deleted file mode 100644 index e94804d025..0000000000 --- a/.devcontainer/.parent/supervisord.conf +++ /dev/null @@ -1,18 +0,0 @@ -[supervisord] -nodaemon=true -logfile = /home/podman-desktop/supervisord.log -pidfile = /home/podman-desktop/supervisord.pid - -[program:tigervnc] -environment=DISPLAY=":0" -command=vncserver :0 -geometry 1920x1080 -depth 24 -SecurityTypes None -user=podman-desktop -autorestart=false -priority=300 - -[program:windowmanager] -environment=DISPLAY=":0" -command=fluxbox -user=podman-desktop -autorestart=true -priority=350 diff --git a/.devcontainer/Containerfile b/.devcontainer/Containerfile deleted file mode 100644 index c0b136dc7a..0000000000 --- a/.devcontainer/Containerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM quay.io/podman-desktop/devcontainer-parent:next - diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index b1d5fbe7e0..0000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "Podman Desktop", - "build": { - "dockerfile": "Containerfile" - }, - // do not use root - "containerUser": "podman-desktop", - // Add the IDs of extensions you want installed when the container is created. - "extensions": ["svelte.svelte-vscode", "bradlc.vscode-tailwindcss"], - "features": {}, - "runArgs": [ - "--cap-add=sys_admin", - "--security-opt", - "seccomp=unconfined", - "--device", - "/dev/fuse", - "--security-opt", - "label=disable", - "--security-opt", - "apparmor=unconfined" - ], - "postStartCommand": "${containerWorkspaceFolder}/.devcontainer/postStartCommand.sh", - "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind", - "workspaceFolder": "/workspace", - "onCreateCommand": "${containerWorkspaceFolder}/.devcontainer/onCreateCommand.sh", - "updateContentCommand": "${containerWorkspaceFolder}/.devcontainer/updateContentCommand.sh", - "remoteEnv": { - "DISPLAY": ":0" - }, - "portsAttributes": { - "9000": { - "label": "vnc", - "onAutoForward": "openPreview" - }, - "3000": { - "label": "website" - } - }, - "hostRequirements": { - "memory": "8gb" - } -} diff --git a/.devcontainer/onCreateCommand.sh b/.devcontainer/onCreateCommand.sh deleted file mode 100755 index f34bba60e1..0000000000 --- a/.devcontainer/onCreateCommand.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2022 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -# This script is run inside the container after the repo has been cloned. -# Launch yarn to install dependencies and build the project. -yarn - -# build podman-desktop -MODE=production yarn run build && yarn run electron-builder build --linux --dir --config .electron-builder.config.cjs diff --git a/.devcontainer/postStartCommand.sh b/.devcontainer/postStartCommand.sh deleted file mode 100755 index 6cbd9d15fd..0000000000 --- a/.devcontainer/postStartCommand.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2022 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -# Need to start all services -/usr/bin/supervisord -c /etc/supervisord.conf & - -# wait X server to be ready or after 2mn exit -echo "Waiting for X server to be ready" -timeout 120 bash -c 'until xdpyinfo -display :0 &> /dev/null; do printf "."; sleep 1; done' - -# launch podman desktop -echo "Launching Podman Desktop" -cd dist/linux-unpacked/&& ./podman-desktop & - - -# Launch the 9000 redirect after 20 seconds -sleep 20 -websockify --web=/usr/share/novnc localhost:9000 localhost:5900 & - -# launch the website rendering -echo "Launching Website" -cd website && yarn start diff --git a/.devcontainer/updateContentCommand.sh b/.devcontainer/updateContentCommand.sh deleted file mode 100755 index ba3fe69ea8..0000000000 --- a/.devcontainer/updateContentCommand.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2022 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -# Do nothing for now (maybe need to use postStartCommand.sh) diff --git a/.electron-builder.config.cjs b/.electron-builder.config.cjs index 94555c45be..8dc7093319 100644 --- a/.electron-builder.config.cjs +++ b/.electron-builder.config.cjs @@ -70,8 +70,8 @@ async function addElectronFuses(context) { * @see https://www.electron.build/configuration/configuration */ const config = { - productName: 'Podman Desktop', - appId: 'io.podman_desktop.PodmanDesktop', + productName: 'Kortex', + appId: 'dev.kortex-hub.Kortex', directories: { output: 'dist', buildResources: 'buildResources', @@ -82,58 +82,27 @@ const config = { const DEFAULT_ASSETS = []; context.packager.config.extraResources = DEFAULT_ASSETS; - // universal build, add both pkg files - // this is hack to avoid issue https://github.com/electron/universal/issues/36 - if ( - context.appOutDir.endsWith('mac-universal-x64-temp') || - context.appOutDir.endsWith('mac-universal-arm64-temp') - ) { - context.packager.config.extraResources = DEFAULT_ASSETS; - context.packager.config.extraResources.push( - 'extensions/podman/packages/extension/assets/podman-installer-macos-universal*.pkg', - ); - return; - } - - if (context.arch === Arch.arm64 && context.electronPlatformName === 'darwin') { - context.packager.config.extraResources.push( - 'extensions/podman/packages/extension/assets/podman-installer-macos-aarch64-*.pkg', - ); - context.packager.config.extraResources.push('extensions/podman/packages/extension/assets/podman-image-arm64.zst'); - } - - if (context.arch === Arch.x64 && context.electronPlatformName === 'darwin') { - context.packager.config.extraResources.push( - 'extensions/podman/packages/extension/assets/podman-installer-macos-amd64-*.pkg', - ); - context.packager.config.extraResources.push('extensions/podman/packages/extension/assets/podman-image-x64.zst'); - } - if (context.electronPlatformName === 'win32') { // add the win-ca package context.packager.config.extraResources.push({ from: 'node_modules/win-ca/lib/roots.exe', to: 'win-ca/roots.exe', }); - // add podman installer - context.packager.config.extraResources.push('extensions/podman/packages/extension/assets/podman-*.exe'); - } - if (context.arch === Arch.x64 && context.electronPlatformName === 'win32') { - context.packager.config.extraResources.push('extensions/podman/packages/extension/assets/podman-image-x64.zst'); - } - if (context.arch === Arch.arm64 && context.electronPlatformName === 'win32') { - context.packager.config.extraResources.push('extensions/podman/packages/extension/assets/podman-image-arm64.zst'); } + context.packager.config.extraResources.push({ + from: 'packages/main/src/chat/db/migrations/', + to: 'chat/db/migrations', + }); }, afterPack: async context => { await addElectronFuses(context); }, files: ['packages/**/dist/**', 'extensions/**/builtin/*.cdix/**', 'packages/main/src/assets/**'], portable: { - artifactName: `podman-desktop${artifactNameSuffix}-\${version}-\${arch}.\${ext}`, + artifactName: `kortex${artifactNameSuffix}-\${version}-\${arch}.\${ext}`, }, nsis: { - artifactName: `podman-desktop${artifactNameSuffix}-\${version}-setup-\${arch}.\${ext}`, + artifactName: `kortex${artifactNameSuffix}-\${version}-setup-\${arch}.\${ext}`, oneClick: false, }, win: { @@ -186,21 +155,21 @@ const config = { '--talk-name=org.freedesktop.Flatpak', ], useWaylandFlags: 'false', - artifactName: 'podman-desktop-${version}.${ext}', + artifactName: 'kortex-${version}.${ext}', runtimeVersion: '24.08', branch: 'main', files: [ - ['.flatpak-appdata.xml', '/share/metainfo/io.podman_desktop.PodmanDesktop.metainfo.xml'], - ['buildResources/icon-512x512.png', '/share/icons/hicolor/512x512/apps/io.podman_desktop.PodmanDesktop.png'], + ['.flatpak-appdata.xml', '/share/metainfo/dev.kortex.Kortex.metainfo.xml'], + ['buildResources/icon-1024x1024.png', '/share/icons/hicolor/1024x1024/apps/io.podman_desktop.PodmanDesktop.png'], ], }, linux: { category: 'Development', - icon: './buildResources/icon-512x512.png', + icon: './buildResources/icon-1024x1024.png', target: ['flatpak', { target: 'tar.gz', arch: ['x64', 'arm64'] }], }, mac: { - artifactName: `podman-desktop${artifactNameSuffix}-\${version}-\${arch}.\${ext}`, + artifactName: `kortex${artifactNameSuffix}-\${version}-\${arch}.\${ext}`, hardenedRuntime: true, entitlements: './node_modules/electron-builder-notarize/entitlements.mac.inherit.plist', target: { @@ -229,8 +198,8 @@ const config = { ], }, protocols: { - name: 'Podman Desktop', - schemes: ['podman-desktop'], + name: 'Kortex', + schemes: ['kortex'], role: 'Editor', }, publish: { diff --git a/.flatpak-appdata.xml b/.flatpak-appdata.xml index 9ebcf8e29f..9fdf86f5fc 100644 --- a/.flatpak-appdata.xml +++ b/.flatpak-appdata.xml @@ -1,154 +1,24 @@ - io.podman_desktop.PodmanDesktop - Podman Desktop + dev.kortex_hub.Kortex + Kortex Apache-2.0 Red Hat, Inc. - Manage Podman and other container engines from a single UI + Kortex CC0-1.0 - https://podman-desktop.io/ - https://github.com/containers/podman-desktop/issues - https://podman-desktop.io/docs/intro + https://TODO + https://TODO + https://TODO -

Podman Desktop is an open source graphical tool enabling you to seamlessly work with containers and Kubernetes from your local environment.

-

Build, run and manage containers:

-
    -
  • Build images from Containerfile or Dockerfile.
  • -
  • Pull images from remote registries.
  • -
  • Start, Stop, Restart containers and pods.
  • -
  • Easily get a terminal in your container.
  • -
  • Inspect container logs.
  • -
  • Push images to OCI registries.
  • -
  • Deploy and test images on Kubernetes.
  • -
-

Multiple configuration options

-
    -
  • Manage OCI registries; add, edit, or delete registries.
  • -
  • Configure your proxy settings (work in progress.)
  • -
  • Configure CPU, memory, and disk of Podman machines (work in progress.)
  • -
  • Handle multiple container engines at the same time (Podman, Docker, Lima...)
  • -
-

You can also bring new features with Podman Desktop plug-ins or Docker Desktop extensions.

- io.podman_desktop.PodmanDesktop.desktop + dev.kortex_hub.Kortex - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/light/dashboard.png - Main dashboard - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/dark/dashboard.png - Main dashboard - - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/light/containers.png - List view of running containers - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/dark/containers.png - List view of running containers - - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/light/pods.png - List view of running pods - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/dark/pods.png - List view of running pods - - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/light/images.png - List view of downloaded images - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/dark/images.png - List view of downloaded images - - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/light/volumes.png - List view of existing volumes - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/dark/volumes.png - List view of existing volumes - - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/light/kubernetes.png - Overview of Kubernetes clusters - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/dark/kubernetes.png - Overview of Kubernetes clusters - - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/light/extensions.png - List of supported extensions - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/dark/extensions.png - List of supported extensions - - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/light/settings.png - Overview of supported customizations - - - https://raw.githubusercontent.com/containers/podman-desktop-media/8786db2a59e62d7bebbe4602c0475a1cadf2f54c/screenshots/linux/dark/settings.png - Overview of supported customizations - fbenoit_at_redhat_com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/.flatpak.desktop b/.flatpak.desktop index 806f83519a..11586f9ebe 100644 --- a/.flatpak.desktop +++ b/.flatpak.desktop @@ -1,10 +1,10 @@ [Desktop Entry] -Name=Podman Desktop +Name=Kortex Exec=run.sh %U Terminal=false Type=Application -Icon=io.podman_desktop.PodmanDesktop -StartupWMClass=Podman Desktop +Icon=dev.kortex_hub.Kortex +StartupWMClass=Kortex Categories=Development; -Keywords=container;pod;image;volume;kubernetes;docker;kind;kubectl; -X-Flatpak=io.podman_desktop.PodmanDesktop +Keywords= +X-Flatpak=dev.kortex_hub.Kortex diff --git a/.github/ISSUE_TEMPLATE/UX_Template.yml b/.github/ISSUE_TEMPLATE/UX_Template.yml deleted file mode 100644 index 990d853228..0000000000 --- a/.github/ISSUE_TEMPLATE/UX_Template.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: UX Request -description: UX Request Form -labels: [UX/UI Issue, Graphic design] -projects: ["podman-desktop/4"] - -body: - - type: markdown - attributes: - value: | - Before opening a UX request, please search for existing issues. - - --- - - - type: textarea - id: UX-description - attributes: - label: UX Description - description: Describe the request - validations: - required: true - - - type: dropdown - id: request-type - attributes: - label: Request type - description: "What type of request is this?" - options: - - "A logo design" - - "An icon" - - "An infographic/chart" - - "A template or design for printed materials" - - "Swag design" - - "Graphic design not covered by the above" - - "UX analysis/suggestions for improvement" - - "User research" - - "User testing" - - "Application mockups/designs" - - "Website mockups/designs" - - "Something else UX-related" - - - - type: textarea - id: Contacts - attributes: - label: Primary Contact - description: Who is the contact the design team can speak with about this issue? - - - type: textarea - id: Deadline - attributes: - label: Deadline for request - description: When do you need this? If this is for an event, please let us know the date of the event and any lead time you need to get materials produced. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 7270288ee5..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Bug 🐞 -description: Report a bug report -labels: [kind/bug 🐞] -projects: ["podman-desktop/4"] - -body: - - type: markdown - attributes: - value: | - Before opening a bug report, please search for the behaviour in the existing issues. - - --- - - Thank you for taking the time to file a bug report. To address this bug as fast as possible, we need some information. - - - type: textarea - id: bug-description - attributes: - label: Bug description - description: What happened? - validations: - required: true - - - type: input - id: os - attributes: - label: Operating system - description: "Which operating system are you on? Please provide the version as well. If you are on a Mac, please specify Apple silicon or Intel." - placeholder: "macOS Ventura 13.4 (Arm), Windows 11" - validations: - required: true - - - type: dropdown - id: install - attributes: - label: Installation Method - description: "How did you install Podman Desktop ?" - options: - - "Installer from website/GitHub releases" - - "Brew (macOS)" - - "Chocolatey (Windows)" - - "Flathub (Linux)" - - "Scoop (Windows)" - - "Winget (Windows)" - - "Other" - - - type: dropdown - id: version - attributes: - label: Version - description: What version of the software are you running? - options: - - "1.19.2" - - "1.20.0" - - "next (development version)" - - "1.19.1" - - "1.18.0" - - "1.17.0" - - "1.16.2" - - "1.16.1" - - "1.16.0" - - "1.15.x" - - "1.14.x" - - "1.13.x" - - "1.12.x" - - "1.11.x" - - "1.10.x" - - "1.9.x" - - "1.8.x" - - "1.7.x" - - "1.6.x" - - "1.5.x" - - "1.4.x" - - "1.3.x" - - "1.2.x" - - "1.1.x" - - "1.0.x" - - "0.x" - validations: - required: true - - - type: textarea - id: steps - attributes: - label: Steps to reproduce - description: What steps do we need to take to reproduce this error? - - - type: textarea - id: logs - attributes: - label: Relevant log output - description: If applicable, provide relevant log output. - render: shell - - - type: textarea - id: additional-context - attributes: - label: Additional context - description: Add any other context or screenshots here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 3ba13e0cec..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1 +0,0 @@ -blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml deleted file mode 100644 index f3250d2153..0000000000 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Enhancement ✨ -description: Suggest an enhancement to an existing feature -labels: [kind/enhancement ✨] -projects: ["podman-desktop/4"] - -body: - - type: markdown - attributes: - value: | - Before opening a new enhancement, please search for potential existing issues. - - --- - - Thank you for taking the time to file an enhancement request, we appreciate and value your time to help the project! - - - type: textarea - id: problem - attributes: - label: Is your enhancement related to a problem? Please describe - description: A clear and concise description of what the problem is. - placeholder: I'm always frustrated when [...] - validations: - required: true - - - type: textarea - id: solution - attributes: - label: Describe the solution you'd like - description: A clear and concise description of what you want to happen. - validations: - required: true - - - type: textarea - id: alternatives - attributes: - label: Describe alternatives you've considered - description: A clear and concise description of any alternative solutions or features you've considered. - - - type: textarea - id: additional-context - attributes: - label: Additional context - description: Add any other context or screenshots here. diff --git a/.github/ISSUE_TEMPLATE/epic.yml b/.github/ISSUE_TEMPLATE/epic.yml deleted file mode 100644 index 5c79b8ab5a..0000000000 --- a/.github/ISSUE_TEMPLATE/epic.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Epic ⚡ -description: A high-level feature -labels: [kind/epic ⚡] -projects: ["podman-desktop/4","podman-desktop/7"] - -body: - - type: markdown - attributes: - value: | - Epics are normally created by the development team, to group a set of related features and plan work across multiple sprints. - The features this epic includes are referenced with the text of the epic. - - - type: textarea - id: domain - attributes: - label: Epic domain - description: A clear and concise description of the feature area or domain that this epic will address. - placeholder: Podman Desktop should support [...] - validations: - required: true - - - type: textarea - id: additional-context - attributes: - label: Additional context - description: Add any other context or screenshots here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index d22d237160..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Feature 💡 -description: Suggest an idea for this project -labels: [kind/feature 💡] -projects: ["podman-desktop/4"] - -body: - - type: markdown - attributes: - value: | - Before opening a feature request, please search for potential existing issues. - - --- - - Thank you for taking the time to file a feature request, we appreciate and value your time to help the project! - - - type: textarea - id: problem - attributes: - label: Is your feature request related to a problem? Please describe - description: A clear and concise description of what the problem is. - placeholder: I'm always frustrated when [...] - validations: - required: true - - - type: textarea - id: solution - attributes: - label: Describe the solution you'd like - description: A clear and concise description of what you want to happen. - validations: - required: true - - - type: textarea - id: alternatives - attributes: - label: Describe alternatives you've considered - description: A clear and concise description of any alternative solutions or features you've considered. - - - type: textarea - id: additional-context - attributes: - label: Additional context - description: Add any other context or screenshots here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 095ddb1160..0000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,18 +0,0 @@ -### What does this PR do? - -### Screenshot / video of UI - - - -### What issues does this PR fix or reference? - - - -### How to test this PR? - - - -- [ ] Tests are covering the bug fix or the new feature diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5218f27a5c..1f629f5c77 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,69 +1,61 @@ -# Set update schedule for GitHub Actions +# +# Copyright (C) 2025 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" - # check at 3am UTC - time: "03:00" - open-pull-requests-limit: 20 - - package-ecosystem: "npm" - directory: "/" + directory: "/" # adjust if your package.json is in a subfolder schedule: - interval: daily - # check at 3am UTC - time: "03:00" - open-pull-requests-limit: 20 + interval: "daily" + # Only allow updates for these specific packages + allow: + - dependency-name: "@ai-sdk/*" + dependency-type: "all" + - dependency-name: "@modelcontextprotocol/sdk" + dependency-type: "all" + - dependency-name: "@oslojs/*" + dependency-type: "all" + - dependency-name: "@types/better-sqlite3" + dependency-type: "all" + - dependency-name: "@types/mime-types" + dependency-type: "all" + - dependency-name: "@types/ms" + dependency-type: "all" + - dependency-name: "ai" + dependency-type: "all" + - dependency-name: "better-sqlite3" + dependency-type: "all" + - dependency-name: "bcrypt-ts" + dependency-type: "all" + - dependency-name: "drizzle-orm" + dependency-type: "all" + - dependency-name: "mime-types" + dependency-type: "all" + - dependency-name: "ms" + dependency-type: "all" + - dependency-name: "neverflow" + dependency-type: "all" + - dependency-name: "openapi-typescript" + dependency-type: "all" + - dependency-name: "random-words" + dependency-type: "all" groups: - argosci: - applies-to: version-updates - patterns: - - "@argos-ci/*" - storybook: - applies-to: version-updates - patterns: - - "@storybook/*" - - "storybook" - - "storybook-dark-mode" - commitlint: - applies-to: version-updates - patterns: - - "@commitlint/*" - docusaurus: - applies-to: version-updates - patterns: - - "@docusaurus/*" - eslint: + ai-sdk: applies-to: version-updates patterns: - - "eslint" - - "@eslint/*" - fortawesome: - applies-to: version-updates - patterns: - - "@fortawesome/*" - typescript-eslint: - applies-to: version-updates - patterns: - - "@typescript-eslint/*" - - "typescript-eslint" - tailwindcss: - applies-to: version-updates - patterns: - - "@tailwindcss/*" - - "tailwindcss" - vitest: - applies-to: version-updates - patterns: - - "@vitest/*" - - "vitest" - typedoc: - applies-to: version-updates - patterns: - - "typedoc" - - "typedoc-plugin-markdown" - - "docusaurus-plugin-typedoc" - + - "@ai-sdk/mcp" + - "ai" diff --git a/.github/semantic.yml b/.github/semantic.yml deleted file mode 100644 index 857c9eff59..0000000000 --- a/.github/semantic.yml +++ /dev/null @@ -1,3 +0,0 @@ -enabled: true -titleAndCommits: true -allowMergeCommits: true diff --git a/.github/workflows/argos.yaml b/.github/workflows/argos.yaml deleted file mode 100644 index abdf098412..0000000000 --- a/.github/workflows/argos.yaml +++ /dev/null @@ -1,81 +0,0 @@ -# -# Copyright (C) 2023-2024 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Argos CI Screenshots - -on: - workflow_dispatch: - push: - branches: - - main - paths: - - 'package.json' - - 'pnpm-lock.yaml' - - 'storybook/**' - - 'website/**' - - 'website-argos/**' - pull_request: - branches: - - main - paths: - - '.github/workflows/argos.yaml' - - 'package.json' - - 'pnpm-lock.yaml' - - 'storybook/**' - - 'website/**' - - 'website-argos/**' - -permissions: - contents: read - -jobs: - take-screenshots: - name: take screenshots - runs-on: ubuntu-22.04 - # disable on forks as secrets are not available - if: github.event.repository.fork == false - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Run website - run: pnpm website:build - - - name: Install Playwright browsers - working-directory: website-argos - run: pnpm playwright install --with-deps chromium - - - name: Take screenshots with Playwright - run: pnpm website:screenshots - - - name: Upload screenshots to Argos - continue-on-error: true - working-directory: website-argos - run: pnpm upload - env: - ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} diff --git a/.github/workflows/codecov-next.yaml b/.github/workflows/codecov-next.yaml deleted file mode 100644 index 68bb34e1db..0000000000 --- a/.github/workflows/codecov-next.yaml +++ /dev/null @@ -1,57 +0,0 @@ -# -# Copyright (C) 2025 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Publish codecov report from main branch - -on: - workflow_dispatch: - push: - branches: - - main - -permissions: - contents: read - -jobs: - codecov: - name: Run tests and push coverage result - runs-on: ubuntu-24.04 - # disable on forks as secrets are not available - if: github.event.repository.fork == false - timeout-minutes: 60 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Run unit tests - run: pnpm test:unit:coverage - - - name: publish codecov report - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 - with: - token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/daily-testing-build.yaml b/.github/workflows/daily-testing-build.yaml deleted file mode 100644 index 81e7364df0..0000000000 --- a/.github/workflows/daily-testing-build.yaml +++ /dev/null @@ -1,163 +0,0 @@ -# -# Copyright (C) 2024 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Daily testing build -run-name: Testing build of Podman Desktop from Main branch - -on: - workflow_dispatch: - schedule: - - cron: '0 1 * * *' - -env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - DEBUG: electron-builder - -permissions: - contents: read - -jobs: - tag: - name: Tagging - runs-on: ubuntu-24.04 - outputs: - githubTag: ${{ steps.TAG_UTIL.outputs.githubTag }} - desktopVersion: ${{ steps.TAG_UTIL.outputs.desktopVersion }} - releaseId: ${{ steps.create_release.outputs.id }} - - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - token: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - fetch-depth: 0 - - - name: Generate tag utilities - id: TAG_UTIL - run: | - CURRENT_DAY=$(date +'%Y%m%d') - SHORT_SHA1=$(git rev-parse --short HEAD) - # grab the version from the package.json - PODMAN_DEKSTOP_VERSION=$(jq -r '.version' package.json) - # remove the -next from the version - STRIPPED_VERSION=${PODMAN_DEKSTOP_VERSION%-next} - TAG_PATTERN=${STRIPPED_VERSION}-$(date +'%Y%m%d%H%M')-${SHORT_SHA1} - echo "githubTag=v$TAG_PATTERN" >> ${GITHUB_OUTPUT} - echo "desktopVersion=$TAG_PATTERN" >> ${GITHUB_OUTPUT} - # check for tag existence - exit the workflow - echo "Checking if tag exists: $(git rev-parse -q --verify "$githubTag")" - if [ git rev-parse -q --verify "$githubTag" ]; then - echo "Tag '$githubTag' exists, skipping..." - exit 1; - else - echo "Tag '$githubTag' does not exist yet, continue" - fi - - - name: Create a Tag, commit and push to testing-prereleases - run: | - # quite heavy solution, we might only consider crating a tag, but not actually pushing whole state of the repo - echo "Setting github.actor: ${{ github.actor }} and id: ${{ github.actor_id }}" - git config --local user.name ${{ github.actor }} - git config --local user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ steps.TAG_UTIL.outputs.desktopVersion }}\",#g" package.json - find extensions/* -maxdepth 2 -name "package.json" | xargs -I {} sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ steps.TAG_UTIL.outputs.desktopVersion }}\",#g" {} - # change the repository field to be the prerelease repository in package.json file - sed -i "s#\"repository\":\ \"\(.*\)\",#\"repository\":\ \"https://github.com/podman-desktop/testing-prereleases\",#g" package.json - cat package.json - git add package.json extensions/*/package.json - # get rid of .github/workflows - requires additional permissions - rm -rf .github/workflows/* - git add .github/ - git commit -m "chore: tag ${{ steps.TAG_UTIL.outputs.githubTag }}" - echo "Tagging with ${{ steps.TAG_UTIL.outputs.githubTag }}" - git tag ${{ steps.TAG_UTIL.outputs.githubTag }} - # push tag to the prereleases repository - git remote add prereleases https://github.com/podman-desktop/testing-prereleases - git push prereleases ${{ steps.TAG_UTIL.outputs.githubTag }} - - - name: Create Release - id: create_release - uses: ncipollo/release-action@bcfe5470707e8832e12347755757cec0eb3c22af # v1.18.0 - with: - tag: ${{ steps.TAG_UTIL.outputs.githubTag }} - name: ${{ steps.TAG_UTIL.outputs.githubTag }} - draft: true - prerelease: true - owner: podman-desktop - repo: testing-prereleases - token: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - - build: - name: Build / ${{ matrix.os }} - needs: tag - runs-on: ${{ matrix.os }} - env: - ELECTRON_ENABLE_INSPECT: true - GITHUB_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-24.04, windows-2025, macos-15] - timeout-minutes: 60 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - repository: podman-desktop/testing-prereleases - ref: ${{ needs.tag.outputs.githubTag }} - - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Install Flatpak dependencies - if: startsWith(matrix.os, 'ubuntu') - run: | - sudo apt-get update - sudo apt-get install flatpak -y - sudo apt-get install flatpak-builder -y - sudo apt-get install elfutils -y - flatpak remote-add --if-not-exists flathub --user https://flathub.org/repo/flathub.flatpakrepo - flatpak install flathub --no-static-deltas --user -y org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08 - - - name: Run Build and Compile on all platforms - timeout-minutes: 60 - run: pnpm compile:next - - release: - needs: [tag, build] - name: Release - runs-on: ubuntu-24.04 - steps: - - name: id - run: echo the release id is ${{ needs.tag.outputs.releaseId }} - - - name: Publish release - uses: StuYarrow/publish-release@01f2a1365bacd77bad861873a7fdf274ab49eefd # v1.1.2 - env: - GITHUB_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - with: - id: ${{ needs.tag.outputs.releaseId }} - repo: testing-prereleases - owner: podman-desktop diff --git a/.github/workflows/downloads-count.yaml b/.github/workflows/downloads-count.yaml deleted file mode 100644 index 3abfd2f56d..0000000000 --- a/.github/workflows/downloads-count.yaml +++ /dev/null @@ -1,90 +0,0 @@ -# -# Copyright (C) 2023 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -# Grab downloads - -name: Downloads Count - -on: - schedule: - - cron: '0 0 * * *' - -permissions: - contents: read - -jobs: - - get-all-releases-count: - runs-on: ubuntu-24.04 - - steps: - - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - id: process-downloads - with: - script: | - const resp = await github.paginate('GET /repos/{owner}/{repo}/releases{?per_page,page}', { - owner: 'containers', - repo: 'podman-desktop', - per_page: 100 - }, (response) => response.data.map(({ tag_name, assets }) => ({ tag_name, assets }))); - - const releases = resp; - - // Keep only tag_name and asset.name and asset.download_count from releases - releases.forEach((release) => { release.assets = release.assets.map(({ name, download_count }) => ({ name, download_count })) }) - - let totalMac = 0; - let totalWin = 0; - let totalLinux = 0; - - // count the total number of downloads for each release for mac,windows,linux assests - releases.forEach((release) => { - release.assets = release.assets.filter((asset) => { - if (asset.name.endsWith(".dmg") || (asset.name.endsWith("arm64.zip") || asset.name.endsWith("x64.zip") || asset.name.endsWith("universal.zip")) ){ - totalMac += asset.download_count; - } else if (asset.name.endsWith(".exe")){ - totalWin += asset.download_count; - } else if (asset.name.endsWith(".tar.gz") || asset.name.endsWith(".flatpak")){ - totalLinux+=asset.download_count; - } - }) - }) - - let totalDownloads = totalMac + totalWin + totalLinux; - - // Export results as environment variables - core.exportVariable('totalDownloads', totalDownloads); - core.exportVariable('totalMac', totalMac); - core.exportVariable('totalWin', totalWin); - core.exportVariable('totalLinux', totalLinux); - - - name: send-event-4 - run: | - curl https://api.segment.io/v1/track \ - -X POST \ - -H 'Content-Type: application/json' \ - -d '{ - "userId": "gh-action", - "event": "download-count", - "properties": { - "mac": "${{ env.totalMac }}", - "windows": "${{ env.totalWin }}", - "linux": "${{ env.totalLinux }}", - "total": "${{ env.totalDownloads }}" - } - }' \ - -u ${{ secrets.SEGMENT_WRITE_KEY }} diff --git a/.github/workflows/e2e-kubernetes-main.yaml b/.github/workflows/e2e-kubernetes-main.yaml deleted file mode 100644 index 02199ec295..0000000000 --- a/.github/workflows/e2e-kubernetes-main.yaml +++ /dev/null @@ -1,156 +0,0 @@ -# -# Copyright (C) 2023-2024 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: e2e-kubernetes-tests-main - -on: - push: - branches: [main] - - workflow_dispatch: - inputs: - organization: - default: 'podman-desktop' - description: 'Organization of the Podman Desktop repository' - type: string - required: true - repositoryName: - default: 'podman-desktop' - description: 'Podman Desktop repository name' - type: string - required: true - branch: - default: 'main' - description: 'Podman Desktop repo branch' - type: string - required: true - kind_provider: - description: 'Kind cluster provider type' - type: choice - options: - - docker - - podman - required: true - -permissions: - contents: read - -jobs: - e2e-tests: - name: Run All E2E tests - runs-on: ubuntu-24.04 - # disable on forks as secrets are not available - if: github.event.repository.fork == false - permissions: - checks: write # required for mikepenz/action-junit-report - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - repository: ${{ github.event.inputs.organization }}/${{ github.event.inputs.repositoryName }} - ref: ${{ github.event.inputs.branch }} - if: github.event_name == 'workflow_dispatch' - - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - if: github.event_name == 'push' - - - name: Set the default provider type variable - env: - DEFAULT_KIND_PROVIDER: 'docker' - run: | - echo "KIND_PROVIDER=${{ github.event.inputs.kind_provider || env.DEFAULT_KIND_PROVIDER }}" >> $GITHUB_ENV - - - name: Update podman - run: | - echo "ubuntu version from kubic repository to install podman we need (v5)" - ubuntu_version='23.10' - echo "Add unstable kubic repo into list of available sources and get the repo key" - sudo sh -c "echo 'deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list" - curl -L "https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/Release.key" | sudo apt-key add - - echo "Updating all dependencies..." - sudo apt-get update -qq - echo "install necessary dependencies for criu package which is not part of ${ubuntu_version}" - sudo apt-get install -qq libprotobuf32t64 python3-protobuf libnet1 - echo "install criu manually from static location" - curl -sLO http://archive.ubuntu.com/ubuntu/pool/universe/c/criu/criu_3.16.1-2_amd64.deb && sudo dpkg -i criu_3.16.1-2_amd64.deb - echo "installing/update podman package..." - sudo apt-get -qq -y install podman || { echo "Start fallback steps for podman nightly installation from a static mirror" && \ - sudo sh -c "echo 'deb http://ftp.lysator.liu.se/pub/opensuse/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list" && \ - curl -L "http://ftp.lysator.liu.se/pub/opensuse/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/Release.key" | sudo apt-key add - && \ - sudo apt-get update && \ - sudo apt-get -y install podman; } - podman version - - - name: Revert unprivileged user namespace restrictions in Ubuntu 24.04 - run: | - # allow unprivileged user namespace - sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - - - name: Set cgroup_manager to 'cgroupfs' instead of systemd - run: | - mkdir -p ~/.config/containers - cat <> ~/.config/containers/containers.conf - [engine] - cgroup_manager="cgroupfs" - EOT - podman info - - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Run E2E kubernetes tests in Production Mode - env: - PODMANDESKTOP_CI_BOT_TOKEN: ${{ secrets.PODMANDESKTOP_CI_BOT_TOKEN }} - TEST_PODMAN_MACHINE: 'true' - SKIP_KIND_INSTALL: 'true' - KIND_PROVIDER_GHA: ${{ env.KIND_PROVIDER }} - ELECTRON_ENABLE_INSPECT: true - run: | - echo "Compiling the Podman Desktop in production mode" - pnpm compile:current --linux dir - path=$(realpath ./dist/linux-unpacked/podman-desktop) - echo "Podman Desktop built binary: $path" - export PODMAN_DESKTOP_BINARY_PATH=$path - pnpm test:e2e:k8s - - - name: Publish Test Report - uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 - if: always() # always run even if the previous step fails - with: - fail_on_failure: true - include_passed: true - detailed_summary: true - annotate_only: true - require_tests: true - report_paths: '**/*results.xml' - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - if: always() - with: - name: k8s-e2e-tests - path: | - ./tests/**/output/ - !./tests/**/traces/raw diff --git a/.github/workflows/e2e-main.yaml b/.github/workflows/e2e-main.yaml deleted file mode 100644 index 248442569f..0000000000 --- a/.github/workflows/e2e-main.yaml +++ /dev/null @@ -1,338 +0,0 @@ -# -# Copyright (C) 2023-2024 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: e2e-tests-main - -on: - push: - branches: [main] - - workflow_dispatch: - inputs: - organization: - default: 'podman-desktop' - description: 'Organization of the Podman Desktop repository' - type: string - required: true - repositoryName: - default: 'podman-desktop' - description: 'Podman Desktop repository name' - type: string - required: true - npm_target: - default: 'test:e2e' - description: 'The npm target to run tests. Use "test:e2e:all" to run all test suites, including Kubernetes tests.' - type: string - required: true - branch: - default: 'main' - description: 'Podman Desktop repo branch' - type: string - required: true - kind_provider: - description: 'Kind cluster provider type' - type: choice - options: - - docker - - podman - required: false - update_with_extensions: - description: 'Installation extensions before update' - type: choice - options: - - 'false' - - 'true' - required: false - -permissions: - contents: read - -jobs: - e2e-tests: - name: Run All E2E tests - runs-on: ubuntu-24.04 - # disable on forks as secrets are not available - if: github.event.repository.fork == false - permissions: - checks: write # required for mikepenz/action-junit-report - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - repository: ${{ github.event.inputs.organization }}/${{ github.event.inputs.repositoryName }} - ref: ${{ github.event.inputs.branch }} - if: github.event_name == 'workflow_dispatch' - - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - if: github.event_name == 'push' - - - name: Set the default env. variables - env: - DEFAULT_NPM_TARGET: 'test:e2e' - DEFAULT_KIND_PROVIDER: 'docker' - run: | - echo "NPM_TARGET=${{ github.event.inputs.npm_target || env.DEFAULT_NPM_TARGET }}" >> $GITHUB_ENV - echo "KIND_PROVIDER=${{ github.event.inputs.kind_provider || env.DEFAULT_KIND_PROVIDER }}" >> $GITHUB_ENV - - - name: Update podman - run: | - echo "ubuntu version from kubic repository to install podman we need (v5)" - ubuntu_version='23.10' - echo "Add unstable kubic repo into list of available sources and get the repo key" - sudo sh -c "echo 'deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list" - curl -L "https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/Release.key" | sudo apt-key add - - echo "Updating all dependencies..." - sudo apt-get update -qq - echo "install necessary dependencies for criu package which is not part of ${ubuntu_version}" - sudo apt-get install -qq libprotobuf32t64 python3-protobuf libnet1 - echo "install criu manually from static location" - curl -sLO http://archive.ubuntu.com/ubuntu/pool/universe/c/criu/criu_3.16.1-2_amd64.deb && sudo dpkg -i criu_3.16.1-2_amd64.deb - echo "installing/update podman package..." - sudo apt-get -qq -y install podman || { echo "Start fallback steps for podman nightly installation from a static mirror" && \ - sudo sh -c "echo 'deb http://ftp.lysator.liu.se/pub/opensuse/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list" && \ - curl -L "http://ftp.lysator.liu.se/pub/opensuse/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/Release.key" | sudo apt-key add - && \ - sudo apt-get update && \ - sudo apt-get -y install podman; } - podman version - - - name: Revert unprivileged user namespace restrictions in Ubuntu 24.04 - run: | - # allow unprivileged user namespace - sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - - # Enables multi-architecture support for building multi-arch manifests - - name: Install QEMU for Multi-Arch - run: | - sudo apt install -y qemu-user-static - - - name: Set cgroup_manager to 'cgroupfs' instead of systemd - run: | - mkdir -p ~/.config/containers - cat <> ~/.config/containers/containers.conf - [engine] - cgroup_manager="cgroupfs" - EOT - podman info - - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Run E2E tests in Production Mode - env: - PODMANDESKTOP_CI_BOT_TOKEN: ${{ secrets.PODMANDESKTOP_CI_BOT_TOKEN }} - TEST_PODMAN_MACHINE: 'true' - SKIP_KIND_INSTALL: 'true' - KIND_PROVIDER_GHA: ${{ env.KIND_PROVIDER }} - ELECTRON_ENABLE_INSPECT: true - run: | - echo "Compiling the Podman Desktop in production mode" - pnpm compile:current --linux dir - path=$(realpath ./dist/linux-unpacked/podman-desktop) - echo "Podman Desktop built binary: $path" - export PODMAN_DESKTOP_BINARY=$path - pnpm ${{ env.NPM_TARGET }} - - - name: Publish Test Report - uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 - if: always() # always run even if the previous step fails - with: - fail_on_failure: true - include_passed: true - detailed_summary: true - annotate_only: true - require_tests: true - report_paths: '**/*results.xml' - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - if: always() - with: - name: e2e-tests - path: | - ./tests/**/output/ - !./tests/**/traces/raw - - win-update-e2e-test: - name: win update e2e tests - ${{ matrix.installation }} - runs-on: windows-2025 - # disable on forks as secrets are not available - if: github.event.repository.fork == false - permissions: - checks: write # required for mikepenz/action-junit-report - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - timeout-minutes: 60 - strategy: - fail-fast: false - matrix: - installation: ['vanilla', 'custom-extensions'] - exclude: - - installation: ${{ (github.event.inputs.update_with_extensions && github.event.inputs.update_with_extensions == 'true') && 'N/A' || 'custom-extensions' }} - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - repository: ${{ github.event.inputs.organization }}/${{ github.event.inputs.repositoryName }} - ref: ${{ github.event.inputs.branch }} - if: github.event_name == 'workflow_dispatch' - - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - if: github.event_name == 'push' - - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Adjust/Downgrade local podman desktop version Windows - run: | - $version="1.15.0" - jq --arg version "$version" '.version = $version' package.json | Out-File -FilePath package.json_tmp - Move-Item -Path package.json_tmp -Destination package.json -Force - - - name: Build Podman Desktop locally with electron updater included - env: - ELECTRON_ENABLE_INSPECT: true - run: | - pnpm compile:current --win nsis - $path=('./dist/win-unpacked/Podman Desktop.exe' | resolve-path).ProviderPath - echo $path - echo ("PODMAN_DESKTOP_BINARY=" + $path) >> $env:GITHUB_ENV - - - name: Run E2E Update test - env: - INSTALLATION_TYPE: ${{ matrix.installation }} - run: | - echo "${{ env.PODMAN_DESKTOP_BINARY }}" - echo "${{ env.INSTALLATION_TYPE }}" - pnpm test:e2e:update:run - - - name: Publish Test Report - uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 - if: always() # always run even if the previous step fails - with: - fail_on_failure: true - include_passed: true - detailed_summary: true - annotate_only: true - require_tests: true - report_paths: '**/*results.xml' - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - if: always() - with: - name: update-e2e-test-${{ matrix.installation }} - path: | - ./tests/**/output/ - !./tests/**/traces/raw - - mac-update-e2e-test: - name: mac update e2e tests - runs-on: macos-15 - # disable on forks as secrets are not available - if: github.event.repository.fork == false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - timeout-minutes: 60 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - repository: ${{ github.event.inputs.organization }}/${{ github.event.inputs.repositoryName }} - ref: ${{ github.event.inputs.branch }} - if: github.event_name == 'workflow_dispatch' - - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - if: github.event_name == 'push' - - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Adjust/Downgrade local podman desktop version - run: | - version="1.15.0" - jq --arg version "$version" '.version = $version' package.json > package.json_tmp - mv package.json_tmp package.json - - - name: Build Podman Desktop locally with electron updater included - env: - ELECTRON_ENABLE_INSPECT: true - run: | - pnpm compile:current --mac dmg - dmgPath=$(realpath $(find ./dist -name "*.dmg")) - echo "DMG Path: $dmgPath" - # extract the dmg - hdiutil attach $dmgPath - pdVolumePath=$(find /Volumes -name *1.15.0*) - echo "PD Volume path: $pdVolumePath" - sudo cp -R "$pdVolumePath/Podman Desktop.app" /Applications - # codesign the extracted app - appPath="/Applications/Podman Desktop.app" - sudo codesign --force --deep --sign - "$appPath" - codesign --verify --deep --verbose=2 "$appPath" - binaryPath="$appPath/Contents/MacOS/Podman Desktop" - echo "PODMAN_DESKTOP_BINARY=$binaryPath" >> $GITHUB_ENV - - - name: Run E2E Update test - env: - UPDATE_PODMAN_DESKTOP: true - run: | - echo "${{ env.PODMAN_DESKTOP_BINARY }}" - pnpm test:e2e:update:run - - - name: Publish Test Report - uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 - if: always() # always run even if the previous step fails - with: - fail_on_failure: true - include_passed: true - detailed_summary: true - annotate_only: true - require_tests: true - report_paths: '**/*results.xml' - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - if: always() - with: - name: mac-update-e2e-test - path: | - ./tests/**/output/ - !./tests/**/traces/raw diff --git a/.github/workflows/e2e-test-job.yaml b/.github/workflows/e2e-test-job.yaml new file mode 100644 index 0000000000..5c4b315731 --- /dev/null +++ b/.github/workflows/e2e-test-job.yaml @@ -0,0 +1,167 @@ +# +# Copyright (C) 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +name: E2E Test Job + +on: + workflow_call: + inputs: + instance: + description: 'GitHub Actions runner instance (e.g., macos-latest, windows-2025, ubuntu-24.04)' + required: true + type: string + mode: + description: 'Test mode (prod or dev)' + required: true + type: string + compile_args: + description: 'Arguments for pnpm compile:current (e.g., --win dir, --linux dir, --mac dir)' + required: true + type: string + +jobs: + e2e-test: + name: ${{ inputs.instance }} + runs-on: ${{ inputs.instance }} + timeout-minutes: 60 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GEMINI_API_KEY: ${{ !startsWith(inputs.instance, 'ubuntu') && secrets.GEMINI_API_KEY || '' }} + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda + name: Install pnpm + with: + run_install: false + + - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 + with: + node-version: 22 + cache: 'pnpm' + + - name: Execute pnpm + run: pnpm install + + - name: Setup Ollama + if: runner.os == 'Linux' + uses: ai-action/setup-ollama@7c1d2c2a6e6c4f1909bf1d099acda773ca36401d # v1 + + - name: Cache Ollama models + if: runner.os == 'Linux' + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + id: ollama-cache + with: + path: ~/.ollama/models + key: ollama-gemma3-1b-${{ runner.os }} + + - name: Pull Ollama model + if: runner.os == 'Linux' + shell: bash + run: | + # Wait for Ollama server to be ready (up to 60 seconds) + for i in {1..30}; do + if ollama list &> /dev/null; then + echo "Ollama server is ready" + break + fi + echo "Waiting for Ollama server... (attempt $i/30)" + sleep 2 + if [ "$i" -eq 30 ]; then + echo "Ollama server failed to start" + exit 1 + fi + done + + # Check if model is already cached and available + if ollama list | grep -q gemma3:1b; then + echo "Model gemma3:1b is already available (from cache)" + echo "OLLAMA_ENABLED=true" >> "$GITHUB_ENV" + exit 0 + fi + + # Pull the model with retries (digest mismatch is a known Ollama issue) + for attempt in {1..5}; do + echo "Pulling gemma3:1b model (attempt $attempt/5)..." + ollama rm gemma3:1b 2>/dev/null || true + + if ollama pull gemma3:1b 2>&1 && ollama list | grep -q gemma3:1b; then + echo "Model gemma3:1b is ready" + echo "OLLAMA_ENABLED=true" >> "$GITHUB_ENV" + exit 0 + fi + + wait_time=$((attempt * 10)) + echo "Pull failed, retrying in ${wait_time} seconds..." + sleep $wait_time + done + + echo "::warning::Failed to pull gemma3:1b model after 5 attempts - Ollama tests will be skipped" + + - name: Run Smoke E2E tests in ${{ inputs.mode == 'prod' && 'Production' || 'Development' }} Mode + shell: bash + run: | + # Derive binary path from instance (OS and architecture) + INSTANCE="${{ inputs.instance }}" + if [[ "$INSTANCE" == *"windows"* ]]; then + if [[ "$INSTANCE" == *"arm"* ]]; then + BINARY_PATH="./dist/win-arm64-unpacked/Kortex.exe" + else + BINARY_PATH="./dist/win-unpacked/Kortex.exe" + fi + elif [[ "$INSTANCE" == *"ubuntu"* ]] || [[ "$INSTANCE" == *"linux"* ]]; then + BINARY_PATH="./dist/linux-unpacked/kortex" + elif [[ "$INSTANCE" == *"macos"* ]]; then + if [[ "$INSTANCE" == *"intel"* ]]; then + BINARY_PATH="./dist/mac/Kortex.app/Contents/MacOS/Kortex" + else + BINARY_PATH="./dist/mac-arm64/Kortex.app/Contents/MacOS/Kortex" + fi + else + echo "::error::Unknown instance: $INSTANCE. Cannot determine binary path." + exit 1 + fi + + if [ "${{ inputs.mode }}" == "prod" ]; then + echo "Compiling Kortex in production mode" + export ELECTRON_ENABLE_INSPECT=true + pnpm compile:current ${{ inputs.compile_args }} + + path=$(realpath "$BINARY_PATH") + echo "Kortex built binary: $path" + export KORTEX_BINARY="$path" + fi + pnpm test:e2e + + - name: Publish Test Report + uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5 + if: always() + with: + annotate_only: true + fail_on_failure: true + include_passed: true + detailed_summary: true + require_tests: true + report_paths: '**/*results.xml' + + - name: Upload test artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: always() + with: + name: results-e2e-${{ inputs.instance }}-${{ inputs.mode }} + path: tests/playwright/output/** + retention-days: 7 diff --git a/.github/workflows/merge-group.yaml b/.github/workflows/merge-group.yaml deleted file mode 100644 index 55f6201302..0000000000 --- a/.github/workflows/merge-group.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright (C) 2025 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: merge-queue -on: - merge_group: - -permissions: - contents: read - -jobs: - dco: - name: DCO - runs-on: ubuntu-24.04 - steps: - - run: echo "This DCO job runs on merge_queue event and does not do anything" - semantic: - name: Semantic PR - runs-on: ubuntu-24.04 - steps: - - run: echo "This DCO job runs on merge_queue event and does not do anything" diff --git a/.github/workflows/next-build.yaml b/.github/workflows/next-build.yaml index 2ee680cf1e..ab6d7e59cd 100644 --- a/.github/workflows/next-build.yaml +++ b/.github/workflows/next-build.yaml @@ -1,5 +1,5 @@ # -# Copyright (C) 2022-2024 Red Hat, Inc. +# Copyright (C) 2022-2025 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -39,13 +39,13 @@ jobs: if: github.event.repository.fork == false outputs: githubTag: ${{ steps.TAG_UTIL.outputs.githubTag}} - desktopVersion: ${{ steps.TAG_UTIL.outputs.desktopVersion}} + kortexVersion: ${{ steps.TAG_UTIL.outputs.kortexVersion}} releaseId: ${{ steps.create_release.outputs.id}} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: - token: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} + token: ${{ secrets.KORTEX_BOT_TOKEN }} fetch-depth: 0 - name: Generate tag utilities id: TAG_UTIL @@ -53,40 +53,40 @@ jobs: CURRENT_DAY=$(date +'%Y%m%d') SHORT_SHA1=$(git rev-parse --short HEAD) # grab the version from the package.json - PODMAN_DEKSTOP_VERSION=$(jq -r '.version' package.json) + KORTEX_VERSION=$(jq -r '.version' package.json) # remove the -next from the version - STRIPPED_VERSION=${PODMAN_DEKSTOP_VERSION%-next} - TAG_PATTERN=${STRIPPED_VERSION}-$(date +'%Y%m%d%H%M')-${SHORT_SHA1} + STRIPPED_VERSION=${KORTEX_VERSION%-next} + TAG_PATTERN=${STRIPPED_VERSION}-next.$(date +'%Y%m%d%H%M')-${SHORT_SHA1} echo "githubTag=v$TAG_PATTERN" >> ${GITHUB_OUTPUT} - echo "desktopVersion=$TAG_PATTERN" >> ${GITHUB_OUTPUT} + echo "kortexVersion=$TAG_PATTERN" >> ${GITHUB_OUTPUT} - name: tag run: | git config --local user.name ${{ github.actor }} git config --local user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ steps.TAG_UTIL.outputs.desktopVersion }}\",#g" package.json - find extensions/* -maxdepth 2 -name "package.json" | xargs -I {} sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ steps.TAG_UTIL.outputs.desktopVersion }}\",#g" {} + sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ steps.TAG_UTIL.outputs.kortexVersion }}\",#g" package.json + find extensions/* -maxdepth 2 -name "package.json" | xargs -I {} sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ steps.TAG_UTIL.outputs.kortexVersion }}\",#g" {} # change the repository field to be the prerelease repository in package.json file - sed -i "s#\"repository\":\ \"\(.*\)\",#\"repository\":\ \"https://github.com/podman-desktop/prereleases\",#g" package.json + sed -i "s#\"repository\":\ \"\(.*\)\",#\"repository\":\ \"https://github.com/kortex-hub/prereleases\",#g" package.json cat package.json git add package.json extensions/*/package.json git commit -m "chore: tag ${{ steps.TAG_UTIL.outputs.githubTag }}" echo "Tagging with ${{ steps.TAG_UTIL.outputs.githubTag }}" git tag ${{ steps.TAG_UTIL.outputs.githubTag }} # push tag to the prereleases repository - git remote add prereleases https://github.com/podman-desktop/prereleases + git remote add prereleases https://github.com/kortex-hub/prereleases git push prereleases ${{ steps.TAG_UTIL.outputs.githubTag }} - name: Create Release id: create_release - uses: ncipollo/release-action@bcfe5470707e8832e12347755757cec0eb3c22af # v1.18.0 + uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0 with: tag: ${{ steps.TAG_UTIL.outputs.githubTag }} name: ${{ steps.TAG_UTIL.outputs.githubTag }} draft: true prerelease: true - owner: containers - repo: podman-desktop-prereleases - token: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} + owner: kortex-hub + repo: prereleases + token: ${{ secrets.KORTEX_BOT_TOKEN }} build: name: Build / ${{ matrix.os }} @@ -98,9 +98,9 @@ jobs: os: [windows-2025, ubuntu-24.04, macos-15] timeout-minutes: 60 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: - repository: podman-desktop/prereleases + repository: kortex-hub/prereleases ref: ${{ needs.tag.outputs.githubTag}} - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 @@ -108,18 +108,18 @@ jobs: with: run_install: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: 22 cache: 'pnpm' - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: "3.x" - - name: setup telemetry key for production - run: | - sed -i -r -e "s/SEGMENT_KEY = '.*'/SEGMENT_KEY = '${{ secrets.SEGMENT_WRITE_KEY }}'/" packages/main/src/plugin/telemetry/telemetry.ts + #- name: setup telemetry key for production + # run: | + # sed -i -r -e "s/SEGMENT_KEY = '.*'/SEGMENT_KEY = '${{ secrets.SEGMENT_WRITE_KEY }}'/" packages/main/src/plugin/telemetry/telemetry.ts - name: Execute pnpm run: pnpm install @@ -143,21 +143,21 @@ jobs: echo "APPLE_APP_SPECIFIC_PASSWORD=${{secrets.APPLE_APP_SPECIFIC_PASSWORD}}" >> $GITHUB_ENV echo "APPLE_TEAM_ID=${{secrets.APPLE_TEAM_ID}}" >> $GITHUB_ENV - - name: Install Azure SignTool on Windows - if: startsWith(matrix.os, 'windows') - run: | - dotnet tool install --global AzureSignTool --version 3.0.0 - echo "AZURE_KEY_VAULT_CERTIFICATE=${{secrets.AZURE_KEY_VAULT_CERTIFICATE}}" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "AZURE_KEY_VAULT_CLIENT_ID=${{secrets.AZURE_KEY_VAULT_CLIENT_ID}}" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "AZURE_KEY_VAULT_SECRET=${{secrets.AZURE_KEY_VAULT_SECRET}}" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "AZURE_KEY_VAULT_TENANT_ID=${{secrets.AZURE_KEY_VAULT_TENANT_ID}}" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "AZURE_KEY_VAULT_URL=${{secrets.AZURE_KEY_VAULT_URL}}" | Out-File -FilePath $env:GITHUB_ENV -Append + #- name: Install Azure SignTool on Windows + # if: startsWith(matrix.os, 'windows') + # run: | + # dotnet tool install --global AzureSignTool --version 3.0.0 + # echo "AZURE_KEY_VAULT_CERTIFICATE=${{secrets.AZURE_KEY_VAULT_CERTIFICATE}}" | Out-File -FilePath $env:GITHUB_ENV -Append + # echo "AZURE_KEY_VAULT_CLIENT_ID=${{secrets.AZURE_KEY_VAULT_CLIENT_ID}}" | Out-File -FilePath $env:GITHUB_ENV -Append + # echo "AZURE_KEY_VAULT_SECRET=${{secrets.AZURE_KEY_VAULT_SECRET}}" | Out-File -FilePath $env:GITHUB_ENV -Append + # echo "AZURE_KEY_VAULT_TENANT_ID=${{secrets.AZURE_KEY_VAULT_TENANT_ID}}" | Out-File -FilePath $env:GITHUB_ENV -Append + # echo "AZURE_KEY_VAULT_URL=${{secrets.AZURE_KEY_VAULT_URL}}" | Out-File -FilePath $env:GITHUB_ENV -Append - name: Run Build timeout-minutes: 40 run: pnpm compile:next env: - GITHUB_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} + GITHUB_TOKEN: ${{ secrets.KORTEX_BOT_TOKEN }} release: needs: [tag, build] @@ -170,11 +170,11 @@ jobs: - name: Publish release uses: StuYarrow/publish-release@01f2a1365bacd77bad861873a7fdf274ab49eefd # v1.1.2 env: - GITHUB_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} + GITHUB_TOKEN: ${{ secrets.KORTEX_BOT_TOKEN }} with: id: ${{ needs.tag.outputs.releaseId}} - repo: podman-desktop-prereleases - owner: podman-desktop + repo: prereleases + owner: kortex-hub publish: needs: [tag, release] @@ -183,50 +183,22 @@ jobs: permissions: id-token: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 name: Install pnpm with: run_install: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22 + node-version: 24 cache: 'pnpm' - name: Execute pnpm run: pnpm install - - name: Set-up npmjs auth token - run: printf "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}\n" >> ~/.npmrc - - - name: Publish API to npmjs + - name: Publish @kortex-app/api to npmjs run: | - echo "Using version ${{ needs.tag.outputs.desktopVersion }}" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ needs.tag.outputs.desktopVersion }}\",#g" packages/extension-api/package.json + echo "Using version ${{ needs.tag.outputs.kortexVersion }}" + sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ needs.tag.outputs.kortexVersion }}\",#g" packages/extension-api/package.json cd packages/extension-api && pnpm publish --provenance --tag next --no-git-checks --access public - - - name: Publish Webview API to npmjs - run: | - echo "Using version ${{ needs.tag.outputs.desktopVersion }}" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ needs.tag.outputs.desktopVersion }}\",#g" packages/webview-api/package.json - cd packages/webview-api && pnpm publish --provenance --tag next --no-git-checks --access public - - - name: Publish ui/svelte to npmjs - run: | - echo "Using version ${{ needs.tag.outputs.desktopVersion }}" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ needs.tag.outputs.desktopVersion }}\",#g" packages/ui/package.json - cd packages/ui && pnpm build && pnpm publish --provenance --tag next --no-git-checks --access public - - - name: Publish tests-playwright to npmjs - run: | - echo "Using version ${{ needs.tag.outputs.desktopVersion }}" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ needs.tag.outputs.desktopVersion }}\",#g" tests/playwright/package.json - cd tests/playwright && pnpm build && pnpm publish --provenance --tag next --no-git-checks --access public - - - name: Publish Podman Extension API to npmjs - run: | - echo "Using version ${{ needs.tag.outputs.desktopVersion }}" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ needs.tag.outputs.desktopVersion }}\",#g" extensions/podman/packages/api/package.json - cd extensions/podman/packages/api - pnpm publish --provenance --tag next --no-git-checks --access public diff --git a/.github/workflows/nightly-e2e.yaml b/.github/workflows/nightly-e2e.yaml new file mode 100644 index 0000000000..3cc6db3ee1 --- /dev/null +++ b/.github/workflows/nightly-e2e.yaml @@ -0,0 +1,77 @@ +# +# Copyright (C) 2025 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +name: nightly e2e tests + +on: + schedule: + - cron: '0 2 * * *' + workflow_dispatch: + inputs: + platform: + description: 'Platform to run tests on' + required: false + default: 'all' + type: choice + options: + - all + - windows + - macos + - linux + +jobs: + windows-e2e-tests: + if: ${{ github.event_name == 'schedule' || github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }} + strategy: + fail-fast: false + matrix: + os: [windows-2025, windows-11-arm] + mode: [prod, dev] + uses: ./.github/workflows/e2e-test-job.yaml + with: + instance: ${{ matrix.os }} + mode: ${{ matrix.mode }} + compile_args: '--win dir' + secrets: inherit + + linux-e2e-tests: + if: ${{ github.event_name == 'schedule' || github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04] + mode: [prod, dev] + uses: ./.github/workflows/e2e-test-job.yaml + with: + instance: ${{ matrix.os }} + mode: ${{ matrix.mode }} + compile_args: '--linux dir' + secrets: inherit + + macos-e2e-tests: + if: ${{ github.event_name == 'schedule' || github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }} + strategy: + fail-fast: false + matrix: + os: [macos-15-intel, macos-26] + mode: [prod, dev] + uses: ./.github/workflows/e2e-test-job.yaml + with: + instance: ${{ matrix.os }} + mode: ${{ matrix.mode }} + compile_args: '--mac dir' + secrets: inherit diff --git a/.github/workflows/pr-check.yaml b/.github/workflows/pr-check.yaml index b54c50548c..76b438f2c7 100644 --- a/.github/workflows/pr-check.yaml +++ b/.github/workflows/pr-check.yaml @@ -1,5 +1,5 @@ # -# Copyright (C) 2022-2024 Red Hat, Inc. +# Copyright (C) 2022-2025 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -38,14 +38,14 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 name: Install pnpm with: run_install: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: 22 cache: 'pnpm' @@ -63,22 +63,22 @@ jobs: - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: windows-installer-x64 - path: ./dist/podman-desktop*-setup-x64.exe + path: ./dist/kortex-*-setup-x64.exe - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: windows-installer-arm64 - path: ./dist/podman-desktop*-setup-arm64.exe + path: ./dist/kortex-*-setup-arm64.exe - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: windows-exe-x64 - path: ./dist/podman-desktop*-next-x64.exe + path: ./dist/kortex*-next-x64.exe - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: windows-exe-arm64 - path: ./dist/podman-desktop*-next-arm64.exe + path: ./dist/kortex*-next-arm64.exe linux: if: ${{ contains(github.event.pull_request.labels.*.name, 'area/ci') || !github.event.pull_request.draft }} @@ -88,14 +88,14 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 name: Install pnpm with: run_install: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: 22 cache: 'pnpm' @@ -110,7 +110,7 @@ jobs: sudo apt-get install flatpak-builder -y sudo apt-get install elfutils -y flatpak remote-add --if-not-exists flathub --user https://flathub.org/repo/flathub.flatpakrepo - flatpak install flathub --no-static-deltas --user -y org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08 + flatpak install flathub --user -y org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08 - name: Run Build timeout-minutes: 20 @@ -122,12 +122,12 @@ jobs: - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: linux - path: ./dist/podman-desktop-*.tar.gz + path: ./dist/kortex-*.tar.gz - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: flatpak - path: ./dist/podman-desktop-*.flatpak + path: ./dist/kortex-*.flatpak darwin: if: ${{ contains(github.event.pull_request.labels.*.name, 'area/ci') || !github.event.pull_request.draft }} @@ -137,14 +137,14 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 name: Install pnpm with: run_install: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: 22 cache: 'pnpm' @@ -162,51 +162,15 @@ jobs: - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: macos-x64-dmg - path: ./dist/podman-desktop-*x64.dmg + path: ./dist/kortex-*x64.dmg - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: macos-arm64-dmg - path: ./dist/podman-desktop-*arm64.dmg + path: ./dist/kortex-*arm64.dmg - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: macos-universal-dmg - path: ./dist/podman-desktop-*universal.dmg - - website-build: - if: ${{ (contains(github.event.pull_request.labels.*.name, 'area/ci') || !github.event.pull_request.draft) && github.event.pull_request.base.ref == 'main' }} - name: build website - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Run website - run: pnpm website:build - - - name: Store pull request details for publish-cloudflare - run: | - echo "${{ github.event.number }}" > PR_NUMBER - echo "${{ github.event.pull_request.head.sha }}" > PR_SHA - - - name: Upload artifact website-content - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: website-content - path: | - website/build - PR_NUMBER - PR_SHA + path: ./dist/kortex-*universal.dmg lint-format: if: ${{ contains(github.event.pull_request.labels.*.name, 'area/ci') || !github.event.pull_request.draft }} @@ -214,13 +178,13 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 name: Install pnpm with: run_install: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: 22 cache: 'pnpm' @@ -248,13 +212,13 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 name: Install pnpm with: run_install: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: 22 cache: 'pnpm' @@ -278,13 +242,13 @@ jobs: matrix: os: [windows-2025, ubuntu-24.04, macos-15] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 name: Install pnpm with: run_install: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: 22 cache: 'pnpm' @@ -305,103 +269,20 @@ jobs: # publish codecov report if linux - name: publish codecov report if: ${{ matrix.os=='ubuntu-24.04' }} - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 - with: - token: ${{ secrets.CODECOV_TOKEN }} + uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 smoke-e2e-tests: if: ${{ contains(github.event.pull_request.labels.*.name, 'area/ci') || !github.event.pull_request.draft }} - name: smoke e2e tests - runs-on: ubuntu-24.04 strategy: + fail-fast: false matrix: - mode: [production, development] - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: Update podman to 5.x - run: | - echo "ubuntu version from kubic repository to install podman we need (v5)" - ubuntu_version='23.10' - echo "Add unstable kubic repo into list of available sources and get the repo key" - sudo sh -c "echo 'deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list" - curl -L "https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/Release.key" | sudo apt-key add - - echo "Updating all dependencies..." - sudo apt-get update -qq - echo "install necessary dependencies for criu package which is not part of ${ubuntu_version}" - sudo apt-get install --allow-unauthenticated -qq libprotobuf32t64 python3-protobuf libnet1 - echo "install criu manually from static location" - curl -sLO http://archive.ubuntu.com/ubuntu/pool/universe/c/criu/criu_3.16.1-2_amd64.deb && sudo dpkg -i criu_3.16.1-2_amd64.deb - echo "installing/update podman package..." - sudo apt-get -qq -y install --allow-unauthenticated podman || { echo "Start fallback steps for podman nightly installation from a static mirror" && \ - sudo sh -c "echo 'deb http://ftp.lysator.liu.se/pub/opensuse/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list" && \ - curl -L "http://ftp.lysator.liu.se/pub/opensuse/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/Release.key" | sudo apt-key add - && \ - sudo apt-get --allow-insecure-repositories update && \ - sudo apt-get --allow-unauthenticated -y install podman; } - podman version - - - name: Revert unprivileged user namespace restrictions in Ubuntu 24.04 - run: | - # allow unprivileged user namespace - sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - - # Enables multi-architecture support for building multi-arch manifests - - name: Install QEMU for Multi-Arch - run: | - sudo apt install -y qemu-user-static - - - name: Set cgroup_manager to 'cgroupfs' instead of systemd - run: | - mkdir -p ~/.config/containers - cat <> ~/.config/containers/containers.conf - [engine] - cgroup_manager="cgroupfs" - EOT - podman info - - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Run Smoke E2E tests in ${{ matrix.mode == 'production' && 'Production' || 'Development' }} Mode - run: | - if [ ${{ matrix.mode }} == 'production' ]; then - echo "Compiling the Podman Desktop in production mode" - export ELECTRON_ENABLE_INSPECT=true - pnpm compile:current --linux dir - path=$(realpath ./dist/linux-unpacked/podman-desktop) - echo "Podman Desktop built binary: $path" - export PODMAN_DESKTOP_BINARY=$path - fi - pnpm test:e2e:smoke - - - name: Publish Test Report - uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 - if: always() # always run even if the previous step fails - with: - fail_on_failure: true - include_passed: true - detailed_summary: true - annotate_only: true - require_tests: true - report_paths: '**/*results.xml' - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - if: always() - with: - name: ${{ matrix.mode }}-smoke-e2e-tests - path: | - ./tests/**/output/ - !./tests/**/traces/raw + mode: [prod, dev] + uses: ./.github/workflows/e2e-test-job.yaml + with: + instance: ubuntu-24.04 + mode: ${{ matrix.mode }} + compile_args: '--linux dir' + secrets: inherit detect_pnpm_changes: if: ${{ contains(github.event.pull_request.labels.*.name, 'area/ci') || !github.event.pull_request.draft }} @@ -411,7 +292,7 @@ jobs: pnpm_lock_changed: ${{ steps.pnpm_changed.outputs.PNPM_LOCK_CHANGED }} steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 2 @@ -427,190 +308,3 @@ jobs: else echo "PNPM_LOCK_CHANGED=false" >> $GITHUB_OUTPUT fi - - win-update-e2e-test: - name: win update e2e tests - needs: detect_pnpm_changes - strategy: - fail-fast: false - matrix: - os: [windows-2025] - if: contains(github.event.pull_request.labels.*.name, 'area/update') || needs.detect_pnpm_changes.outputs.pnpm_lock_changed == 'true' - runs-on: ${{ matrix.os }} - timeout-minutes: 60 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Adjust/Downgrade local podman desktop version Windows - run: | - $version="1.20.0" - jq --arg version "$version" '.version = $version' package.json | Out-File -FilePath package.json_tmp - Move-Item -Path package.json_tmp -Destination package.json -Force - - - name: Build Podman Desktop locally with electron updater included, allow prereleases - env: - ELECTRON_ENABLE_INSPECT: true - run: | - (Get-Content packages/main/src/plugin/updater.ts).Replace('autoUpdater.autoDownload = false;', 'autoUpdater.autoDownload = false;autoUpdater.allowPrerelease=true;') | Set-Content packages/main/src/plugin/updater.ts - pnpm compile:current --win nsis - $runnerArch=$env:RUNNER_ARCH - $unpackedPath = "dist/win-unpacked" - if ($runnerArch -eq 'ARM64') { - $unpackedPath = "dist/win-arm64-unpacked" - } - echo ("PD_DIST_PATH=" + $unpackedPath) >> $env:GITHUB_ENV - $path=("./$unpackedPath/Podman Desktop.exe" | resolve-path).ProviderPath - echo $path - echo ("PODMAN_DESKTOP_BINARY=" + $path) >> $env:GITHUB_ENV - - - name: Manually set testing-prereleases as update target - run: | - echo "Replace app-update.yml repo to a testing-prerelease, which are more stable update target then the prerelease" - (Get-Content "$Env:PD_DIST_PATH/resources/app-update.yml").Replace('repo: podman-desktop', 'repo: testing-prereleases') | Set-Content "$Env:PD_DIST_PATH/resources/app-update.yml" - echo "Show app-update.yml after replace..." - cat "$Env:PD_DIST_PATH/resources/app-update.yml" - - - name: Run E2E Update test - run: | - echo "${{ env.PODMAN_DESKTOP_BINARY }}" - pnpm test:e2e:update:run - - - name: Publish Test Report - uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 - if: always() # always run even if the previous step fails - with: - fail_on_failure: true - include_passed: true - detailed_summary: true - annotate_only: true - require_tests: true - report_paths: '**/*results.xml' - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - if: always() - with: - name: ${{ matrix.os }}-update-e2e-test - path: | - ./tests/**/output/ - !./tests/**/traces/raw - - mac-update-e2e-test: - name: mac update e2e tests - needs: detect_pnpm_changes - if: contains(github.event.pull_request.labels.*.name, 'area/update') || needs.detect_pnpm_changes.outputs.pnpm_lock_changed == 'true' - runs-on: macos-15 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - timeout-minutes: 60 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Adjust/Downgrade local podman desktop version - run: | - testingVersion="1.20.0" - actualVersion=$(jq -r '.version' package.json) - echo "Current version: $actualVersion, testing version: $testingVersion" - version=$testingVersion - echo "PD_VERSION=$version" >> $GITHUB_ENV - jq --arg version "$version" '.version = $version' package.json > package.json_tmp - mv package.json_tmp package.json - - - name: Build Podman Desktop locally and allow prereleases on update - env: - ELECTRON_ENABLE_INSPECT: true - run: | - echo "Allow Prerelease when updating, setting it inline in the code" - sed -i '' '/autoUpdater.autoDownload = false;/a \ - autoUpdater.allowPrerelease = true; \ - ' packages/main/src/plugin/updater.ts - pnpm compile:current --mac dmg - dmgPath=$(realpath $(find ./dist -name "*.dmg")) - echo "DMG Path: $dmgPath" - # extract the dmg - hdiutil attach $dmgPath - pdVolumePath=$(find /Volumes -name "*${{ env.PD_VERSION }}*") - echo "PD Volume path: $pdVolumePath" - sudo cp -R "$pdVolumePath/Podman Desktop.app" /Applications - # codesign the extracted app - appPath="/Applications/Podman Desktop.app" - sudo codesign --force --deep --sign - "$appPath" - codesign --verify --deep --verbose=2 "$appPath" - binaryPath="$appPath/Contents/MacOS/Podman Desktop" - echo "PD_APP_PATH=$appPath" >> $GITHUB_ENV - echo "PODMAN_DESKTOP_BINARY=$binaryPath" >> $GITHUB_ENV - - - name: Manually set testing-prereleases as update target - run: | - echo "Replace app-update.yml repo to a testing-prerelease" - sudo sed -i '' 's/repo: podman-desktop/repo: testing-prereleases/' "$PD_APP_PATH/Contents/Resources/app-update.yml" - cat "$PD_APP_PATH/Contents/Resources/app-update.yml" - - - name: Run E2E Update test - env: - UPDATE_PODMAN_DESKTOP: true - run: | - echo "${{ env.PODMAN_DESKTOP_BINARY }}" - pnpm test:e2e:update:run - - - name: Publish Test Report - uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 - if: always() # always run even if the previous step fails - with: - fail_on_failure: true - include_passed: true - detailed_summary: true - annotate_only: true - require_tests: true - report_paths: '**/*results.xml' - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - if: always() - with: - name: mac-update-e2e-test - path: | - ./tests/**/output/ - !./tests/**/traces/raw - - update-e2e-test: - name: update E2E test - runs-on: ubuntu-24.04 - needs: [win-update-e2e-test, mac-update-e2e-test] - if: | - always() - steps: - - name: Evaluate the Windows and Mac E2E Update test results - run: | - echo "Windows updater result: ${{ needs.win-update-e2e-test.result }}" - echo "Mac updater result: ${{ needs.mac-update-e2e-test.result }}" - if [[ "${{ needs.win-update-e2e-test.result }}" = "failure" || "${{ needs.mac-update-e2e-test.result }}" = "failure" ]]; then - echo "Windows or Mac udpater test failed..." - exit 1 - else - echo "Windows and Mac updater test succeeded or was skipped..." - fi diff --git a/.github/workflows/publish-flathub.yaml b/.github/workflows/publish-flathub.yaml deleted file mode 100644 index c2504a1f37..0000000000 --- a/.github/workflows/publish-flathub.yaml +++ /dev/null @@ -1,146 +0,0 @@ -# -# Copyright (C) 2024-2025 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Publish update to flathub.org - -on: - workflow_dispatch: - inputs: - version: - description: 'release version like 1.14.2' - required: true - repository_dispatch: - types: [ publish-to-flathub ] - -permissions: - contents: read - -jobs: - - update-flathub: - permissions: - contents: write # for Git to git push - name: Publish Podman Desktop to flathub - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - repository: 'flathub/io.podman_desktop.PodmanDesktop' - ref: 'master' - # Change the token to the one that has write access to the fork repository - token: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - - - name: Set version - id: VERSION - run: | - version="" - if [ "${{ github.event_name }}" == "repository_dispatch" ] - then - version="${{ github.event.client_payload.version }}" - else - version="${{ github.event.inputs.version }}" - fi - # strip out the prefix v if it's there - if [[ $version == v* ]]; then - version="${version:1}" - fi - echo "desktopVersion=$version" >> ${GITHUB_OUTPUT} - - name: Update the yaml file - run: | - # Grab the source code of Podman Desktop - export PODMAN_DESKTOP_SOURCE_URL="https://github.com/containers/podman-desktop/archive/refs/tags/v${{ steps.VERSION.outputs.desktopVersion }}.tar.gz" - wget ${PODMAN_DESKTOP_SOURCE_URL} -qO podman-desktop-source.tgz - # compute shasum256 and take the first column of the text - export PODMAN_DESKTOP_SOURCE_SHASUM=$(shasum -a 256 podman-desktop-source.tgz | awk '{print $1}') - - echo "podman Desktop source .tgz file sha256 is ${PODMAN_DESKTOP_SOURCE_SHASUM}" - - # Grab the pnpm cache for arm64 - export PNPM_STORE_CACHE_ARM64_URL="https://github.com/containers/podman-desktop/releases/download/v${{ steps.VERSION.outputs.desktopVersion }}/store-cache-pnpm-arm64.tgz" - wget ${PNPM_STORE_CACHE_ARM64_URL} -qO podman-desktop-pnpm-arm64.tgz - # compute shasum256 - export PODMAN_DESKTOP_PNPM_ARM64_SHASUM=$(shasum -a 256 podman-desktop-pnpm-arm64.tgz | awk '{print $1}') - echo "podman Desktop pnpm arm64 .tgz file sha256 is ${PODMAN_DESKTOP_PNPM_ARM64_SHASUM}" - - # Grab the pnpm cache for amd64 - export PNPM_STORE_CACHE_AMD64_URL="https://github.com/containers/podman-desktop/releases/download/v${{ steps.VERSION.outputs.desktopVersion }}/store-cache-pnpm-amd64.tgz" - wget ${PNPM_STORE_CACHE_AMD64_URL} -qO podman-desktop-pnpm-amd64.tgz - # compute shasum256 - export PODMAN_DESKTOP_PNPM_AMD64_SHASUM=$(shasum -a 256 podman-desktop-pnpm-amd64.tgz | awk '{print $1}') - echo "podman Desktop pnpm amd64 .tgz file sha256 is ${PODMAN_DESKTOP_PNPM_AMD64_SHASUM}" - - # find the version of electron being used by podman desktop - wget "https://raw.githubusercontent.com/podman-desktop/podman-desktop/refs/tags/v${{ steps.VERSION.outputs.desktopVersion }}/package.json" -qO podman-desktop-package.json - # set the version of electron - ELECTRON_VERSION=$(jq -r '.devDependencies.electron' podman-desktop-package.json) - echo "Found electron ${ELECTRON_VERSION}" - - # electron root url - export ELECTRON_BASE_URL="https://github.com/electron/electron/releases/download/v${ELECTRON_VERSION}" - export ELECTRON_BASE_URL_SHA=$(echo -n "$ELECTRON_BASE_URL" | sha256sum | cut -d ' ' -f 1) - export ELECTRON_CACHE_DIRECTORY="electron-cache/${ELECTRON_BASE_URL_SHA}" - export ELECTRON_CACHE_DIRECTORY_SLASH="${ELECTRON_CACHE_DIRECTORY}/" - - # download electron binary (amd64) - export ELECTRON_AMD64_URL="${ELECTRON_BASE_URL}/electron-v${ELECTRON_VERSION}-linux-x64.zip" - wget ${ELECTRON_AMD64_URL} -qO electron-x64.zip - # compute shasum - export ELECTRON_AMD64_SHASUM=$(shasum -a 256 electron-x64.zip | awk '{print $1}') - echo "electron x64 zip sha256 is ${ELECTRON_AMD64_SHASUM}" - - # download electron binary (arm64) - export ELECTRON_ARM64_URL="${ELECTRON_BASE_URL}/electron-v${ELECTRON_VERSION}-linux-arm64.zip" - wget ${ELECTRON_ARM64_URL} -qO electron-arm64.zip - # compute shasum - export ELECTRON_ARM64_SHASUM=$(shasum -a 256 electron-arm64.zip | awk '{print $1}') - echo "electron arm64 zip sha256 is ${ELECTRON_ARM64_SHASUM}" - - # copy original yaml file - cp io.podman_desktop.PodmanDesktop.yml io.podman_desktop.PodmanDesktop.ori - - # update the url and sha256 for the electron binaries for amd64 and arm64 - yq -i '(.modules[].sources[] | select(.url == "*electron*x64*")) += {"url":strenv(ELECTRON_AMD64_URL), "dest":strenv(ELECTRON_CACHE_DIRECTORY_SLASH), "sha256":strenv(ELECTRON_AMD64_SHASUM)}' io.podman_desktop.PodmanDesktop.yml - yq -i '(.modules[].sources[] | select(.url == "*electron*arm64*")) += {"url":strenv(ELECTRON_ARM64_URL), "dest":strenv(ELECTRON_CACHE_DIRECTORY_SLASH), "sha256":strenv(ELECTRON_ARM64_SHASUM)}' io.podman_desktop.PodmanDesktop.yml - - # update the pnpm store url/sha256 - yq -i '(.modules[].sources[] | select(.url == "*store-cache-pnpm*amd64*")) += {"url":strenv(PNPM_STORE_CACHE_AMD64_URL), "sha256":strenv(PODMAN_DESKTOP_PNPM_AMD64_SHASUM)}' io.podman_desktop.PodmanDesktop.yml - yq -i '(.modules[].sources[] | select(.url == "*store-cache-pnpm*arm64*")) += {"url":strenv(PNPM_STORE_CACHE_ARM64_URL), "sha256":strenv(PODMAN_DESKTOP_PNPM_ARM64_SHASUM)}' io.podman_desktop.PodmanDesktop.yml - - # update the source url/sha256 of podman desktop - yq -i '(.modules[].sources[] | select(.url == "*podman-desktop*refs*tags*")) += {"url":strenv(PODMAN_DESKTOP_SOURCE_URL), "sha256":strenv(PODMAN_DESKTOP_SOURCE_SHASUM)}' io.podman_desktop.PodmanDesktop.yml - - # update the version of the cache for the env variable - sed -i -E 's|^( *- export ELECTRON_CACHE="\$\(\s*pwd\s*\)/)[^"]+(".*)|\1'"$ELECTRON_CACHE_DIRECTORY"'\2|' io.podman_desktop.PodmanDesktop.yml - - env: - GITHUB_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - PODMAN_DESKTOP_VERSION: ${{ steps.VERSION.outputs.desktopVersion }} - - name: Create Pull Request to update flathub - run: | - git config --local user.name ${{ github.actor }} - git config --local user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" - export LOCAL_BRANCH_NAME="update-to-${{ steps.VERSION.outputs.desktopVersion }}" - git checkout -b "${LOCAL_BRANCH_NAME}" - git add io.podman_desktop.PodmanDesktop.yml - export TITLE="feat: update Podman Desktop to version ${{ steps.VERSION.outputs.desktopVersion }}" - git commit -m "${TITLE}" - git remote add podman-desktop-bot https://github.com/podman-desktop-bot/io.podman_desktop.PodmanDesktop - git push podman-desktop-bot "+${LOCAL_BRANCH_NAME}" - gh pr create --title "${TITLE}" --body "${TITLE}" -H "podman-desktop-bot:${LOCAL_BRANCH_NAME}" - - env: - GH_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} diff --git a/.github/workflows/publish-to-chocolatey.yaml b/.github/workflows/publish-to-chocolatey.yaml deleted file mode 100644 index 6f66116bc9..0000000000 --- a/.github/workflows/publish-to-chocolatey.yaml +++ /dev/null @@ -1,116 +0,0 @@ -# -# Copyright (C) 2022 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Publish update to Chocolatey - -on: - workflow_dispatch: - inputs: - version: - description: 'release version like 0.0.6' - required: true - force: - description: 'Force the push of the package' - required: false - default: 'false' - repository_dispatch: - types: [ publish-to-chocolatey ] - -permissions: - contents: read - -jobs: - - version: - name: Extracting version - runs-on: ubuntu-24.04 - outputs: - desktopVersion: ${{ steps.VERSION.outputs.desktopVersion}} - - steps: - - name: set version - id: VERSION - run: | - version="" - if [ "${{ github.event_name }}" == "repository_dispatch" ] - then - version="${{ github.event.client_payload.version }}" - else - version="${{ github.event.inputs.version }}" - fi - # strip out the prefix v if it's there - if [[ $version == v* ]]; then - version="${version:1}" - fi - echo "desktopVersion=$version" >> ${GITHUB_OUTPUT} - - winget-bump: - permissions: - contents: write # for Git to git push - name: Update Chocolatey - needs: version - runs-on: windows-2025 - defaults: - run: - shell: powershell - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Install the Chocolatey Automatic Package Updater Module - run: choco install au -y - - name: Build the Podman Desktop chocolatey Package by making sure we're using the latest release - working-directory: ./.chocolatey/podman-desktop - run: | - if ($env:automaticUpdateForce -eq 'true') { - $au_Force = $true; ./update.ps1 - } else { - ./update.ps1 - } - cat podman-desktop.nuspec - env: - au_Push: ${{ github.event.inputs.force }} - automaticUpdateForce: ${{ github.event.inputs.force }} - VERSION: ${{ needs.version.outputs.desktopVersion }} - - name: Create the PR to bump the version in the main branch - run: | - git config --local user.name ${{ github.actor }} - git config --local user.email "fbenoit@redhat.com" - $bumpedBranchName = "update-chocolatey-for-${{ needs.version.outputs.desktopVersion }}" - git checkout -b $bumpedBranchName - git add .chocolatey - git commit --signoff -m "chore: Update chocolatey packages for ${{ needs.version.outputs.desktopVersion }}" - git push origin "$bumpedBranchName" - New-Item -Path . -Name "pr-title" -ItemType "file" -Value "${{ needs.version.outputs.desktopVersion }} has been released`n`nUpdate Chocolatey packages for ${{ needs.version.outputs.desktopVersion }}" - $pullRequestUrl = gh pr create --title "chore: Update Chocolatey package to ${{ needs.version.outputs.desktopVersion }}" --body-file ./pr-title --head "$bumpedBranchName" --base "main" -R "containers/podman-desktop" - echo "📢 Pull request created: $pullRequestUrl" - echo "➡️ Flag the PR as being ready for review" - gh pr ready "$pullRequestUrl" - echo "🔅 Mark the PR as being ok to be merged automatically" - gh pr merge "$pullRequestUrl" --auto --rebase - env: - GH_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - GITHUB_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - - name: Publish Package to Chocolatey - run: | - if ($env:automaticUpdateForce -eq 'true') { - $au_Force = $true; Push-Package - } else { - Push-Package - } - working-directory: ./.chocolatey/podman-desktop - env: - api_key: ${{ secrets.CHOCOLATEY_API_KEY }} - automaticUpdateForce: ${{ github.event.inputs.force }} diff --git a/.github/workflows/publish-to-podman_io.yaml b/.github/workflows/publish-to-podman_io.yaml deleted file mode 100644 index 1634c24025..0000000000 --- a/.github/workflows/publish-to-podman_io.yaml +++ /dev/null @@ -1,101 +0,0 @@ -# -# Copyright (C) 2023 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Publish update to podman.io website - -on: - workflow_dispatch: - inputs: - version: - permissions: - contents: write # for Git to git push - description: 'release version like 1.1.0' - required: true - repository_dispatch: - types: [ publish-to-podman_io ] - -permissions: - contents: read - -jobs: - - version: - name: Extracting version - runs-on: ubuntu-24.04 - outputs: - desktopVersion: ${{ steps.VERSION.outputs.desktopVersion}} - - steps: - - name: set version - id: VERSION - run: | - version="" - if [ "${{ github.event_name }}" == "repository_dispatch" ] - then - version="${{ github.event.client_payload.version }}" - else - version="${{ github.event.inputs.version }}" - fi - # strip out the prefix v if it's there - if [[ $version == v* ]]; then - version="${version:1}" - fi - echo "desktopVersion=$version" >> ${GITHUB_OUTPUT} - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - repository: containers/podman.io - ref: refs/heads/main - token: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - - - - name: Replace the content of the file - run: | - # Replace the version in static/data/global.ts file (replace the xxx value) - # export const LATEST_DESKTOP_VERSION = 'xxx'; - sed -i "s/export const LATEST_DESKTOP_VERSION = '.*';/export const LATEST_DESKTOP_VERSION = '${{ steps.VERSION.outputs.desktopVersion }}';/g" static/data/global.ts - # Replace the version of the blog post - # export const LATEST_DESKTOP_DOWNLOAD_URL = 'https://podman-desktop.io/blog/podman-desktop-release-xxx'; - # need to strip out the last digit after the last . to get the blog post version (so for example 1.4.1 becomes 1.4) - blogPostVersion=$(echo ${{ steps.VERSION.outputs.desktopVersion }} | sed 's/\.[^.]*$//') - sed -i "s/export const LATEST_DESKTOP_DOWNLOAD_URL = '.*';/export const LATEST_DESKTOP_DOWNLOAD_URL = 'https:\/\/podman-desktop.io\/blog\/podman-desktop-release-$blogPostVersion';/g" static/data/global.ts - echo "New content of the file:" - cat static/data/global.ts - - - name: Create the PR to bump the version in the main branch - run: | - git config --local user.name ${{ github.actor }} - git config --local user.email "fbenoit@redhat.com" - bumpedBranchName="update-podman-desktop-io-for-v${{ steps.VERSION.outputs.desktopVersion }}" - echo "branch name: $bumpedBranchName" - git checkout -b $bumpedBranchName - echo "adding the file to the commit" - git add static/data/global.ts - echo "commiting the file" - git commit --signoff -m "chore: Update Podman Desktop version to v${{ steps.VERSION.outputs.desktopVersion }}" - git remote -v - git remote add podman-desktop-bot https://github.com/podman-desktop-bot/podman.io - git push podman-desktop-bot "+$bumpedBranchName" - printf "Podman Desktop v${{ steps.VERSION.outputs.desktopVersion }} has been released 🎉\nUpdate website for v${{ steps.VERSION.outputs.desktopVersion }}" > ./pr-title - pullRequestUrl=$(gh pr create --title "chore: Update Podman Desktop version to v${{ steps.VERSION.outputs.desktopVersion }}" --body-file ./pr-title --head "podman-desktop-bot:$bumpedBranchName" --base "main" -R "containers/podman.io") - echo "📢 Pull request created: $pullRequestUrl" - echo "➡️ Flag the PR as being ready for review" - gh pr ready "$pullRequestUrl" - echo "🔅 Mark the PR as being ok to be merged automatically" - gh pr merge "$pullRequestUrl" --auto --rebase - env: - GH_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - GITHUB_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} diff --git a/.github/workflows/publish-to-winget.yaml b/.github/workflows/publish-to-winget.yaml deleted file mode 100644 index 6971d374da..0000000000 --- a/.github/workflows/publish-to-winget.yaml +++ /dev/null @@ -1,73 +0,0 @@ -# -# Copyright (C) 2022 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Publish update to Winget - -on: - workflow_dispatch: - inputs: - version: - description: 'release version like 0.0.6' - required: true - repository_dispatch: - types: [ publish-to-winget ] - -permissions: - contents: read - -jobs: - - version: - name: Extracting version - runs-on: ubuntu-24.04 - outputs: - desktopVersion: ${{ steps.VERSION.outputs.desktopVersion}} - - steps: - - name: set version - id: VERSION - run: | - version="" - if [ "${{ github.event_name }}" == "repository_dispatch" ] - then - version="${{ github.event.client_payload.version }}" - else - version="${{ github.event.inputs.version }}" - fi - # strip out the prefix v if it's there - if [[ $version == v* ]]; then - version="${version:1}" - fi - echo "desktopVersion=$version" >> ${GITHUB_OUTPUT} - - winget-bump: - name: Update Winget - needs: version - runs-on: windows-2025 - defaults: - run: - shell: powershell - steps: - - name: Create winget PR - run: | - Invoke-WebRequest https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe - .\wingetcreate.exe update RedHat.Podman-Desktop -u $Env:URL_X64 $Env:URL_ARM64 -v $Env:VERSION -t $Env:TOKEN --submit - env: - TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - VERSION: ${{ needs.version.outputs.desktopVersion }} - URL_X64: ${{ format('https://github.com/containers/podman-desktop/releases/download/v{0}/podman-desktop-{0}-setup-x64.exe|x64', needs.version.outputs.desktopVersion) }} - URL_ARM64: ${{ format('https://github.com/containers/podman-desktop/releases/download/v{0}/podman-desktop-{0}-setup-arm64.exe|arm64', needs.version.outputs.desktopVersion) }} diff --git a/.github/workflows/publish-website-pr-cloudflare.yaml b/.github/workflows/publish-website-pr-cloudflare.yaml deleted file mode 100644 index b43eabd654..0000000000 --- a/.github/workflows/publish-website-pr-cloudflare.yaml +++ /dev/null @@ -1,55 +0,0 @@ -# -# Copyright (C) 2025 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -# NOTE: Because this worklow is using secrets, it cannot run directly on a pull-request workflow, which is running in the context of the forked repository. - -name: Publish pull request content on cloudflare pages - -on: - workflow_run: - workflows: - - 'pr-check' - types: - - completed - -permissions: - contents: read - deployments: write - -jobs: - publish: - name: Publish content of the website pull request on cloudflare pages - runs-on: ubuntu-24.04 - steps: - - name: Download website content artifact - uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11 - with: - workflow: ${{ github.event.workflow_run.workflow_id }} - name: website-content - path: content - allow_forks: true - - - name: Deploy to Cloudflare Pages - uses: AdrianGonz97/refined-cf-pages-action@45ea15c1c69a7bce8ffbb79ba114daa2b52f0cff # v1.3.0 - with: - apiToken: ${{ secrets.CLOUDFLARE_AUTH_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - githubToken: ${{ secrets.GITHUB_TOKEN }} - projectName: podman-desktop-website-pr - deploymentName: Website Preview - comment: false - directory: content/website/build diff --git a/.github/workflows/release-generate-sbom.yaml b/.github/workflows/release-generate-sbom.yaml deleted file mode 100644 index dd9066fc46..0000000000 --- a/.github/workflows/release-generate-sbom.yaml +++ /dev/null @@ -1,55 +0,0 @@ -# -# Copyright (C) 2025 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Generate sbom for release - -on: - workflow_dispatch: - inputs: - version: - description: 'Release version for sbom generation (e.g. 0.0.1)' - required: true - -env: - RELEASE_TAG: v${{ inputs.version }} - -jobs: - generate-sbom: - name: Generate SBOM file - runs-on: ubuntu-24.04 - permissions: - actions: read - contents: write - - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ env.RELEASE_TAG }} - - - name: Generate sbom file - uses: anchore/sbom-action@v0 - with: - path: ./ - output-file: ./podman-desktop-sbom.spdx.json - artifact-name: podman-desktop-sbom.spdx.json - - - name: Upload sbom file to release - uses: svenstaro/upload-release-action@81c65b7cd4de9b2570615ce3aad67a41de5b1a13 # v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ./podman-desktop-sbom.spdx.json - tag: ${{ env.RELEASE_TAG }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index dee02f04c0..0000000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,310 +0,0 @@ -# -# Copyright (C) 2022-2024 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: release - -on: - workflow_dispatch: - inputs: - version: - description: 'Version to release' - required: true - branch: - description: 'Branch to use for the release' - required: true - default: main -env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - DEBUG: electron-builder - -permissions: - contents: read - -jobs: - - tag: - name: Tagging - runs-on: ubuntu-24.04 - permissions: - contents: write - outputs: - githubTag: ${{ steps.TAG_UTIL.outputs.githubTag}} - desktopVersion: ${{ steps.TAG_UTIL.outputs.desktopVersion}} - releaseId: ${{ steps.create_release.outputs.id}} - - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ github.event.inputs.branch }} - - name: Generate tag utilities - id: TAG_UTIL - run: | - TAG_PATTERN=${{ github.event.inputs.version }} - echo "githubTag=v$TAG_PATTERN" >> ${GITHUB_OUTPUT} - echo "desktopVersion=$TAG_PATTERN" >> ${GITHUB_OUTPUT} - - - name: tag - run: | - git config --local user.name ${{ github.actor }} - - # Add the new version in package.json file - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ steps.TAG_UTIL.outputs.desktopVersion }}\",#g" package.json - find extensions/* -maxdepth 5 -name "package.json" | xargs -I {} sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ steps.TAG_UTIL.outputs.desktopVersion }}\",#g" {} - git add package.json extensions/*/package.json extensions/*/packages/extension/package.json - - # Update the issue template with the new version and move old version below - nextVersionLineNumber=$(grep -n "next (development version)" .github/ISSUE_TEMPLATE/bug_report.yml | cut -d ":" -f 1 | head -n 1) - currentVersionItem=$(sed "$(expr ${nextVersionLineNumber} - 1)q;d" .github/ISSUE_TEMPLATE/bug_report.yml) - newVersionItem=$(echo "$currentVersionItem" | sed -r -e "s|\".*\"|\"${{ steps.TAG_UTIL.outputs.desktopVersion }}\"|") - # delete the lines before the next version line - sed -i "$(expr ${nextVersionLineNumber} - 1)d" .github/ISSUE_TEMPLATE/bug_report.yml - # insert the version being tagged - sed -i "$(expr ${nextVersionLineNumber} - 1)i\\${newVersionItem}" .github/ISSUE_TEMPLATE/bug_report.yml - sed -i "$(expr ${nextVersionLineNumber} + 1)i\\${currentVersionItem}" .github/ISSUE_TEMPLATE/bug_report.yml - # add the changes - git add .github/ISSUE_TEMPLATE/bug_report.yml - - # Add the new version in the appdata.xml file - xmlReleasesLineNumber=$(grep -n "" .flatpak-appdata.xml | cut -d ":" -f 1 | head -n 1) - newFlatpakReleaseItem=" " - sed -i "$(expr ${xmlReleasesLineNumber} + 1)i\\${newFlatpakReleaseItem}" .flatpak-appdata.xml - git add .flatpak-appdata.xml - - # commit the changes - git commit -m "chore: 🥁 tagging ${{ steps.TAG_UTIL.outputs.githubTag }} 🥳" - echo "Tagging with ${{ steps.TAG_UTIL.outputs.githubTag }}" - git tag ${{ steps.TAG_UTIL.outputs.githubTag }} - git push origin ${{ steps.TAG_UTIL.outputs.githubTag }} - - name: Create Release - id: create_release - uses: ncipollo/release-action@bcfe5470707e8832e12347755757cec0eb3c22af # v1.18.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag: ${{ steps.TAG_UTIL.outputs.githubTag }} - name: ${{ steps.TAG_UTIL.outputs.githubTag }} - draft: true - prerelease: true - - name: Create the PR to bump the version in the main branch (only if we're tagging from main branch) - if: ${{ github.event.inputs.branch == 'main' }} - run: | - git config --local user.name ${{ github.actor }} - git config --local user.email "fbenoit@redhat.com" - CURRENT_VERSION=$(echo "${{ steps.TAG_UTIL.outputs.desktopVersion }}") - tmp=${CURRENT_VERSION%.*} - minor=${tmp#*.} - bumpedVersion=${CURRENT_VERSION%%.*}.$((minor + 1)).0 - bumpedBranchName="bump-to-${bumpedVersion}" - git checkout -b "${bumpedBranchName}" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${bumpedVersion}-next\",#g" package.json - find extensions/* -maxdepth 5 -name "package.json" | xargs -I {} sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${bumpedVersion}-next\",#g" {} - git add package.json extensions/*/package.json extensions/*/packages/extension/package.json - git commit -s --amend -m "chore: bump version to ${bumpedVersion}" - git push origin "${bumpedBranchName}" - echo -e "📢 Bump version to ${bumpedVersion}\n\n${{ steps.TAG_UTIL.outputs.desktopVersion }} has been released.\n\n Time to switch to the new ${bumpedVersion} version 🥳" > /tmp/pr-title - pullRequestUrl=$(gh pr create --title "chore: 📢 Bump version to ${bumpedVersion}" --body-file /tmp/pr-title --head "${bumpedBranchName}" --base "main") - echo "📢 Pull request created: ${pullRequestUrl}" - echo "➡️ Flag the PR as being ready for review" - gh pr ready "${pullRequestUrl}" - echo "🔅 Mark the PR as being ok to be merged automatically" - gh pr merge "${pullRequestUrl}" --auto --rebase - env: - GITHUB_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }} - - build: - name: Build / ${{ matrix.os }} ${{ matrix.airgap == 'true' && '(Air Gap)' || '' }} - needs: tag - permissions: - contents: write - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: "windows-2025" - - os: "windows-2025" - airgap: "true" - - os: "macos-15" - - os: "macos-15" - airgap: "true" - - os: "ubuntu-24.04" - timeout-minutes: 60 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ needs.tag.outputs.githubTag}} - - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: "3.x" - - - name: setup telemetry key for production - run: | - sed -i -r -e "s/SEGMENT_KEY = '.*'/SEGMENT_KEY = '${{ secrets.SEGMENT_WRITE_KEY }}'/" packages/main/src/plugin/telemetry/telemetry.ts - - - name: Execute pnpm - run: pnpm install - - - name: Install flatpak on Linux - if: ${{ matrix.os=='ubuntu-24.04' }} - run: | - sudo apt-get update - sudo apt-get install flatpak -y - sudo apt-get install flatpak-builder -y - sudo apt-get install elfutils -y - flatpak remote-add --if-not-exists flathub --user https://flathub.org/repo/flathub.flatpakrepo - flatpak install flathub --no-static-deltas --user -y org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08 - - name: Set macOS environment variables - if: startsWith(matrix.os, 'macos') - run: | - echo "CSC_LINK=${{secrets.CSC_LINK}}" >> $GITHUB_ENV - echo "CSC_KEY_PASSWORD=${{secrets.CSC_KEY_PASSWORD}}" >> $GITHUB_ENV - echo "APPLE_ID=${{secrets.APPLE_ID}}" >> $GITHUB_ENV - echo "APPLE_APP_SPECIFIC_PASSWORD=${{secrets.APPLE_APP_SPECIFIC_PASSWORD}}" >> $GITHUB_ENV - echo "APPLE_TEAM_ID=${{secrets.APPLE_TEAM_ID}}" >> $GITHUB_ENV - - - name: Install Azure SignTool on Windows - if: startsWith(matrix.os, 'windows') - run: | - dotnet tool install --global AzureSignTool --version 3.0.0 - echo "AZURE_KEY_VAULT_CERTIFICATE=${{secrets.AZURE_KEY_VAULT_CERTIFICATE}}" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "AZURE_KEY_VAULT_CLIENT_ID=${{secrets.AZURE_KEY_VAULT_CLIENT_ID}}" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "AZURE_KEY_VAULT_SECRET=${{secrets.AZURE_KEY_VAULT_SECRET}}" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "AZURE_KEY_VAULT_TENANT_ID=${{secrets.AZURE_KEY_VAULT_TENANT_ID}}" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "AZURE_KEY_VAULT_URL=${{secrets.AZURE_KEY_VAULT_URL}}" | Out-File -FilePath $env:GITHUB_ENV -Append - - - name: Build & Publish artifacts - timeout-minutes: 40 - env: - AIRGAP_DOWNLOAD: ${{ matrix.airgap == 'true' && '1' || '' }} - run: pnpm compile:next - - release: - needs: [tag, build] - name: Release - permissions: - contents: write - runs-on: ubuntu-24.04 - steps: - - name: id - run: echo the release id is ${{ needs.tag.outputs.releaseId}} - - - name: Publish release - uses: StuYarrow/publish-release@01f2a1365bacd77bad861873a7fdf274ab49eefd # v1.1.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - id: ${{ needs.tag.outputs.releaseId}} - - publish: - needs: [tag, release] - name: Publish - runs-on: ubuntu-24.04 - permissions: - id-token: write - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Set-up npmjs auth token - run: printf "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}\n" >> ~/.npmrc - - - name: Publish API to npmjs - run: | - echo "Using version ${{ needs.tag.outputs.desktopVersion }}" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ needs.tag.outputs.desktopVersion }}\",#g" packages/extension-api/package.json - cd packages/extension-api && pnpm publish --provenance --tag latest --no-git-checks --access public - - - name: Publish Webview API to npmjs - run: | - echo "Using version ${{ needs.tag.outputs.desktopVersion }}" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ needs.tag.outputs.desktopVersion }}\",#g" packages/webview-api/package.json - cd packages/webview-api && pnpm publish --provenance --tag latest --no-git-checks --access public - - - name: Publish ui/svelte to npmjs - run: | - echo "Using version ${{ needs.tag.outputs.desktopVersion }}" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ needs.tag.outputs.desktopVersion }}\",#g" packages/ui/package.json - cd packages/ui && pnpm build && pnpm publish --provenance --tag latest --no-git-checks --access public - - - name: Publish tests-playwright to npmjs - run: | - echo "Using version ${{ needs.tag.outputs.desktopVersion }}" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ needs.tag.outputs.desktopVersion }}\",#g" tests/playwright/package.json - cd tests/playwright && pnpm build && pnpm publish --provenance --tag latest --no-git-checks --access public - - - name: Publish Podman Extension API to npmjs - run: | - echo "Using version ${{ needs.tag.outputs.desktopVersion }}" - sed -i "s#version\":\ \"\(.*\)\",#version\":\ \"${{ needs.tag.outputs.desktopVersion }}\",#g" extensions/podman/packages/api/package.json - cd extensions/podman/packages/api - pnpm publish --provenance --tag latest --no-git-checks --access public - - # publish the pnpm store for flathub builds - pnpm-store: - needs: [tag, release] - name: pnpm-store-${{ matrix.arch }} - runs-on: ubuntu-24.04 - permissions: - contents: write - strategy: - matrix: - arch: [amd64, arm64] - - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ needs.tag.outputs.githubTag}} - - - name: Set up QEMU - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 - - name: create the pnpm store from the dependencies - run: | - podman run --platform linux/${{ matrix.arch }} -v $(pwd):/project --rm -it --entrypoint=sh node:22 -c "cd /project && npm install -g corepack@latest && corepack enable pnpm && COREPACK_ENABLE_DOWNLOAD_PROMPT=0 pnpm install --frozen-lockfile --store-dir pnpm-store" - # now the store is in the pnpm-store directory - # create a tarball of the store - echo "Creating the archive store-cache-pnpm-${{ matrix.arch }}.tgz" - tar -czf store-cache-pnpm-${{ matrix.arch }}.tgz pnpm-store - - - name: Upload binaries to release - uses: svenstaro/upload-release-action@81c65b7cd4de9b2570615ce3aad67a41de5b1a13 # v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: store-cache-pnpm-${{ matrix.arch }}.tgz - tag: ${{ needs.tag.outputs.githubTag }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml deleted file mode 100644 index a7fae3ecfa..0000000000 --- a/.github/workflows/scorecard.yml +++ /dev/null @@ -1,75 +0,0 @@ -# -# Copyright (C) 2025 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Scorecard supply-chain security -on: - # For Branch-Protection check. Only the default branch is supported. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection - branch_protection_rule: - # To guarantee Maintained check is occasionally updated. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained - schedule: - - cron: '43 7 * * 1' - push: - branches: [ "main" ] - -# Declare default permissions as read only. -permissions: read-all - -jobs: - analysis: - name: Scorecard analysis - runs-on: ubuntu-24.04 - # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. - if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' - permissions: - # Needed to upload the results to code-scanning dashboard. - security-events: write - # Needed to publish results and get a badge (see publish_results below). - id-token: write - # Uncomment the permissions below if installing in a private repository. - # contents: read - # actions: read - - steps: - - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - persist-credentials: false - - - name: "Run analysis" - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 - with: - results_file: results.sarif - results_format: sarif - publish_results: true - - # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF - # format to the repository Actions tab. - - name: "Upload artifact" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: SARIF file - path: results.sarif - retention-days: 5 - - # Upload the results to GitHub's code scanning dashboard (optional). - # Commenting out will disable upload of results to your repo's Code Scanning dashboard - - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 - with: - sarif_file: results.sarif diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml deleted file mode 100644 index 5ee11287f5..0000000000 --- a/.github/workflows/stale.yaml +++ /dev/null @@ -1,65 +0,0 @@ -# -# Copyright (C) 2024 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: stale - -on: - schedule: - - cron: "5 0 * * *" - workflow_dispatch: - -permissions: - contents: read - -jobs: - stale: - permissions: - issues: write # for actions/stale to close stale issues - pull-requests: write # for actions/stale to close stale PRs - - runs-on: ubuntu-24.04 - steps: - - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - # Please refer to https://github.com/actions/stale/ - # limit to 1000 operations per run - operations-per-run: 1000 - days-before-issue-stale: 180 - days-before-issue-close: 30 - stale-issue-label: lifecycle/stale - exempt-issue-labels: lifecycle/frozen - stale-issue-message: > - This issue has been automatically marked as stale because it has not had - activity in the last 6 months. It will be closed in 30 days if no further activity occurs. Please - feel free to leave a comment if you believe the issue is still relevant. - Thank you for your contributions! - close-issue-message: > - This issue has been automatically closed because it has not had any further - activity in the last 30 days. Thank you for your contributions! - days-before-pr-stale: 180 - days-before-pr-close: 14 - stale-pr-label: lifecycle/stale - exempt-pr-labels: lifecycle/frozen - stale-pr-message: > - This pull request has been automatically marked as stale because it has not had - activity in the last 6 months. It will be closed in 2 weeks if no further activity occurs. Please - feel free to give a status update or ping for review. Thank you for your contributions! - close-pr-message: > - This pull request has been automatically closed because it has not had any further - activity in the last 2 weeks. Thank you for your contributions! - remove-stale-when-updated: true diff --git a/.github/workflows/website-next.yaml b/.github/workflows/website-next.yaml deleted file mode 100644 index a75580fe3e..0000000000 --- a/.github/workflows/website-next.yaml +++ /dev/null @@ -1,80 +0,0 @@ -# -# Copyright (C) 2022 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Publish Website - -on: - workflow_dispatch: - push: - branches: - - main - paths: - - 'package.json' - - 'pnpm-lock.yaml' - - 'storybook/**' - - 'website/**' - - 'packages/extension-api/src/**' - -env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - -permissions: - contents: read - -jobs: - deploy: - permissions: - contents: write # for peaceiris/actions-gh-pages to push pages branch - name: Build and deploy website - runs-on: ubuntu-24.04 - # disable on forks as secrets are not available - if: github.event.repository.fork == false - timeout-minutes: 60 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ needs.tag.outputs.githubTag}} - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Install pnpm - with: - run_install: false - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 22 - cache: 'pnpm' - - - name: Execute pnpm - run: pnpm install - - - name: Run Build - timeout-minutes: 20 - run: pnpm website:build - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - # Build output to publish to the `gh-pages` branch: - publish_dir: ./website/build - # The following lines assign commit authorship to the official - # GH-Actions bot for deploys to `gh-pages` branch: - # https://github.com/actions/checkout/issues/13#issuecomment-724415212 - # The GH actions bot is used by default if you didn't specify the two fields. - # You can swap them out with your own user credentials. - user_name: github-actions[bot] - user_email: 41898282+github-actions[bot]@users.noreply.github.com diff --git a/.gitignore b/.gitignore index 1517cd3bd1..a63a730232 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ dist /storybook/storybook-static/ yarn.lock extensions/podman/assets/ +.claude +output/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..97fa2b8f5f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.preferences.importModuleSpecifier": "non-relative" +} diff --git a/ADOPTERS.md b/ADOPTERS.md deleted file mode 100644 index ba3521293e..0000000000 --- a/ADOPTERS.md +++ /dev/null @@ -1,25 +0,0 @@ -# Podman Desktop Adopters - -Thank you to all the individuals, organizations, and projects that are using Podman Desktop! - -### Contribution Guidelines - -If you're using Podman Desktop and want to add your organization to this list submit a pull request with your change. In your pull request, please include the following information: - -- **Name/Organization/Project name:** Clearly identify the adopter. -- **Status:** Provide a status of your usage of the solution: ![production](https://img.shields.io/badge/-production-blue?style=flat), ![testing](https://img.shields.io/badge/-development%20&%20testing-green?style=flat). -- **More Information:** Eventually include a link that points to more information about your usage of the solution. - -## Organizations using Podman Desktop - -Below is a list of organizations that are using Podman Desktop. - -| Organization | Status | More Information | -| -------------------------------- | ----------------------------------------------------------------------- | ---------------- | -| [SAMPLE Acme](https://acme.com/) | ![production](https://img.shields.io/badge/-production-blue?style=flat) | -| [Amadeus](https://amadeus.com/) | ![production](https://img.shields.io/badge/-production-blue?style=flat) | Migration of 2k+ developers' container engines to Podman Desktop in Jan 2024. Regular collaboration with the Podman Desktop engineering team. -| [École et observatoire des Sciences de la Terre](https://eost.unistra.fr/) | ![production](https://img.shields.io/badge/-production-blue?style=flat) | Used to improve security on our developer workstations | - -## Solutions built with Podman Desktop - -Below is a list of solutions where Podman Desktop is being used or referenced. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..f2fbf5fb1c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,271 @@ +# AGENTS.md + +This file provides guidance to AI code assistants when working with code in this repository. + +## Project Overview + +Kortex is an Electron-based desktop application built with Svelte that provides AI-powered container and Kubernetes management capabilities. It integrates with multiple AI providers (Gemini, OpenAI-compatible services, OpenShift AI) and supports flow execution through providers like Goose. The application also implements the Model Context Protocol (MCP) for AI agent integration. + +## Core Architecture + +### Multi-Process Electron Application + +Kortex follows the standard Electron architecture with three main processes: + +- **Main Process** (`packages/main`): Node.js backend handling system integration, extension loading, container/Kubernetes operations, and business logic +- **Renderer Process** (`packages/renderer`): Svelte-based UI running in the browser context +- **Preload Scripts** (`packages/preload` and `packages/preload-webview`): Bridge layer for secure IPC communication between main and renderer + +### Plugin System and Dependency Injection + +The core plugin system is implemented in `packages/main/src/plugin/index.ts` using Inversify for dependency injection. Key components include: + +- **PluginSystem class**: Orchestrates extension loading, configuration registry, and all core services +- **Extension Loader** (`ExtensionLoader`): Manages extension lifecycle (loading, starting, stopping, uninstalling) +- **Container**: Inversify DI container binding all major registries and managers as singletons + +All major services are registered as singletons in the DI container during initialization: + +- `ProviderRegistry`: Manages inference, container, and Kubernetes providers +- `ContainerProviderRegistry`: Handles Docker/Podman container operations +- `KubernetesClient`: Kubernetes cluster management +- `MCPManager` and `MCPRegistry`: Model Context Protocol integration +- `FlowManager`: Manages flow execution with providers like Goose +- `ChatManager`: AI chat functionality +- `ConfigurationRegistry`: Settings and configuration management + +### Extensions + +Extensions are located in the `extensions/` directory and follow a standard structure: + +- Each extension has a `package.json` with `main` pointing to `./dist/extension.js` +- Extensions must declare `engines.kortex` version compatibility +- Extensions can contribute: + - Inference providers (Gemini, OpenAI-compatible, OpenShift AI) + - Flow providers (Goose) + - MCP registries + - Configuration properties +- Extensions are built using Vite and export a standard activation API + +Available built-in extensions: + +- `gemini`: Google Gemini AI provider integration +- `goose`: Goose flow execution provider +- `mcp-registries`: MCP server registries +- `openai-compatible`: OpenAI-compatible API support +- `openshift-ai`: OpenShift AI platform integration + +### Extension API + +Extensions interact with Kortex through `@kortex-app/api` (`packages/extension-api`), which provides TypeScript definitions for: + +- Provider registration (inference, container, Kubernetes, VM, flow) +- Configuration management +- Command and menu contributions +- UI components (webviews, views, status bar items) +- Authentication providers +- MCP server integration + +## Development Commands + +### Setup and Installation + +```bash +# Install dependencies +pnpm install + +# Start in watch/development mode +pnpm watch +``` + +### Building + +```bash +# Build entire application +pnpm run build + +# Build individual packages +pnpm run build:main # Main process +pnpm run build:renderer # UI/renderer +pnpm run build:preload # Preload scripts +pnpm run build:extensions # All extensions + +# Build specific extension +cd extensions/gemini && pnpm run build +``` + +### Testing + +```bash +# Run all tests (unit + e2e) +pnpm test + +# Unit tests only +pnpm run test:unit + +# Unit tests with coverage +pnpm run test:unit:coverage + +# Run tests for specific packages +pnpm run test:main # Main process tests +pnpm run test:renderer # Renderer tests +pnpm run test:preload # Preload tests +pnpm run test:extensions # Extension tests + +# E2E tests with Playwright +pnpm run test:e2e # Build and run e2e tests +pnpm run test:e2e:run # Run e2e tests only (must build first) +pnpm run test:e2e:report # Show Playwright test report + +# Watch mode for development +pnpm run test:watch +``` + +### Code Quality + +```bash +# Format code +pnpm run format:check # Check formatting +pnpm run format:fix # Fix formatting issues + +# Linting +pnpm run lint:check # Check for linting issues +pnpm run lint:fix # Fix linting issues + +# Type checking +pnpm run typecheck # Check all packages +pnpm run typecheck:main # Main process only +pnpm run typecheck:renderer # Renderer only +pnpm run svelte:check # Svelte component type checking +``` + +### Building Distributables + +```bash +# Development build (directory output, no packaging) +pnpm run compile + +# Production build for release +pnpm run compile:next # With auto-publishing +pnpm run compile:current # Current version +pnpm run compile:pull-request # Without publishing +``` + +## Key Technical Details + +### Workspace Structure + +This is a pnpm monorepo with workspaces defined in `pnpm-workspace.yaml`: + +- `packages/*`: Core application packages (main, renderer, preload, extension-api, api, webview-api) +- `extensions/*`: Extension packages +- `scripts`: Build and utility scripts +- `tests/playwright`: E2E test suite + +### IPC Communication Pattern + +The main/renderer communication follows a structured pattern: + +- Main process exposes handlers via `ipcHandle()` in `packages/main/src/plugin/index.ts` +- Handlers follow naming convention: `:` (e.g., `container-provider-registry:listContainers`) +- Renderer invokes via exposed preload APIs +- Events are sent to renderer via `apiSender.send()` for real-time updates + +### Test File Organization + +Tests are co-located with source files: + +- Unit tests: `*.spec.ts` files alongside source code +- E2E tests: Located in `tests/playwright/src/` +- Test configuration: `vitest.config.js` at root defines workspace test projects +- 627 total test files across the codebase + +### Extension Lifecycle + +1. **Discovery**: `ExtensionLoader` scans `extensions/` directory +2. **Initialization**: Extensions are loaded via `ExtensionLoader.init()` +3. **Activation**: Extensions start when `ExtensionLoader.start()` is called after system ready +4. **Runtime**: Extensions can be started/stopped/removed dynamically through registry +5. **Development**: Use `extension-development-folders` API to load extensions from custom paths + +### Configuration System + +Configuration is managed through `ConfigurationRegistry`: + +- Properties defined in extension's `package.json` under `contributes.configuration.properties` +- Scopes: `DEFAULT`, `ContainerProviderConnection`, `KubernetesProviderConnection`, `InferenceProviderConnection`, `InferenceProviderConnectionFactory` +- Configuration can be experimental and requires explicit enablement +- Values stored and retrieved with dot notation (e.g., `gemini.factory.apiKey`) + +### Flow Execution + +Flows are AI-powered automation workflows: + +- Providers register flow capabilities through `ProviderRegistry` +- `FlowManager` coordinates flow generation, execution, and lifecycle +- Flows can be deployed to Kubernetes (YAML generation) +- Goose extension provides flow runtime capabilities +- MCP servers can be connected to flows for tool access + +### MCP (Model Context Protocol) Integration + +- `MCPRegistry`: Manages MCP registry sources (like mcp-get.com) +- `MCPManager`: Handles remote MCP server connections and lifecycle +- `MCPIPCHandler`: IPC bridge for MCP operations +- MCP servers provide tools that can be accessed by AI models +- Credentials and setup stored securely via `SafeStorageRegistry` + +## Important Patterns + +### Working with Containers and Pods + +Container operations go through `ContainerProviderRegistry`: + +- List containers: `container-provider-registry:listContainers` +- Container lifecycle: `startContainer`, `stopContainer`, `deleteContainer` +- Image operations: `pullImage`, `buildImage`, `deleteImage` +- Pod operations: `createPod`, `startPod`, `stopPod`, `removePod` +- All operations require `engineId` parameter identifying the container engine + +### Kubernetes Operations + +Kubernetes operations use `KubernetesClient`: + +- Context management: `getContexts()`, `setContext()`, `deleteContext()` +- Resource operations: CRUD operations for pods, deployments, services, etc. +- Port forwarding: `createPortForward()`, `deletePortForward()` +- Exec into containers: `execIntoContainer()` with stdin/stdout callbacks +- Apply YAML: `applyResourcesFromFile()`, `applyResourcesFromYAML()` + +### Task Management + +Long-running operations should use `TaskManager`: + +```typescript +const task = taskManager.createTask({ + title: 'Operation description', + action: { + name: 'Navigate to result', + execute: () => navigationManager.navigateToResource(), + }, +}); +// Set task.status = 'success' or task.error on completion +``` + +### Creating New Extensions + +1. Create directory in `extensions/` +2. Add `package.json` with required fields: + - `engines.kortex`: Version compatibility + - `main`: Entry point (`./dist/extension.js`) + - `contributes`: Configuration, commands, etc. +3. Create `src/extension.ts` with `activate()` function +4. Add extension to build in root `package.json` scripts +5. Build using Vite: `vite build` + +### Security Considerations + +- External URLs require user confirmation (handled in `setupSecurityRestrictionsOnLinks`) +- Safe storage for credentials via `SafeStorageRegistry` +- Configuration scopes limit exposure of sensitive data +- Preload scripts enforce security boundaries between processes diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..f2fbf5fb1c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,271 @@ +# AGENTS.md + +This file provides guidance to AI code assistants when working with code in this repository. + +## Project Overview + +Kortex is an Electron-based desktop application built with Svelte that provides AI-powered container and Kubernetes management capabilities. It integrates with multiple AI providers (Gemini, OpenAI-compatible services, OpenShift AI) and supports flow execution through providers like Goose. The application also implements the Model Context Protocol (MCP) for AI agent integration. + +## Core Architecture + +### Multi-Process Electron Application + +Kortex follows the standard Electron architecture with three main processes: + +- **Main Process** (`packages/main`): Node.js backend handling system integration, extension loading, container/Kubernetes operations, and business logic +- **Renderer Process** (`packages/renderer`): Svelte-based UI running in the browser context +- **Preload Scripts** (`packages/preload` and `packages/preload-webview`): Bridge layer for secure IPC communication between main and renderer + +### Plugin System and Dependency Injection + +The core plugin system is implemented in `packages/main/src/plugin/index.ts` using Inversify for dependency injection. Key components include: + +- **PluginSystem class**: Orchestrates extension loading, configuration registry, and all core services +- **Extension Loader** (`ExtensionLoader`): Manages extension lifecycle (loading, starting, stopping, uninstalling) +- **Container**: Inversify DI container binding all major registries and managers as singletons + +All major services are registered as singletons in the DI container during initialization: + +- `ProviderRegistry`: Manages inference, container, and Kubernetes providers +- `ContainerProviderRegistry`: Handles Docker/Podman container operations +- `KubernetesClient`: Kubernetes cluster management +- `MCPManager` and `MCPRegistry`: Model Context Protocol integration +- `FlowManager`: Manages flow execution with providers like Goose +- `ChatManager`: AI chat functionality +- `ConfigurationRegistry`: Settings and configuration management + +### Extensions + +Extensions are located in the `extensions/` directory and follow a standard structure: + +- Each extension has a `package.json` with `main` pointing to `./dist/extension.js` +- Extensions must declare `engines.kortex` version compatibility +- Extensions can contribute: + - Inference providers (Gemini, OpenAI-compatible, OpenShift AI) + - Flow providers (Goose) + - MCP registries + - Configuration properties +- Extensions are built using Vite and export a standard activation API + +Available built-in extensions: + +- `gemini`: Google Gemini AI provider integration +- `goose`: Goose flow execution provider +- `mcp-registries`: MCP server registries +- `openai-compatible`: OpenAI-compatible API support +- `openshift-ai`: OpenShift AI platform integration + +### Extension API + +Extensions interact with Kortex through `@kortex-app/api` (`packages/extension-api`), which provides TypeScript definitions for: + +- Provider registration (inference, container, Kubernetes, VM, flow) +- Configuration management +- Command and menu contributions +- UI components (webviews, views, status bar items) +- Authentication providers +- MCP server integration + +## Development Commands + +### Setup and Installation + +```bash +# Install dependencies +pnpm install + +# Start in watch/development mode +pnpm watch +``` + +### Building + +```bash +# Build entire application +pnpm run build + +# Build individual packages +pnpm run build:main # Main process +pnpm run build:renderer # UI/renderer +pnpm run build:preload # Preload scripts +pnpm run build:extensions # All extensions + +# Build specific extension +cd extensions/gemini && pnpm run build +``` + +### Testing + +```bash +# Run all tests (unit + e2e) +pnpm test + +# Unit tests only +pnpm run test:unit + +# Unit tests with coverage +pnpm run test:unit:coverage + +# Run tests for specific packages +pnpm run test:main # Main process tests +pnpm run test:renderer # Renderer tests +pnpm run test:preload # Preload tests +pnpm run test:extensions # Extension tests + +# E2E tests with Playwright +pnpm run test:e2e # Build and run e2e tests +pnpm run test:e2e:run # Run e2e tests only (must build first) +pnpm run test:e2e:report # Show Playwright test report + +# Watch mode for development +pnpm run test:watch +``` + +### Code Quality + +```bash +# Format code +pnpm run format:check # Check formatting +pnpm run format:fix # Fix formatting issues + +# Linting +pnpm run lint:check # Check for linting issues +pnpm run lint:fix # Fix linting issues + +# Type checking +pnpm run typecheck # Check all packages +pnpm run typecheck:main # Main process only +pnpm run typecheck:renderer # Renderer only +pnpm run svelte:check # Svelte component type checking +``` + +### Building Distributables + +```bash +# Development build (directory output, no packaging) +pnpm run compile + +# Production build for release +pnpm run compile:next # With auto-publishing +pnpm run compile:current # Current version +pnpm run compile:pull-request # Without publishing +``` + +## Key Technical Details + +### Workspace Structure + +This is a pnpm monorepo with workspaces defined in `pnpm-workspace.yaml`: + +- `packages/*`: Core application packages (main, renderer, preload, extension-api, api, webview-api) +- `extensions/*`: Extension packages +- `scripts`: Build and utility scripts +- `tests/playwright`: E2E test suite + +### IPC Communication Pattern + +The main/renderer communication follows a structured pattern: + +- Main process exposes handlers via `ipcHandle()` in `packages/main/src/plugin/index.ts` +- Handlers follow naming convention: `:` (e.g., `container-provider-registry:listContainers`) +- Renderer invokes via exposed preload APIs +- Events are sent to renderer via `apiSender.send()` for real-time updates + +### Test File Organization + +Tests are co-located with source files: + +- Unit tests: `*.spec.ts` files alongside source code +- E2E tests: Located in `tests/playwright/src/` +- Test configuration: `vitest.config.js` at root defines workspace test projects +- 627 total test files across the codebase + +### Extension Lifecycle + +1. **Discovery**: `ExtensionLoader` scans `extensions/` directory +2. **Initialization**: Extensions are loaded via `ExtensionLoader.init()` +3. **Activation**: Extensions start when `ExtensionLoader.start()` is called after system ready +4. **Runtime**: Extensions can be started/stopped/removed dynamically through registry +5. **Development**: Use `extension-development-folders` API to load extensions from custom paths + +### Configuration System + +Configuration is managed through `ConfigurationRegistry`: + +- Properties defined in extension's `package.json` under `contributes.configuration.properties` +- Scopes: `DEFAULT`, `ContainerProviderConnection`, `KubernetesProviderConnection`, `InferenceProviderConnection`, `InferenceProviderConnectionFactory` +- Configuration can be experimental and requires explicit enablement +- Values stored and retrieved with dot notation (e.g., `gemini.factory.apiKey`) + +### Flow Execution + +Flows are AI-powered automation workflows: + +- Providers register flow capabilities through `ProviderRegistry` +- `FlowManager` coordinates flow generation, execution, and lifecycle +- Flows can be deployed to Kubernetes (YAML generation) +- Goose extension provides flow runtime capabilities +- MCP servers can be connected to flows for tool access + +### MCP (Model Context Protocol) Integration + +- `MCPRegistry`: Manages MCP registry sources (like mcp-get.com) +- `MCPManager`: Handles remote MCP server connections and lifecycle +- `MCPIPCHandler`: IPC bridge for MCP operations +- MCP servers provide tools that can be accessed by AI models +- Credentials and setup stored securely via `SafeStorageRegistry` + +## Important Patterns + +### Working with Containers and Pods + +Container operations go through `ContainerProviderRegistry`: + +- List containers: `container-provider-registry:listContainers` +- Container lifecycle: `startContainer`, `stopContainer`, `deleteContainer` +- Image operations: `pullImage`, `buildImage`, `deleteImage` +- Pod operations: `createPod`, `startPod`, `stopPod`, `removePod` +- All operations require `engineId` parameter identifying the container engine + +### Kubernetes Operations + +Kubernetes operations use `KubernetesClient`: + +- Context management: `getContexts()`, `setContext()`, `deleteContext()` +- Resource operations: CRUD operations for pods, deployments, services, etc. +- Port forwarding: `createPortForward()`, `deletePortForward()` +- Exec into containers: `execIntoContainer()` with stdin/stdout callbacks +- Apply YAML: `applyResourcesFromFile()`, `applyResourcesFromYAML()` + +### Task Management + +Long-running operations should use `TaskManager`: + +```typescript +const task = taskManager.createTask({ + title: 'Operation description', + action: { + name: 'Navigate to result', + execute: () => navigationManager.navigateToResource(), + }, +}); +// Set task.status = 'success' or task.error on completion +``` + +### Creating New Extensions + +1. Create directory in `extensions/` +2. Add `package.json` with required fields: + - `engines.kortex`: Version compatibility + - `main`: Entry point (`./dist/extension.js`) + - `contributes`: Configuration, commands, etc. +3. Create `src/extension.ts` with `activate()` function +4. Add extension to build in root `package.json` scripts +5. Build using Vite: `vite build` + +### Security Considerations + +- External URLs require user confirmation (handled in `setupSecurityRestrictionsOnLinks`) +- Safe storage for credentials via `SafeStorageRegistry` +- Configuration scopes limit exposure of sensitive data +- Preload scripts enforce security boundaries between processes diff --git a/CODE-GUIDELINES.md b/CODE-GUIDELINES.md deleted file mode 100644 index f2d60ebba3..0000000000 --- a/CODE-GUIDELINES.md +++ /dev/null @@ -1,171 +0,0 @@ -# Guidelines for Podman Desktop Code - -## Production code - -## Unit tests code - -### Use `vi.mocked`, not a generic `myFunctionMock` - -If you define a mock with `const myFunctionMock = vi.fn();` its type is `Mock`, which is a generic type. - -For example, do not write this, or Typescript won't be able to detect that you passed an object instead of a string to `mockResolvedValue`: - -```ts -const windowMethodMock = vi.fn(); - -Object.defineProperty(global, 'window', { - value: { - windowMethod: windowMethodMock, - }, -}); - -test('...', () => { - windowMethodMock.mockResolvedValue({ msg: 'a string' }); // here, Typescript is not able to detect that the type is wrong -}); -``` - -Instead, you can write `vi.mocked(window.windowMethod).mock...`, and Typescript will check that you correctly pass a string to `mockResolvedValue`: - -```ts -Object.defineProperty(global, 'window', { - value: { - windowMethod: vi.fn(), - }, -}); - -test('...', () => { - vi.mocked(window.windowMethod).mockResolvedValue('a string'); -}); -``` - -### Mock complete modules, spy on parts of module for specific tests - -When testing a module, you have to decide for each imported module if you mock the entire module or if you spy on specific functions of the module -for specific tests and keep the real implementation for the other functions. - -System modules (`node:fs`, etc) are most generally mocked, so you are sure that unit tests are executed in isolation of the system. For internal modules, -it's up to you to decide if you want to mock them or not, depending on the coverage you want for the unit tests. - -#### Mock a complete module - -Mock completely an imported module with `vi.mock('/path/to/module)`, and define mock implementation for each test with `vi.mocked(function).mock...()`. - -Use `vi.resetAllMocks()` in the top-level `beforeEach` to reset all mocks to a no-op function returning `undefined` before to start each test. - -```ts -import { existsSync } from 'node:fs'; -import { beforeEach, describe, expect, test, vi } from 'vitest'; - -// completely mock the fs module, to be sure to -// run the tests in complete isolation from the filesystem -vi.mock('node:fs'); - -beforeEach(() => { - vi.resetAllMocks(); -}); - -describe('the file exists', () => { - beforeEach(() => { - vi.mocked(existsSync).mockReturnValue(true); - }); - - test('file exists', () => { - // existsSync is mocked to return true - expect(codeCheckingIfFileExists('/file/not/found')).toBeTruthy(); - }); -}); - -describe('the file does not exist', () => { - beforeEach(() => { - vi.mocked(existsSync).mockReturnValue(false); - }); - - test('root does not exists', () => { - // existsSync is mocked to return false - expect(codeCheckingIfFileExists('/')).toBeFalsy(); - }); -}); - -test('file existence is not defined', () => { - // a no-op mock returning undefined is called - expect(codeCheckingIfFileExists('/file/not/found')).toBeUndefined(); -}); -``` - -#### Spy on a function for a specific test - -When you want to mock only one or a small number of functions of a module (for example a function of the module you are testing, or a function of an helper module from which you want to use real implementation for some functions) for a particular test, you can use `vi.spyOn(module, 'function')` to mock only `function` and keep the original implementation for the rest of the module. - -To be sure that the spied function is restored to its original implementation for the other tests, use `vi.restoreAllMocks()` in the top-level `beforeEach`. - -```ts -// helpers.ts -export function f1(): boolean { - return true; -} - -// mymodule.ts -import { f1 } from './helpers.js'; - -export class MyModuleToTest { - f2(): boolean { - return f1(); - } -} - -// mymodule.spec.ts -import { beforeEach, describe, expect, test, vi } from 'vitest'; -import { MyModuleToTest } from './mymodule.js'; -import * as helpers from './helpers.js'; - -let myModuleToTest: MyModuleToTest; - -beforeEach(() => { - myModuleToTest = new MyModuleToTest(); - - // restore f1 to its original implementation - vi.restoreAllMocks(); -}); - -describe('f1 returns false', () => { - beforeEach(() => { - vi.spyOn(helpers, 'f1').mockReturnValue(false); - }); - - test('f2 returns false', () => { - expect(myModuleToTest.f2()).toBeFalsy(); - expect(helpers.f1).toHaveBeenCalledOnce(); - }); -}); - -test('f2 returns true', () => { - // use the original implementation of f1 - expect(myModuleToTest.f2()).toBeTruthy(); - // this won't work, as f1 is not spied for this test - // expect(helpers.f1).toHaveBeenCalledOnce(); -}); -``` - -### screen.getBy vs screen.queryBy - -Calling `element = screen.getBy...` throws an error if no element is found. -For this reason, it is not necessary to call `expect(element).toBeInTheDocument()`, as the assertion -has already been done as part of `screen.getBy...`. - -It is necessary to use `element = screen.queryBy...` followed by `expect(element).not.toBeInTheDocument()` -when checking if a component does NOT exist, as this call does not throw any error, -but returns a `null` value if the element is not found. - -### Testing style attribute - -When we need to ensure a given style is applied to an HTMLElement, we should be using [tohavestyle](https://github.com/testing-library/jest-dom?tab=readme-ov-file#tohavestyle) - -#### Examples - -```ts -const { getByText } = render(); - -const text = getByText('text in the page'); -// [Good] -expect(text).toHaveStyle({ color: '#FFFFF'}); -``` diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md deleted file mode 100644 index 4209b95ddc..0000000000 --- a/CODE-OF-CONDUCT.md +++ /dev/null @@ -1,3 +0,0 @@ -## The Podman Desktop Project Community Code of Conduct - -The Podman Desktop project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). diff --git a/CODEOWNERS b/CODEOWNERS index 311b86d793..ad356dd5d0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,10 +1,18 @@ -# Default Owners -* @podman-desktop/reviewers @benoitf - -# website -website/** @podman-desktop/reviewers @slemeur @cdrage @benoitf @Firewall -website/docs/** @podman-desktop/reviewers @shipsing @slemeur @cdrage @benoitf @Firewall - -# playwright tests -tests/playwright/** @podman-desktop/reviewers @podman-desktop/qe-reviewers @benoitf +# +# Copyright (C) 2025 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +* @kortex-hub/reviewers diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 94a4b74b08..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,460 +0,0 @@ -# Contributing to Podman Desktop - -

- Podman Desktop -

- -We'd love to have you join the community! Below summarizes the processes -that we follow. - -## Topics - -- [Reporting Issues](#reporting-issues) -- [Providing Extensions](#providing-extensions) -- [Working On Issues](#working-on-issues) -- [Contributing](#contributing) -- [Continuous Integration](#continuous-integration) -- [Submitting Pull Requests](#submitting-pull-requests) -- [Communication](#communication) -- [Code Architecture](#code-architecture) -- [Maintainer Tasks](#maintainer-tasks) -- [Website Contributions](#website-contributions) - -## Reporting Issues - -Before opening an issue, check the backlog of -[open issues](https://github.com/containers/podman-desktop/issues) -to see if someone else has already reported it. - -If so, feel free to add -your scenario, or additional information, to the discussion. Or simply -"subscribe" to it to be notified when it is updated. - -If you find a new issue with the project we'd love to hear about it! The most -important aspect of a bug report is that it includes enough information for -us to reproduce it. So, please include as much detail as possible and try -to remove the extra stuff that doesn't really relate to the issue itself. -The easier it is for us to reproduce it, the faster it'll be fixed! - -Please don't include any private/sensitive information in your issue! - -## Providing Extensions - -Some of the best features of Podman Desktop aren't even in this repository! -Podman Desktop provides a set of API that expands its capabilities -by installing [extensions](https://podman-desktop.io/extensions). - -Extensions add support for new container engines, command line tools, -Kubernetes providers, or add elements to the UI like actions, badges, or views. -You can create your own extension and contribute it to the catalog for -others to use. - -See the [extension documentation](https://podman-desktop.io/docs/extensions) on our website for more information. - -## Working On Issues - -Often issues will be assigned to someone, to be worked on at a later time. - -If you are a member of the [Containers](https://github.com/containers) organization, -self-assign the issue with the `status/in-progress` label. - -If you can not set the label: add a quick comment in the issue asking that -the `status/in-progress` label to be set and a maintainer will label it. - -## Contributing - -This section describes how to start a contribution to Podman Desktop. - -### Prerequisites: Prepare your environment - -You can develop on either: `Windows`, `macOS` or `Linux`. - -Requirements: - -- [Node.js 22+](https://nodejs.org/en/) -- [pnpm v9.x](https://pnpm.io/installation) (`corepack enable pnpm`) - -Optional Linux requirements: - -- [Flatpak builder, runtime, and SDK, version 24.08](https://docs.flatpak.org/en/latest/first-build.html) - ```sh - flatpak remote-add --if-not-exists flathub --user https://flathub.org/repo/flathub.flatpakrepo - flatpak install --user flathub org.flatpak.Builder org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08 - ``` -- GNU C and C++ compiler - Fedora/RHEL - ```sh - dnf install gcc-c++ - ``` - Ubuntu/Debian - ```sh - apt-get install build-essential - ``` - -On Windows: - -- [Microsoft Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022) - -### Step 1. Fork and clone Podman Desktop - -Clone and fork the project. - -Fork the repo using GitHub site and then clone the directory: - -```sh -git clone https://github.com//podman-desktop && cd podman-desktop -``` - -### Step 2. Install dependencies - -Fetch all dependencies using the command `pnpm`: - -```sh -pnpm install -``` - -### Step 3. Start in watch mode - -Run the application in watch mode: - -```sh -pnpm watch -``` - -The dev environment will track all files changes and reload the application respectively. - -### Step 4. Write and run tests - -Write tests! Please try to write some unit tests when submitting your PR. - -Run the unit and component tests using `pnpm`: - -```sh -pnpm test:unit -``` - -Depending on to what part of project you contribute to, you can specify to run tests for the given module only, ie., if you are working on extensions, you can run the tests for extensions and have faster feedback: - -```sh -pnpm test:extensions -``` - -or if you are contributing to a particular extension, you can call: - -```sh -pnpm test:extensions:compose -``` - -This will show a test results for restricted amount of tests: - -``` - ✓ src/os.spec.ts (3) - ✓ src/detect.spec.ts (10) 518ms - ✓ src/compose-github-releases.spec.ts (10) - ✓ src/compose-extension.spec.ts (16) - ✓ src/compose-wrapper-generator.spec.ts (4) - - Test Files 5 passed (5) - Tests 43 passed (43) - Start at 17:17:07 - Duration 1.27s (transform 562ms, setup 0ms, collect 1.25s, tests 587ms) -``` - -Check the npm script tasks in our `package.json` for more options. - -### Step 5. Run E2E tests - -In case of adding new feature, it is always suitable to make sure we do not bring any new regression. For this purpose we are using the E2E tests. They can be built and run using `pnpm` with a variety of options: - -- For all the tests: - -```sh -pnpm test:e2e -``` - -- For the smoke tests: - -```sh -pnpm test:e2e:smoke -``` - -- For the extension tests: - -```sh -pnpm test:e2e:extension -``` - -You can find more specific options on the [package.json](https://github.com/containers/podman-desktop/blob/main/package.json) file, under 'scripts'. - -However, there are some things that you have to take into account: - -- In order to make the tests pass you have to either: - - Remove `settings.json` from `~/.local/share/containers/podman-desktop/configuration/` or, - - Remove the objects with keys `"welcome.version"` and `"telemetry.*"` from the file, if you do not want to lose your settings -- Some of the tests can only be executed in certain operating systems. If your execution is skipping a test, this is very likely the reason why. -- If you want to execute the tests outside of the repository, you can find a setup guide in this [README](https://github.com/containers/podman-desktop/tree/main/tests/playwright#podman-desktop-playwright-tests). - -Finally, after executing the E2E tests, you can check the results in your browser with: - -```sh -pnpm exec playwright show-report tests/playwright/output/html-results -``` - -In case of an error, you can find more information that can help you debug in the `podman-desktop/tests/playwright/output` folder. You have the video repetitions on `videos`, captures of the application failing the test on `screenshots`, and the traces of the execution on `traces`. The latter ones can be opened with `npx playwright show-trace **_NOTE:_** macOS and Windows create binaries while Linux will create a `.flatpak`. Make sure your flatpak dependencies are installed for successful compiling on Linux. - -> **_macOS NOTE:_** On macOS the `dist/` folder will contain folders for `arm64` and `universal` `.app` files. Ignore these and use the `.app` file in the `dist/mac/` folder for testing. - -## Submitting Pull Requests - -### Process - -Whether it is a large patch or a one-line bug fix, make sure you explain in detail what's changing! - -Make sure you include the issue in your PR! For example, say: `Closes #XXX`. - -PRs will be approved by an [approver][owners] listed in [`CODEOWNERS`](CODEOWNERS). - -We typically require one approval for code as well as documentation-related PR's. If it is a large code-related PR, proof of review / testing (a video / screenshot) is required. - -**Avoid enabling auto-merge** until the PR has undergone sufficient reviews and contributors have been given ample time for assessment. A maintainer will review the PR prior to the final merge. It's common for code PRs to require up to a week before merging due to reasons such as ongoing releases or dependencies on other PRs. Additionally, documentation PRs might take a few days for integration. - -Some tips for the PR process: - -- No PR too small! Feel free to open a PR against tests, bugs, new features, docs, etc. -- Make sure you include as much information as possible in your PR so maintainers can understand. -- Try to break up larger PRs into smaller ones for easier reviewing -- Any additional code changes should be in a new commit so we can see what has changed between reviews. -- Squash your commits into logical pieces of work. - -### Use the correct commit message semantics - -We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification. - -Some examples for correct titles would be: - -- `fix: prevent racing of requests` -- `chore: drop support for Node 6` -- `docs: add quickstart guide` - -For Podman Desktop we use the following types: - -- `fix:` A bug fix -- `chore:` Very small change / insignificant impact -- `docs:` Documentation only changes (ex. website) -- `build:` Changes that affect the build system -- `ci:` Changes to the CI (ex. GitHub actions) -- `feat:` A new feature -- `perf:` A code change that improves performance -- `refactor:` A code change that neither fixes a bug nor adds a feature -- `style:` Changes that affect the formatting, but not the ability of the code -- `test:` Adding missing tests / new tests - -Title formatting: - -``` -[optional scope]: - -[optional body] - -[optional footer(s)] -``` - -### Sign your PRs - -The sign-off is a line at the end of the explanation for the patch. Your -signature certifies that you wrote the patch or otherwise have the right to pass -it on as an open-source patch. - -Then you just add a line to every git commit message: - - Signed-off-by: Joe Smith - -Legal name must be used (no pseudonyms or anonymous contributions) - -If you set your `user.name` and `user.email` git configs, you can sign your -commit automatically with `git commit -s`. - -### Skipping Jobs for Draft Pull Requests on GitHub - -When creating a pull request in **draft mode** on GitHub, all CI/CD jobs are **skipped by default**. This behavior is intentional to avoid triggering unnecessary workflows while the pull request is still in progress. - -#### Triggering Jobs in Draft Mode - -If you want to run jobs for a pull request in **draft mode**, you need to manually apply the `area/ci` label to the pull request. Applying this label signals the CI system to execute the associated workflows, even though the pull request remains in draft. - -#### Steps to Trigger Jobs in Draft Mode - -1. Open your pull request in draft mode. -2. Navigate to the **Labels** section in the right-hand sidebar. -3. Apply the `area/ci` label. - -This action will trigger the configured CI/CD workflows for your draft pull request. - -#### Example Scenario - -- **Without the `area/ci` label**: No jobs will run for your draft pull request. -- **With the `area/ci` label**: Jobs will be triggered, allowing you to validate your work in progress. - -This ensures that CI resources are used efficiently while still providing flexibility for testing during the draft stage. - -### Review process - -1. Submit your PR -2. Reviewers are assigned by GitHub to two Podman Desktop developers -3. PR's require 1 LGTM / Approval (2 if it's a large code change) - -> **_NOTE:_** Confirm that your PR works on macOS, Windows and Linux if it's a significant change (not a UI improvement) - -> **_NOTE:_** If your PR hasn't been merged in an appropriate amount of time, ping the two developers assigned to the issue with `@` - -## Continuous Integration - -All pull requests and branch-merges automatically run: - -- Format and lint checking -- Cross-platform builds (Windows, macOS, Linux) -- Unit test (Linux) -- E2E tests (Linux, triggered by PR check, do not prevent merging of the PR in case of instability) - -You can follow these jobs in Github Actions https://github.com/containers/podman-desktop/actions - -## Communication - -For bugs/feature requests please [file issues](https://github.com/containers/podman-desktop/issues/new/choose) - -Discussions are possible using Github Discussions https://github.com/containers/podman-desktop/discussions/ - -## Code Architecture - -### Frameworks and tooling - -Within Podman Desktop, we use the following frameworks and tools to build the desktop application: - -- [Electron](https://www.electronjs.org/): In order to deploy cross-platform to multiple operating systems. -- [Svelte](https://svelte.dev/): The reactive UI/UX framework for building the interface. -- [Tailwind CSS](https://tailwindcss.com/): A utility-first CSS framework for the UI/UX framework. -- [Vite](https://vitejs.dev/): Dev tooling for rapid development, debugging and deployment. - -> **_NOTE:_** We also use TypeScript instead of JavaScript for strongly typed programming language development. - -### Testing - -Within Podman Desktop, we use the following for testing: - -- [Vitest](https://vitest.dev/): Unit tests - Written as `spec.ts` files. -- [Testing Library](https://testing-library.com/): Component tests - Utilities and best practices for writing component tests. -- [Playwright](https://playwright.dev/): Integration and E2E tests. - -### Folders - -Below are brief descriptions on the architecture on each folder of Podman Desktop and how it's organized. - -If you're unsure where to add code (renderer, UI, extensions, plugins) see the below TLDR: - -- `__mocks__/`: Mock packages for Vitest. -- `buildResources`: Podman Desktop logo location / build resources for electron -- `extensions`: We separate functionality into separate "extensions" to keep Podman Desktop modular. Here you'll find extensions such as Kubernetes, CRC, Podman and Docker functionality that Podman Desktop interacts with and integrates into the API (see `packages/extension-api`). Examples include `extensions/crc`, `extensions/podman`, `extensions/docker`. -- `packages/extension-api`: The extension API for extensions such as `extensions/podman` to interact with the Podman Desktop GUI. This API acts as a "middleware" to the main Electron functionality such as displaying notifications, progress messages, configuration changes, etc. -- `packages/main`: Electron process code that is responsible for creating the app's main windows, setting up system events and communicating with other processes -- `packages/preload`: Electron code that runs before the page gets rendered. Typically has access to APIs and used to setup communication processes between the main and renderer code. -- `packages/preload-docker-extension`: Electron preload code specific to the Docker Desktop extension. -- `packages/renderer`: Electron code that runs in the renderer process. The renderer runs separate to the main process and is responsible for typically rendering the main pages of Podman Desktop. Typically, this is where you find the `.svelte` code that renders the main Podman Desktop UI. -- `scripts`: Scripts Podman Desktop requires such as `pnpm watch` functionality and updating Electron vendorered modules. -- `tests`: Contains e2e tests for Podman Desktop. -- `types`: Additional types required for TypeScript. -- `website`: The documentation as well as [Podman Desktop website](https://podman-desktop.io) developed in [Docusaurus](https://docusaurus.io). -- `node_modules`: Location for Node.JS packages / dependencies. - -> **_NOTE:_** Each `extension` folder is a separately packaged module. If there are any issues with loading, make sure your module is packaged correctly. - -### UI colors - -Colors in Podman Desktop are now managed by a [`color-registry.ts`](https://github.com/containers/podman-desktop/blob/main/packages/main/src/plugin/color-registry.ts) file in order to easily switch between light and dark mode. - -When contributing a UI component to Podman Desktop that is colorized, you must go through some steps to figure out what color to use and how to reference it. - -Steps: - -1. Open the [`color-registry.ts`](https://github.com/containers/podman-desktop/blob/main/packages/main/src/plugin/color-registry.ts) file. -2. Figure out which color category from the `initColors()` function. -3. Use the referenced color with the format `[var(--pd-)]` - -Example: - -1. Choose what UI component you want to add: Ex. I want to add a new primary button. -2. Look under `initColors()` and pick `this.initButton()` and scroll down to `protected initButton()`. -3. Pick a color. I want to use the the "primary" button. So I will pick: `${button}primary-bg`. -4. Scroll up and note the `const` below `protected initButton()` which is `const button = 'button-';` -5. The color can be referenced with `[var(--pd-button-primary-bg)]`. The `[var(--pd-` portion will always be consistent when refering to a color variable. -6. For example: - -```ts - diff --git a/packages/renderer/src/lib/chat/components/McpsToInstallToast.svelte b/packages/renderer/src/lib/chat/components/McpsToInstallToast.svelte new file mode 100644 index 0000000000..4fb1f3f7b3 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/McpsToInstallToast.svelte @@ -0,0 +1,34 @@ + + +
+

The following MCPs are required to use this suggestion:

+
    + {#each mcpsToInstall as mcp (mcp.serverId)} +
  • + +
  • + {/each} +
+
diff --git a/packages/renderer/src/lib/chat/components/NoModelsAvailable.svelte b/packages/renderer/src/lib/chat/components/NoModelsAvailable.svelte new file mode 100644 index 0000000000..dc4eabfd20 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/NoModelsAvailable.svelte @@ -0,0 +1,31 @@ + + +
+

No AI Models Available

+

You need to configure at least one AI model to start chatting.

+ +
+ {#each $providerInfos as providerInfo (providerInfo.id)} + {#if providerInfo.inferenceProviderConnectionCreation} +
+
+
+ +
+ {providerInfo.name} +
+
+ +
+
+ {/if} + {/each} +
+
diff --git a/packages/renderer/src/lib/chat/components/app-sidebar.svelte b/packages/renderer/src/lib/chat/components/app-sidebar.svelte new file mode 100644 index 0000000000..676d08dea0 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/app-sidebar.svelte @@ -0,0 +1,66 @@ + + +{#if context.open} + + + +
+ { + context.setOpenMobile(false); + }} + class="flex flex-row items-center gap-3" + > + + Chatbot + + + + + {#snippet child({ props })} + + {/snippet} + + New Chat + +
+
+
+ + + +
+{/if} diff --git a/packages/renderer/src/lib/chat/components/auth-form.svelte b/packages/renderer/src/lib/chat/components/auth-form.svelte new file mode 100644 index 0000000000..bc26fcf66e --- /dev/null +++ b/packages/renderer/src/lib/chat/components/auth-form.svelte @@ -0,0 +1,89 @@ + + + + +
+
+ + + +
+ +
+ + + +
+ + {@render submitButton({ pending, success: !!form?.success })} + {@render children()} +
diff --git a/packages/renderer/src/lib/chat/components/chat-header.svelte b/packages/renderer/src/lib/chat/components/chat-header.svelte new file mode 100644 index 0000000000..aaf7d951a8 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/chat-header.svelte @@ -0,0 +1,112 @@ + + +
+ + + {#if !sidebar.open || (innerWidth.current ?? 768) < 768} + + + {#snippet child({ props })} + + {/snippet} + + New Chat + + {/if} + + {#if !readonly} + +
+ {#if noMcps} +
+ +
+ {:else} + + {/if} +
+
+ Tokens: {tokens} +
+ {/if} + + + +
diff --git a/packages/renderer/src/lib/chat/components/chat.svelte b/packages/renderer/src/lib/chat/components/chat.svelte new file mode 100644 index 0000000000..4076999311 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/chat.svelte @@ -0,0 +1,185 @@ + + +
+
+ {#if hasModels} +
+ + +
+ {#if !readonly} + + {/if} + +
+ + {:else} + + {/if} +
+
+ + + diff --git a/packages/renderer/src/lib/chat/components/code-block.svelte b/packages/renderer/src/lib/chat/components/code-block.svelte new file mode 100644 index 0000000000..e2ba7fb974 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/code-block.svelte @@ -0,0 +1,31 @@ + + +{#if inline} + + {@render children?.()} + +{:else} +
+
+          {@render children?.()}
+        
+
+{/if} diff --git a/packages/renderer/src/lib/chat/components/icons/PenToSquareIcon.svelte b/packages/renderer/src/lib/chat/components/icons/PenToSquareIcon.svelte new file mode 100644 index 0000000000..2f40e860cc --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/PenToSquareIcon.svelte @@ -0,0 +1,12 @@ + + + diff --git a/packages/renderer/src/lib/chat/components/icons/arrow-up.svelte b/packages/renderer/src/lib/chat/components/icons/arrow-up.svelte new file mode 100644 index 0000000000..7c5361342b --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/arrow-up.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/check-circle-fill.svelte b/packages/renderer/src/lib/chat/components/icons/check-circle-fill.svelte new file mode 100644 index 0000000000..749f801e18 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/check-circle-fill.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/chevron-down.svelte b/packages/renderer/src/lib/chat/components/icons/chevron-down.svelte new file mode 100644 index 0000000000..3f28c089a5 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/chevron-down.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/chevron-up.svelte b/packages/renderer/src/lib/chat/components/icons/chevron-up.svelte new file mode 100644 index 0000000000..e405e24f6a --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/chevron-up.svelte @@ -0,0 +1,24 @@ + + + diff --git a/packages/renderer/src/lib/chat/components/icons/globe.svelte b/packages/renderer/src/lib/chat/components/icons/globe.svelte new file mode 100644 index 0000000000..52d9171643 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/globe.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/loader.svelte b/packages/renderer/src/lib/chat/components/icons/loader.svelte new file mode 100644 index 0000000000..d0f93378fc --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/loader.svelte @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/lock.svelte b/packages/renderer/src/lib/chat/components/icons/lock.svelte new file mode 100644 index 0000000000..134dea753c --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/lock.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/message.svelte b/packages/renderer/src/lib/chat/components/icons/message.svelte new file mode 100644 index 0000000000..ac0b830ce4 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/message.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/more-horizontal.svelte b/packages/renderer/src/lib/chat/components/icons/more-horizontal.svelte new file mode 100644 index 0000000000..d06930e963 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/more-horizontal.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/panel-left.svelte b/packages/renderer/src/lib/chat/components/icons/panel-left.svelte new file mode 100644 index 0000000000..cc95b3ba28 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/panel-left.svelte @@ -0,0 +1,26 @@ + + + diff --git a/packages/renderer/src/lib/chat/components/icons/paperclip.svelte b/packages/renderer/src/lib/chat/components/icons/paperclip.svelte new file mode 100644 index 0000000000..e7b0a92f97 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/paperclip.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/pencil-edit.svelte b/packages/renderer/src/lib/chat/components/icons/pencil-edit.svelte new file mode 100644 index 0000000000..16241af90b --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/pencil-edit.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/plus.svelte b/packages/renderer/src/lib/chat/components/icons/plus.svelte new file mode 100644 index 0000000000..9597190370 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/plus.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/share.svelte b/packages/renderer/src/lib/chat/components/icons/share.svelte new file mode 100644 index 0000000000..ac130076b3 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/share.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/sidebar-left.svelte b/packages/renderer/src/lib/chat/components/icons/sidebar-left.svelte new file mode 100644 index 0000000000..68eecc04fb --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/sidebar-left.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/sparkles.svelte b/packages/renderer/src/lib/chat/components/icons/sparkles.svelte new file mode 100644 index 0000000000..b807562001 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/sparkles.svelte @@ -0,0 +1,34 @@ + + + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/stop.svelte b/packages/renderer/src/lib/chat/components/icons/stop.svelte new file mode 100644 index 0000000000..5c9d44eab3 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/stop.svelte @@ -0,0 +1,22 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/icons/trash.svelte b/packages/renderer/src/lib/chat/components/icons/trash.svelte new file mode 100644 index 0000000000..41b63c98ff --- /dev/null +++ b/packages/renderer/src/lib/chat/components/icons/trash.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/renderer/src/lib/chat/components/ipc-chat-transport.ts b/packages/renderer/src/lib/chat/components/ipc-chat-transport.ts new file mode 100644 index 0000000000..bcbf709948 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/ipc-chat-transport.ts @@ -0,0 +1,70 @@ +import type { ChatRequestOptions, ChatTransport, UIMessage, UIMessageChunk } from 'ai'; + +import type { ModelInfo } from '/@/lib/chat/components/model-info'; + +import { currentChatId } from '../state/current-chat-id.svelte.js'; + +interface Dependencies { + getModel: () => ModelInfo; + getMCPTools: () => Record>; + onEnd: (tokens: number) => void; +} + +export class IPCChatTransport implements ChatTransport { + constructor(private readonly dependencies: Dependencies) {} + + async sendMessages( + options: { + trigger: 'submit-message' | 'regenerate-message'; + chatId: string; + messageId: string | undefined; + messages: T[]; + abortSignal: AbortSignal | undefined; + } & ChatRequestOptions, + ): Promise> { + const uiMessages = JSON.parse(JSON.stringify(options.messages)); + const model = this.dependencies.getModel(); + console.log('Selected model', model); + const { + dependencies: { onEnd }, + } = this; + + const tools = this.dependencies.getMCPTools(); + + return new ReadableStream({ + async start(controller): Promise { + const { providerId, connectionName, label } = model; + await window.inferenceStreamText( + { + chatId: options.chatId, + providerId, + connectionName, + modelId: label, + tools, + messages: uiMessages, + }, + (chunk: UIMessageChunk) => { + console.log('IPCChatTransport->chunk:', chunk); + controller.enqueue(chunk); + }, + (error: unknown) => { + console.error('Error during inferenceStreamText:', error); + controller.error(error); + }, + (tokens: number) => { + console.log('IPCChatTransport: Stream completed tokens: ', tokens); + controller.close(); + onEnd(tokens); + }, + ); + currentChatId.value = options.chatId; + }, + }); + } + + reconnectToStream(options: { chatId: string } & ChatRequestOptions): Promise | null> { + //FIX ME: not implemented + console.log('reconnecting to stream with options', options); + return Promise.resolve(null); + } +} diff --git a/packages/renderer/src/lib/chat/components/markdown/index.ts b/packages/renderer/src/lib/chat/components/markdown/index.ts new file mode 100644 index 0000000000..d7aef19c8b --- /dev/null +++ b/packages/renderer/src/lib/chat/components/markdown/index.ts @@ -0,0 +1 @@ +export { default as Markdown } from './renderer.svelte'; diff --git a/packages/renderer/src/lib/chat/components/markdown/renderer.svelte b/packages/renderer/src/lib/chat/components/markdown/renderer.svelte new file mode 100644 index 0000000000..bbcd027b7b --- /dev/null +++ b/packages/renderer/src/lib/chat/components/markdown/renderer.svelte @@ -0,0 +1,89 @@ + + + + {#snippet ol(props)} + {@const { children, ...rest } = props} +
    + {@render children?.()} +
+ {/snippet} + {#snippet ul(props)} + {@const { children, ...rest } = props} +
    + {@render children?.()} +
+ {/snippet} + {#snippet li(props)} + {@const { children, ...rest } = props} +
  • + {@render children?.()} +
  • + {/snippet} + + {#snippet strong(props)} + {@const { children, ...rest } = props} + + {@render children?.()} + + {/snippet} + {#snippet a(props)} + {@const { children, ...rest } = props} + + {@render children?.()} + + {/snippet} + + {#snippet h1(props)} + {@const { children, ...rest } = props} +

    + {@render children?.()} +

    + {/snippet} + {#snippet h2(props)} + {@const { children, ...rest } = props} +

    + {@render children?.()} +

    + {/snippet} + {#snippet h3(props)} + {@const { children, ...rest } = props} +

    + {@render children?.()} +

    + {/snippet} + {#snippet h4(props)} + {@const { children, ...rest } = props} +

    + {@render children?.()} +

    + {/snippet} + {#snippet h5(props)} + {@const { children, ...rest } = props} +
    + {@render children?.()} +
    + {/snippet} + {#snippet h6(props)} + {@const { children, ...rest } = props} +
    + {@render children?.()} +
    + {/snippet} + {#snippet code(props)} + + {/snippet} +
    diff --git a/packages/renderer/src/lib/chat/components/mcp-filter-card.svelte b/packages/renderer/src/lib/chat/components/mcp-filter-card.svelte new file mode 100644 index 0000000000..6250e6002f --- /dev/null +++ b/packages/renderer/src/lib/chat/components/mcp-filter-card.svelte @@ -0,0 +1,71 @@ + + +
    +
    +
    + + {mcp.name} +
    +
    +
    + {#each filtered as [tool, { description }] (tool)} + {@const checked = selectedTools?.has(tool)} +
    +
    + + + + {#snippet child({ props })} +
    + {tool} + {description} +
    + {/snippet} +
    + {description} +
    +
    +
    + {/each} +
    +
    diff --git a/packages/renderer/src/lib/chat/components/mcp-messages.spec.ts b/packages/renderer/src/lib/chat/components/mcp-messages.spec.ts new file mode 100644 index 0000000000..3c9f020748 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/mcp-messages.spec.ts @@ -0,0 +1,114 @@ +/********************************************************************** + * Copyright (C) 2025 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ +import '@testing-library/jest-dom/vitest'; + +import type { UIMessage } from '@ai-sdk/svelte'; +import { fireEvent, render, screen } from '@testing-library/svelte'; +import type { DynamicToolUIPart } from 'ai'; +import { describe, expect, test } from 'vitest'; + +import MCPMessages from './mcp-messages.svelte'; + +function dynamicTool(toolName: string, id: string): DynamicToolUIPart { + return { + type: 'dynamic-tool', + state: 'output-available', + toolCallId: id, + toolName, + input: { foo: id }, + output: { bar: id }, + } as DynamicToolUIPart; +} + +describe('mcp-messages.svelte', () => { + test('shows empty state when no assistant dynamic-tool parts are present', async () => { + const messages: UIMessage[] = [ + { id: 'u1', role: 'user', parts: [{ type: 'text', text: 'Hello' }] }, + { id: 'a1', role: 'assistant', parts: [{ type: 'text', text: 'Hi!' }] }, + // dynamic-tool but from user: must be ignored + { id: 'u2', role: 'user', parts: [dynamicTool('Ignored Tool', 't0')] }, + ]; + + render(MCPMessages, { messages }); + + // Header should always be present + expect(screen.getByText('MCP')).toBeInTheDocument(); + + // Click to open the MCP panel + const showMcpButton = screen.getByRole('button', { name: 'Show MCP panel' }); + await fireEvent.click(showMcpButton); + + // Empty state should be visible + expect(screen.getByText('No MCP activity yet.')).toBeInTheDocument(); + }); + + test('renders one entry per assistant message that contains dynamic-tool parts', async () => { + const msgWithOneTool: UIMessage = { + id: 'a1', + role: 'assistant', + parts: [dynamicTool('Tool A', 't1')], + }; + + const msgWithTwoTools: UIMessage = { + id: 'a2', + role: 'assistant', + parts: [dynamicTool('Tool B1', 't2'), dynamicTool('Tool B2', 't3')], + }; + + const assistantWithoutTools: UIMessage = { + id: 'a3', + role: 'assistant', + parts: [{ type: 'text', text: 'no tools here' }], + }; + + const systemMessage: UIMessage = { + id: 's1', + role: 'system', + parts: [dynamicTool('System Tool', 'tX')], // should be ignored because not assistant + }; + + const messages = [ + { id: 'u1', role: 'user', parts: [{ type: 'text', text: 'Hello' }] } satisfies UIMessage, + msgWithOneTool, + msgWithTwoTools, + assistantWithoutTools, + systemMessage, + ]; + + render(MCPMessages, { messages }); + + // Empty state should not be present + expect(screen.queryByText('No MCP activity yet.')).not.toBeInTheDocument(); + + const showMcpButton = screen.getByRole('button', { name: 'Show MCP panel' }); + await fireEvent.click(showMcpButton); + + // The component wraps each ToolParts in a specific container div; count them + //const entryContainers = container.querySelectorAll('.rounded-md.bg-background.p-2.ring-1.ring-border'); + const entryContainers = await screen.findAllByRole('row'); + expect(entryContainers.length).toBe(2); // only a1 and a2 contain dynamic-tool parts + + // Ensure tool names from ToolParts appear (verifies children rendered) + expect(screen.getByText('Tool A', { exact: true })).toBeInTheDocument(); + expect(screen.getByText('Tool B1', { exact: true })).toBeInTheDocument(); + expect(screen.getByText('Tool B2', { exact: true })).toBeInTheDocument(); + + // Assistant without tools and non-assistant dynamic tools should not contribute entries + expect(screen.queryByText('System Tool')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/renderer/src/lib/chat/components/mcp-messages.svelte b/packages/renderer/src/lib/chat/components/mcp-messages.svelte new file mode 100644 index 0000000000..6db3caf578 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/mcp-messages.svelte @@ -0,0 +1,83 @@ + + +{#if open} + +{:else} + + +{/if} diff --git a/packages/renderer/src/lib/chat/components/mcp-selector.svelte b/packages/renderer/src/lib/chat/components/mcp-selector.svelte new file mode 100644 index 0000000000..9f85f3664c --- /dev/null +++ b/packages/renderer/src/lib/chat/components/mcp-selector.svelte @@ -0,0 +1,86 @@ + + + (open = val)}> + + {#snippet child({ props })} + + {/snippet} + + + {#if $mcpRemoteServerInfos.length === 0} + + No MCP available + + {/if} + + {#each $mcpRemoteServerInfos as mcpRemoteServerInfo (mcpRemoteServerInfo.id)} + s.id === mcpRemoteServerInfo.id)} + > +
    +
    {mcpRemoteServerInfo.name}
    +
    + +
    + +
    +
    + {/each} +
    +
    +
    diff --git a/packages/renderer/src/lib/chat/components/mcp-tools-sidepanel.svelte b/packages/renderer/src/lib/chat/components/mcp-tools-sidepanel.svelte new file mode 100644 index 0000000000..2554cf31da --- /dev/null +++ b/packages/renderer/src/lib/chat/components/mcp-tools-sidepanel.svelte @@ -0,0 +1,96 @@ + + +{#if mcpSelectorOpen} + +{:else} + + +{/if} diff --git a/packages/renderer/src/lib/chat/components/message-reasoning.svelte b/packages/renderer/src/lib/chat/components/message-reasoning.svelte new file mode 100644 index 0000000000..b098984fd7 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/message-reasoning.svelte @@ -0,0 +1,69 @@ + + +
    + {#if loading} +
    +
    Reasoning
    +
    + +
    +
    + {:else} +
    +
    Reasoned for a few seconds
    + + +
    { + expanded = !expanded; + }} + > + +
    +
    + {/if} + + {#if expanded} +
    + +
    + {/if} +
    diff --git a/packages/renderer/src/lib/chat/components/messages.svelte b/packages/renderer/src/lib/chat/components/messages.svelte new file mode 100644 index 0000000000..2403a4108c --- /dev/null +++ b/packages/renderer/src/lib/chat/components/messages.svelte @@ -0,0 +1,65 @@ + + +
    + {#if mounted && messages.length === 0} + + {/if} + + {#each messages as message (message.id)} + + {/each} + + {#if loading && messages.length > 0 && messages[messages.length - 1].role === 'user'} + + {/if} + +
    +
    diff --git a/packages/renderer/src/lib/chat/components/messages/ExportIcon.svelte b/packages/renderer/src/lib/chat/components/messages/ExportIcon.svelte new file mode 100644 index 0000000000..b1208da9e9 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/messages/ExportIcon.svelte @@ -0,0 +1,12 @@ + + + diff --git a/packages/renderer/src/lib/chat/components/messages/overview.svelte b/packages/renderer/src/lib/chat/components/messages/overview.svelte new file mode 100644 index 0000000000..aec99668c8 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/messages/overview.svelte @@ -0,0 +1,14 @@ + + +
    +
    +

    + +

    + +
    +
    diff --git a/packages/renderer/src/lib/chat/components/messages/preview-message.svelte b/packages/renderer/src/lib/chat/components/messages/preview-message.svelte new file mode 100644 index 0000000000..f41c4e1cea --- /dev/null +++ b/packages/renderer/src/lib/chat/components/messages/preview-message.svelte @@ -0,0 +1,165 @@ + + +
    + +
    + {#if message.role === 'assistant'} +
    +
    + +
    +
    + {/if} + +
    + {#if message.role === 'assistant'} + + {#if tools.length > 0} + + {/if} + {/if} + + {#if message.parts.filter(part => part.type === 'file').length > 0} +
    + {#each message.parts.filter(part => part.type === 'file') as attachment (attachment.url)} + + {/each} +
    + {/if} + + {#each message.parts as part, i (`${message.id}-${i}`)} + {@const { type } = part} + {#if type === 'reasoning'} + + {:else if type === 'text'} + {#if mode === 'view'} +
    + {#if message.role === 'user' && !readonly} + + + {#snippet child({ props })} + + {/snippet} + + Edit message + + {/if} +
    + +
    +
    + {:else if mode === 'edit'} +
    +
    + + + +
    + {/if} + + + + {/if} + {/each} + + + +
    +
    +
    diff --git a/packages/renderer/src/lib/chat/components/messages/thinking-message.svelte b/packages/renderer/src/lib/chat/components/messages/thinking-message.svelte new file mode 100644 index 0000000000..9cca02b153 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/messages/thinking-message.svelte @@ -0,0 +1,27 @@ + + +
    +
    +
    + +
    + +
    +
    Hmm...
    +
    +
    +
    diff --git a/packages/renderer/src/lib/chat/components/messages/tool-parts.spec.ts b/packages/renderer/src/lib/chat/components/messages/tool-parts.spec.ts new file mode 100644 index 0000000000..54f887ef93 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/messages/tool-parts.spec.ts @@ -0,0 +1,167 @@ +/********************************************************************** + * Copyright (C) 2025 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ +import '@testing-library/jest-dom/vitest'; + +import { render, screen } from '@testing-library/svelte'; +import userEvent from '@testing-library/user-event'; +import type { DynamicToolUIPart } from 'ai'; +import { describe, expect, test, vi } from 'vitest'; + +import ToolParts from './tool-parts.svelte'; + +function matches(content: string, val: unknown): boolean { + try { + return JSON.stringify(JSON.parse(content)) === JSON.stringify(val); + } catch (e: unknown) { + return false; + } +} + +vi.mock('svelte/transition', () => ({ + slide: (): { delay: number; duration: number } => ({ + delay: 0, + duration: 0, + }), +})); + +describe('tool-parts.svelte', () => { + test('renders tool sections collapsed by default and toggles to show input/output details', async () => { + const tools: Array = [ + { + type: 'dynamic-tool', + state: 'output-available', + toolCallId: 't1', + toolName: 'Tool Text', + input: { a: 1 }, + output: { content: [{ type: 'text', text: 'Hello world' }] }, + }, + { + type: 'dynamic-tool', + state: 'output-available', + toolCallId: 't2', + toolName: 'Tool Image', + input: { b: 2 }, + output: { content: [{ type: 'image', data: 'AAA', mimeType: 'image/png' }] }, + }, + { + type: 'dynamic-tool', + state: 'output-available', + toolCallId: 't3', + toolName: 'Tool Resource', + input: { c: 3 }, + output: { + content: [ + { + type: 'resource', + resource: { uri: 'file:///tmp/readme.txt', mimeType: 'text/plain', text: 'Some text here' }, + }, + ], + }, + }, + { + type: 'dynamic-tool', + state: 'output-available', + toolCallId: 't4', + toolName: 'Tool Result', + input: { d: 4 }, + output: { toolResult: { ok: true } }, + }, + { + type: 'dynamic-tool', + state: 'output-available', + toolCallId: 't5', + toolName: 'Tool Non-MCP', + input: { e: 5 }, + output: 'plain', + }, + { + type: 'dynamic-tool', + state: 'output-available', + toolCallId: 't6', + toolName: 'Tool Error', + input: { f: 6 }, + output: { isError: true, content: [] }, + }, + ]; + + const user = userEvent.setup(); + + render(ToolParts, { tools: tools }); + + // Verify headers are present + for (const t of tools) { + const element = screen.getByText(t.toolName); + expect(element).toBeDefined(); + expect(element).toBeInTheDocument(); + } + + // By default no input JSONs should be visible + for (const t of tools) { + expect(screen.queryByText(content => matches(content, t.input))).not.toBeInTheDocument(); + } + + // Helper to click the toggle for a given tool name + async function toggle(name: string): Promise { + const title = screen.getByText(name); + const row = title.parentElement as HTMLElement | null; + expect(row).toBeTruthy(); + const toggleEl = row!.querySelector('.cursor-pointer') as HTMLElement | null; + expect(toggleEl).toBeTruthy(); + await user.click(toggleEl!); + } + + // Expand Tool Text and assert text content rendering + await toggle('Tool Text'); + expect(screen.getByText('Input')).toBeInTheDocument(); + expect(screen.getByText(content => matches(content, { a: 1 }))).toBeInTheDocument(); + // Output renders a Text block + expect(screen.getByText('Text')).toBeInTheDocument(); + expect(screen.getByText('Hello world')).toBeInTheDocument(); + + // Collapse Tool Text and ensure input JSON disappears + await toggle('Tool Text'); + expect(screen.queryByText(content => matches(content, { a: 1 }))).not.toBeInTheDocument(); + + // Expand Tool Image and assert image rendering + await toggle('Tool Image'); + expect(screen.getByText(content => matches(content, { b: 2 }))).toBeInTheDocument(); + const img = screen.getByRole('img', { name: 'MCP image/png content' }) as HTMLImageElement; + expect(img).toBeInTheDocument(); + expect(img.src).toContain('data:image/png;base64,AAA'); + + // Expand Tool Resource and assert resource details + await toggle('Tool Resource'); + expect(screen.getAllByText('Resource').length).toBeGreaterThan(0); + expect(screen.getByText('file:///tmp/readme.txt')).toBeInTheDocument(); + // The resource also renders an inner Text section with the provided text + expect(screen.getByText('Some text here')).toBeInTheDocument(); + + // Expand Tool Result and assert toolResult branch + await toggle('Tool Result'); + expect(screen.getByText(content => matches(content, { ok: true }))).toBeInTheDocument(); + + // Expand Tool Non-MCP and assert raw JSON of output + await toggle('Tool Non-MCP'); + // JSON.stringify('plain') => "plain" + expect(screen.getByText('"plain"')).toBeInTheDocument(); + + // Expand Tool Error and check error message appears + await toggle('Tool Error'); + expect(screen.getByText('MCP reported an error')).toBeInTheDocument(); + }); +}); diff --git a/packages/renderer/src/lib/chat/components/messages/tool-parts.svelte b/packages/renderer/src/lib/chat/components/messages/tool-parts.svelte new file mode 100644 index 0000000000..194afd2e87 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/messages/tool-parts.svelte @@ -0,0 +1,165 @@ + + + +
    + {#each tools as tool (tool.toolCallId)} +
    + + +
    {tool.toolName}
    + + +
    + +
    +
    + + {#if expanded.has(tool.toolCallId)} +
    + Input + + {JSON.stringify(tool.input, null, 2)} + + + Output + {#if isMcpCallToolResult(tool.output)} + {#if isRecord(tool.output) && 'isError' in tool.output && tool.output.isError} +
    MCP reported an error
    + {/if} + + {#if isRecord(tool.output) && Array.isArray(tool.output.content)} +
    + {#each tool.output.content as item, idx (idx)} + {#if isMcpTextContent(item)} +
    +
    Text
    +
    {formatText(item.text)}
    +
    + {:else if isMcpImageContent(item)} +
    +
    Image ({item.mimeType})
    + MCP {item.mimeType} content +
    + {:else if isMcpResourceContent(item)} +
    +
    Resource
    +
    URI: {item.resource.uri}
    + {#if item.resource.mimeType} +
    MIME: {item.resource.mimeType}
    + {/if} + {#if 'text' in item.resource && item.resource.text} +
    +
    Text
    + {formatText(item.resource.text)} +
    + {:else if 'blob' in item.resource && item.resource.blob} +
    Binary blob provided.
    + {/if} +
    + {:else} + {JSON.stringify(item, null, 2)} + {/if} + {/each} +
    + {:else if isRecord(tool.output) && 'toolResult' in tool.output} + + {JSON.stringify(tool.output.toolResult, null, 2)} + + {:else} + + {JSON.stringify(tool.output, null, 2)} + + {/if} + {:else} + + {JSON.stringify(tool.output, null, 2)} + + {/if} +
    + {/if} + + {/each} +
    + diff --git a/packages/renderer/src/lib/chat/components/model-info.ts b/packages/renderer/src/lib/chat/components/model-info.ts new file mode 100644 index 0000000000..5173a74b72 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/model-info.ts @@ -0,0 +1,23 @@ +/********************************************************************** + * Copyright (C) 2025 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +export interface ModelInfo { + providerId: string; + connectionName: string; + label: string; +} diff --git a/packages/renderer/src/lib/chat/components/model-selector.svelte b/packages/renderer/src/lib/chat/components/model-selector.svelte new file mode 100644 index 0000000000..c112b0f50f --- /dev/null +++ b/packages/renderer/src/lib/chat/components/model-selector.svelte @@ -0,0 +1,97 @@ + + + (open = val)}> + + {#snippet child({ props })} + + {/snippet} + + + {#each groups.entries() as [key, mModels] (key)} + + + + {key} + + {#each mModels as model (model.label)} + +
    +
    {model.label}
    +
    + +
    + +
    +
    + {/each} + +
    + {/each} +
    +
    diff --git a/packages/renderer/src/lib/chat/components/multimodal-input.svelte b/packages/renderer/src/lib/chat/components/multimodal-input.svelte new file mode 100644 index 0000000000..326cc4d03b --- /dev/null +++ b/packages/renderer/src/lib/chat/components/multimodal-input.svelte @@ -0,0 +1,202 @@ + + +
    + {#if mounted && chatClient.messages.length === 0 && attachments.length === 0} + + {/if} + + {#if attachments.length > 0} +
    + {#each attachments as attachment (attachment.url)} + + {/each} + +
    + {/if} + + diff --git a/packages/renderer/src/lib/chat/components/ui/tooltip/index.ts b/packages/renderer/src/lib/chat/components/ui/tooltip/index.ts new file mode 100644 index 0000000000..3fd33fb1ad --- /dev/null +++ b/packages/renderer/src/lib/chat/components/ui/tooltip/index.ts @@ -0,0 +1,31 @@ +import { Tooltip as TooltipPrimitive } from 'bits-ui'; +import type { ComponentProps } from 'svelte'; + +import Content from './tooltip-content.svelte'; +import Trigger from './tooltip-trigger.svelte'; + +const Root = TooltipPrimitive.Root; +const Provider = TooltipPrimitive.Provider; +const Portal = TooltipPrimitive.Portal; + +type TooltipProps = ComponentProps; +type TooltipTriggerProps = ComponentProps; +type TooltipProviderProps = ComponentProps; +type TooltipContentProps = ComponentProps; + +export { + Content, + Portal, + Provider, + Root, + // + Root as Tooltip, + Content as TooltipContent, + type TooltipContentProps, + type TooltipProps, + Provider as TooltipProvider, + type TooltipProviderProps, + Trigger as TooltipTrigger, + type TooltipTriggerProps, + Trigger, +}; diff --git a/packages/renderer/src/lib/chat/components/ui/tooltip/tooltip-content.svelte b/packages/renderer/src/lib/chat/components/ui/tooltip/tooltip-content.svelte new file mode 100644 index 0000000000..a24ec168a5 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,48 @@ + + + + + {@render children?.()} + + {#snippet child({ props })} +
    + {/snippet} +
    +
    +
    diff --git a/packages/renderer/src/lib/chat/components/ui/tooltip/tooltip-trigger.svelte b/packages/renderer/src/lib/chat/components/ui/tooltip/tooltip-trigger.svelte new file mode 100644 index 0000000000..16086d4ef4 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/ui/tooltip/tooltip-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/renderer/src/lib/chat/components/visibility-selector.svelte b/packages/renderer/src/lib/chat/components/visibility-selector.svelte new file mode 100644 index 0000000000..51d0539073 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/visibility-selector.svelte @@ -0,0 +1,85 @@ + + + (open = val)}> + + {#snippet child({ props })} + + {/snippet} + + + + {#each visibilities as visibility (visibility.id)} + { + chatHistory.updateVisibility(chat.id, visibility.id).catch((err: unknown) => { + console.error('Failed to update chat visibility', err); + }); + open = false; + }} + class="group/item flex flex-row items-center justify-between gap-4" + data-active={visibility.id === chatFromHistory?.visibility} + > +
    + {visibility.label} +
    + {visibility.description} +
    +
    +
    + +
    +
    + {/each} +
    +
    diff --git a/packages/renderer/src/lib/chat/components/visibility-type.ts b/packages/renderer/src/lib/chat/components/visibility-type.ts new file mode 100644 index 0000000000..a5620b0047 --- /dev/null +++ b/packages/renderer/src/lib/chat/components/visibility-type.ts @@ -0,0 +1 @@ +export type VisibilityType = 'private' | 'public'; diff --git a/packages/renderer/src/lib/chat/hooks/chat-history.svelte.ts b/packages/renderer/src/lib/chat/hooks/chat-history.svelte.ts new file mode 100644 index 0000000000..11af58b253 --- /dev/null +++ b/packages/renderer/src/lib/chat/hooks/chat-history.svelte.ts @@ -0,0 +1,75 @@ +import { getContext, setContext } from 'svelte'; +import { toast } from 'svelte-sonner'; + +import type { VisibilityType } from '/@/lib/chat/components/visibility-type'; +import type { Chat } from '/@api/chat/schema.js'; + +const contextKey = Symbol('ChatHistory'); + +export class ChatHistory { + #loading = $state(false); + #revalidating = $state(false); + chats = $state([]); + + get loading(): boolean { + return this.#loading; + } + + get revalidating(): boolean { + return this.#revalidating; + } + + constructor(chatsPromise: Promise) { + this.#loading = true; + this.#revalidating = true; + chatsPromise + .then(chats => (this.chats = chats)) + .finally(() => { + this.#loading = false; + this.#revalidating = false; + }) + .catch((error: unknown) => { + console.error('Failed to load chat history', error); + }); + } + + getChatDetails = (chatId: string): Chat | undefined => { + return this.chats.find(c => c.id === chatId); + }; + + updateVisibility = async (chatId: string, visibility: VisibilityType): Promise => { + const chat = this.chats.find(c => c.id === chatId); + if (chat) { + chat.visibility = visibility; + } + const res = await fetch('/api/chat/visibility', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ chatId, visibility }), + }); + if (!res.ok) { + toast.error('Failed to update chat visibility'); + // try reloading data from source in case another competing mutation caused an issue + await this.refetch(); + } + }; + + setContext(): void { + setContext(contextKey, this); + } + + async refetch(): Promise { + this.#revalidating = true; + try { + this.chats = await window.inferenceGetChats(); + } finally { + this.#revalidating = false; + } + } + + static fromContext(): ChatHistory { + return getContext(contextKey); + } +} diff --git a/packages/renderer/src/lib/chat/hooks/is-mobile.svelte.ts b/packages/renderer/src/lib/chat/hooks/is-mobile.svelte.ts new file mode 100644 index 0000000000..8742f9f805 --- /dev/null +++ b/packages/renderer/src/lib/chat/hooks/is-mobile.svelte.ts @@ -0,0 +1,9 @@ +import { MediaQuery } from 'svelte/reactivity'; + +import { BREAKPOINTS } from '/@/lib/chat/utils/constants'; + +export class IsMobile extends MediaQuery { + constructor() { + super(`max-width: ${BREAKPOINTS.md.value - 1}${BREAKPOINTS.md.unit}`); + } +} diff --git a/packages/renderer/src/lib/chat/hooks/local-storage.svelte.ts b/packages/renderer/src/lib/chat/hooks/local-storage.svelte.ts new file mode 100644 index 0000000000..0fb61a7cc9 --- /dev/null +++ b/packages/renderer/src/lib/chat/hooks/local-storage.svelte.ts @@ -0,0 +1,39 @@ +/* eslint-disable import/no-duplicates */ +import { on } from 'svelte/events'; +import { createSubscriber } from 'svelte/reactivity'; +/* eslint-enable simple-import-sort/imports */ + +export class LocalStorage { + #key: string; + #defaultValue: T; + #subscribe: () => void; + + constructor(key: string, defaultValue: T) { + this.#key = key; + this.#defaultValue = defaultValue; + + this.#subscribe = createSubscriber(update => { + const off = on(window, 'storage', event => { + if (event.key === this.#key) { + update(); + } + }); + + return off; + }); + } + + get value(): T { + this.#subscribe(); + const storedValue = localStorage.getItem(this.#key); + return storedValue === null ? this.#defaultValue : JSON.parse(storedValue); + } + + set value(v: T) { + localStorage.setItem(this.#key, JSON.stringify(v)); + } + + delete(): void { + localStorage.removeItem(this.#key); + } +} diff --git a/packages/renderer/src/lib/chat/hooks/lock.ts b/packages/renderer/src/lib/chat/hooks/lock.ts new file mode 100644 index 0000000000..ba20e0fc35 --- /dev/null +++ b/packages/renderer/src/lib/chat/hooks/lock.ts @@ -0,0 +1,17 @@ +import { getContext, setContext } from 'svelte'; + +export class Lock { + locked = false; +} + +const lockKey = (key: string): symbol => Symbol.for(`lock:${key}`); + +export function getLock(key: string): Lock { + const k = lockKey(key); + let lock = getContext(k); + if (!lock) { + lock = new Lock(); + setContext(k, lock); + } + return lock; +} diff --git a/packages/renderer/src/lib/chat/hooks/selected-model.svelte.ts b/packages/renderer/src/lib/chat/hooks/selected-model.svelte.ts new file mode 100644 index 0000000000..a710170227 --- /dev/null +++ b/packages/renderer/src/lib/chat/hooks/selected-model.svelte.ts @@ -0,0 +1,14 @@ +import { SynchronizedCookie } from '/@/lib/chat/utils/reactivity.svelte'; + +/** + * @deprecated + */ +export class SelectedModel extends SynchronizedCookie { + constructor(value: string) { + super('selected-model', value); + } + + static fromContext(): SelectedModel { + return super.fromContext('selected-model'); + } +} diff --git a/packages/renderer/src/lib/chat/route/CustomChat.svelte b/packages/renderer/src/lib/chat/route/CustomChat.svelte new file mode 100644 index 0000000000..2ec0c8b6dd --- /dev/null +++ b/packages/renderer/src/lib/chat/route/CustomChat.svelte @@ -0,0 +1,57 @@ + + +{#if selectedChatModel} +
    + + + {#await chatMessagesPromise} + Loading + {:then { chat, messages }} + sidebarCollapsed.value = !open}> + + + + + + {/await} + +
    +{/if} + diff --git a/packages/renderer/src/lib/chat/route/chat-layout.svelte b/packages/renderer/src/lib/chat/route/chat-layout.svelte new file mode 100644 index 0000000000..745b418aad --- /dev/null +++ b/packages/renderer/src/lib/chat/route/chat-layout.svelte @@ -0,0 +1,16 @@ + + + + + {@render children?.()} + diff --git a/packages/renderer/src/lib/chat/route/main-chat-layout.svelte b/packages/renderer/src/lib/chat/route/main-chat-layout.svelte new file mode 100644 index 0000000000..9b8baf3b15 --- /dev/null +++ b/packages/renderer/src/lib/chat/route/main-chat-layout.svelte @@ -0,0 +1,12 @@ + + + + + {@render children()} + diff --git a/packages/renderer/src/lib/chat/state/current-chat-id.svelte.ts b/packages/renderer/src/lib/chat/state/current-chat-id.svelte.ts new file mode 100644 index 0000000000..fa14f347c5 --- /dev/null +++ b/packages/renderer/src/lib/chat/state/current-chat-id.svelte.ts @@ -0,0 +1 @@ +export const currentChatId = $state<{ value: string | undefined }>({ value: undefined }); diff --git a/packages/renderer/src/lib/chat/state/flow-creation-data.svelte.ts b/packages/renderer/src/lib/chat/state/flow-creation-data.svelte.ts new file mode 100644 index 0000000000..e77cd393a7 --- /dev/null +++ b/packages/renderer/src/lib/chat/state/flow-creation-data.svelte.ts @@ -0,0 +1,14 @@ +import type { ModelInfo } from '/@/lib/chat/components/model-info'; +import type { FlowGenerationParameters } from '/@api/chat/flow-generation-parameters-schema'; + +export interface FlowCreationData extends FlowGenerationParameters { + model: ModelInfo; + tools: Record; +} + +/** + * A state to temporarily hold the data for a new flow + * when navigating from a chat session to the creation page. + * It's set to `undefined` after being read to prevent stale data. + */ +export const flowCreationData = $state<{ value: FlowCreationData | undefined }>({ value: undefined }); diff --git a/packages/renderer/src/lib/chat/state/sidebar-collapsed.svelte.ts b/packages/renderer/src/lib/chat/state/sidebar-collapsed.svelte.ts new file mode 100644 index 0000000000..04634223a9 --- /dev/null +++ b/packages/renderer/src/lib/chat/state/sidebar-collapsed.svelte.ts @@ -0,0 +1 @@ +export const sidebarCollapsed = $state({ value: true }); diff --git a/packages/renderer/src/lib/chat/utils/chat.ts b/packages/renderer/src/lib/chat/utils/chat.ts new file mode 100644 index 0000000000..a63c994c60 --- /dev/null +++ b/packages/renderer/src/lib/chat/utils/chat.ts @@ -0,0 +1,54 @@ +import type { Attachment } from '@ai-sdk/ui-utils'; +import type { AssistantModelMessage, FileUIPart, ToolModelMessage, UIMessage } from 'ai'; + +import type { Document, Message } from '/@api/chat/schema.js'; + +export function convertToUIMessages(messages: Array): Array { + return messages.map(message => ({ + id: message.id, + parts: message.parts as UIMessage['parts'], + role: message.role as UIMessage['role'], + // Note: content will soon be deprecated in @ai-sdk/react + content: '', + createdAt: message.createdAt, + experimental_attachments: (message.attachments as Array) ?? [], + tokens: message.tokens, + })); +} + +export function getMostRecentUserMessage(messages: Array): UIMessage | undefined { + const userMessages = messages.filter(message => message.role === 'user'); + return userMessages.at(-1); +} + +export function getDocumentTimestampByIndex(documents: Array, index: number): Date { + if (!documents) { + return new Date(); + } + if (index > documents.length) { + return new Date(); + } + + return documents[index].createdAt; +} + +type ResponseMessageWithoutId = ToolModelMessage | AssistantModelMessage; +type ResponseMessage = ResponseMessageWithoutId & { id: string }; + +export function getTrailingMessageId({ messages }: { messages: Array }): string | null { + const trailingMessage = messages.at(-1); + + if (!trailingMessage) { + return null; + } + + return trailingMessage.id; +} + +export function fileUIPart2Attachment(part: FileUIPart): Attachment { + return { + name: part.filename ?? part.url.substring(part.url.lastIndexOf('/') + 1), + contentType: part.mediaType, + url: part.url, + }; +} diff --git a/packages/renderer/src/lib/chat/utils/constants.ts b/packages/renderer/src/lib/chat/utils/constants.ts new file mode 100644 index 0000000000..62bb23adb2 --- /dev/null +++ b/packages/renderer/src/lib/chat/utils/constants.ts @@ -0,0 +1,24 @@ +export const BREAKPOINTS = { + sm: { + unit: 'px', + value: 640, + }, + md: { + unit: 'px', + value: 768, + }, + lg: { + unit: 'px', + value: 1024, + }, + xl: { + unit: 'px', + value: 1280, + }, + '2xl': { + unit: 'px', + value: 1536, + }, +} as const; + +export const allowAnonymousChats = true; diff --git a/packages/renderer/src/lib/chat/utils/reactivity.svelte.ts b/packages/renderer/src/lib/chat/utils/reactivity.svelte.ts new file mode 100644 index 0000000000..61ef6d8209 --- /dev/null +++ b/packages/renderer/src/lib/chat/utils/reactivity.svelte.ts @@ -0,0 +1,53 @@ +import { getContext, setContext } from 'svelte'; + +export class Box { + value = $state() as T; + + constructor(value: T) { + this.value = value; + } +} + +/** + * Expects there to be a route at `/api/synchronized-cookie/:key` that sets a cookie with the given key/value. + * That handler is responsible for validating the cookie value and setting the cookie with the given key. + * This uses fire-and-forget logic for setting the cookie, optimistically updating local state. + */ +export class SynchronizedCookie { + #contextKey: symbol; + #key: string; + #value = $state()!; + + constructor(key: string, value: string) { + this.#key = key; + this.#value = value; + this.#contextKey = Symbol.for(`SynchronizedCookie:${key}`); + } + + get key(): string { + return this.#key; + } + + get value(): string { + return this.#value; + } + + set value(v: string) { + fetch(`/api/synchronized-cookie/${this.#key}`, { + method: 'POST', + body: JSON.stringify({ value: v }), + headers: { + 'Content-Type': 'application/json', + }, + }).catch(console.error); + this.#value = v; + } + + setContext(): void { + setContext(this.#contextKey, this); + } + + static fromContext(key: string): SynchronizedCookie { + return getContext(Symbol.for(`SynchronizedCookie:${key}`)); + } +} diff --git a/packages/renderer/src/lib/chat/utils/shadcn.ts b/packages/renderer/src/lib/chat/utils/shadcn.ts new file mode 100644 index 0000000000..f546a382ed --- /dev/null +++ b/packages/renderer/src/lib/chat/utils/shadcn.ts @@ -0,0 +1,13 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null }; diff --git a/packages/renderer/src/lib/chat/utils/types.ts b/packages/renderer/src/lib/chat/utils/types.ts new file mode 100644 index 0000000000..76c97ee0aa --- /dev/null +++ b/packages/renderer/src/lib/chat/utils/types.ts @@ -0,0 +1,9 @@ +import type { Snippet } from 'svelte'; + +export type WithElementRef = T & { + ref?: U | null; +}; + +export type WithElementRefAndChild = WithElementRef & { + child?: Snippet<[{ props: T }]>; +}; diff --git a/packages/renderer/src/lib/compose/ComposeActions.svelte b/packages/renderer/src/lib/compose/ComposeActions.svelte index 589f12e941..eaa43cadd3 100644 --- a/packages/renderer/src/lib/compose/ComposeActions.svelte +++ b/packages/renderer/src/lib/compose/ComposeActions.svelte @@ -125,8 +125,7 @@ if (dropdownMenu) { hidden={compose.status === 'RUNNING' || compose.status === 'STOPPING'} detailed={detailed} inProgress={compose.actionInProgress && compose.status === 'STARTING'} - icon={faPlay} - iconOffset="pl-[0.15rem]" /> + icon={faPlay} /> { vi.mocked(states).secretSearchPattern = writable(''); vi.mocked(states).kubernetesContextsCheckingStateDelayed = writable(); vi.mocked(states).kubernetesCurrentContextState = writable(); + + // Mock window.events + Object.defineProperty(window, 'events', { + value: { + receive: vi.fn(() => ({ + dispose: vi.fn(), + })), + }, + configurable: true, + writable: true, + }); }); test('Expect configmap empty screen', async () => { diff --git a/packages/renderer/src/lib/container/ContainerActions.svelte b/packages/renderer/src/lib/container/ContainerActions.svelte index d98e399b9d..9e8fa33d32 100644 --- a/packages/renderer/src/lib/container/ContainerActions.svelte +++ b/packages/renderer/src/lib/container/ContainerActions.svelte @@ -191,8 +191,7 @@ if (dropdownMenu) { hidden={container.state === 'RUNNING' || container.state === 'STOPPING'} detailed={detailed} inProgress={container.actionInProgress && container.state === 'STARTING'} - icon={faPlay} - iconOffset="pl-[0.15rem]" /> + icon={faPlay}/> { const pStatus: ProviderStatus = 'started'; const pInfo: ProviderContainerConnectionInfo = { name: 'test', + connectionType: ProviderConnectionType.CONTAINER, displayName: 'test', status: 'started', endpoint: { diff --git a/packages/renderer/src/lib/container/container-utils.ts b/packages/renderer/src/lib/container/container-utils.ts index 3726e2c06a..1634c36664 100644 --- a/packages/renderer/src/lib/container/container-utils.ts +++ b/packages/renderer/src/lib/container/container-utils.ts @@ -16,7 +16,7 @@ * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ -import type { Port } from '@podman-desktop/api'; +import type { Port } from '@kortex-app/api'; import { ContainerIcon } from '@podman-desktop/ui-svelte/icons'; // eslint-disable-next-line unicorn/prefer-node-protocol import { Buffer } from 'buffer'; diff --git a/packages/renderer/src/lib/context/contextKey.ts b/packages/renderer/src/lib/context/contextKey.ts index b7d65dda0a..6e0d2343b1 100644 --- a/packages/renderer/src/lib/context/contextKey.ts +++ b/packages/renderer/src/lib/context/contextKey.ts @@ -28,7 +28,7 @@ /* eslint-disable sonarjs/single-character-alternation */ /* eslint-disable sonarjs/updated-loop-counter */ /* eslint-disable sonarjs/function-return-type */ -import type { Event } from '@podman-desktop/api'; +import type { Event } from '@kortex-app/api'; import type { IDisposable } from '/@api/disposable.js'; diff --git a/packages/renderer/src/lib/dashboard/DashboardPage.svelte b/packages/renderer/src/lib/dashboard/DashboardPage.svelte index d2978cee2d..c213aca5e1 100644 --- a/packages/renderer/src/lib/dashboard/DashboardPage.svelte +++ b/packages/renderer/src/lib/dashboard/DashboardPage.svelte @@ -3,10 +3,8 @@ import { NavPage } from '@podman-desktop/ui-svelte'; import { SvelteMap } from 'svelte/reactivity'; import ProviderConfiguring from '/@/lib/dashboard/ProviderConfiguring.svelte'; -import ExtensionBanners from '/@/lib/recommendation/ExtensionBanners.svelte'; import { providerInfos } from '../../stores/providers'; -import LearningCenter from '../learning-center/LearningCenter.svelte'; import NotificationsBox from './NotificationsBox.svelte'; import ProviderConfigured from './ProviderConfigured.svelte'; import type { InitializationContext } from './ProviderInitUtils'; @@ -16,7 +14,6 @@ import ProviderNotInstalled from './ProviderNotInstalled.svelte'; import ProviderReady from './ProviderReady.svelte'; import ProviderStarting from './ProviderStarting.svelte'; import ProviderStopped from './ProviderStopped.svelte'; -import ReleaseNotesBox from './ReleaseNotesBox.svelte'; const providerInitContexts = new SvelteMap(); @@ -39,15 +36,16 @@ function getInitializationContext(id: string): InitializationContext { } - + {#snippet content()}
    + {#if providersReady.length > 0} {#each providersReady as providerReady (providerReady.internalId)} diff --git a/packages/renderer/src/lib/dashboard/NewContentOnDashboardBadge.spec.ts b/packages/renderer/src/lib/dashboard/NewContentOnDashboardBadge.spec.ts index c99371e2d6..6acb062c42 100644 --- a/packages/renderer/src/lib/dashboard/NewContentOnDashboardBadge.spec.ts +++ b/packages/renderer/src/lib/dashboard/NewContentOnDashboardBadge.spec.ts @@ -17,7 +17,7 @@ ***********************************************************************/ import '@testing-library/jest-dom/vitest'; -import type { ProviderStatus } from '@podman-desktop/api'; +import type { ProviderStatus } from '@kortex-app/api'; import { render, screen } from '@testing-library/svelte'; import { tick } from 'svelte'; import { router } from 'tinro'; @@ -26,7 +26,7 @@ import { expect, test } from 'vitest'; import { notificationQueue } from '/@/stores/notifications'; import { providerInfos } from '/@/stores/providers'; import type { NotificationCard } from '/@api/notification'; -import type { ProviderContainerConnectionInfo, ProviderInfo } from '/@api/provider-info'; +import { ProviderConnectionType, type ProviderContainerConnectionInfo, type ProviderInfo } from '/@api/provider-info'; import NewContentOnDashboardBadge from './NewContentOnDashboardBadge.svelte'; @@ -47,6 +47,7 @@ const notification1: NotificationCard = { const pStatus: ProviderStatus = 'started'; const pInfo: ProviderContainerConnectionInfo = { name: 'test', + connectionType: ProviderConnectionType.CONTAINER, displayName: 'test', status: 'started', endpoint: { diff --git a/packages/renderer/src/lib/dashboard/NotificationCardItem.spec.ts b/packages/renderer/src/lib/dashboard/NotificationCardItem.spec.ts deleted file mode 100644 index 88c1d7f1f6..0000000000 --- a/packages/renderer/src/lib/dashboard/NotificationCardItem.spec.ts +++ /dev/null @@ -1,135 +0,0 @@ -/********************************************************************** - * Copyright (C) 2023 Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ***********************************************************************/ -import '@testing-library/jest-dom/vitest'; - -import { faCircleExclamation, faCircleInfo, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; -import { fireEvent, render, screen } from '@testing-library/svelte'; -import { beforeAll, expect, test, vi } from 'vitest'; - -import type { NotificationCard } from '/@api/notification'; - -import NotificationCardItem from './NotificationCardItem.svelte'; - -const removeNotificationMock = vi.fn(); - -beforeAll(() => { - Object.defineProperty(window, 'removeNotification', { value: removeNotificationMock }); -}); - -test('Expect notification card to show notification title, description and close button', async () => { - const notification: NotificationCard = { - id: 1, - extensionId: 'extension', - title: 'title', - body: 'description', - type: 'info', - }; - render(NotificationCardItem, { - notification, - }); - - const titleDiv = screen.getByLabelText('Notification title'); - const descriptionDiv = screen.getByLabelText('Notification description'); - expect(titleDiv).toBeInTheDocument(); - expect(descriptionDiv).toBeInTheDocument(); - expect(titleDiv.textContent).toEqual('title'); - expect(descriptionDiv.textContent).toContain('description'); - - const deleteButton = screen.getByRole('button', { name: 'Delete notification 1' }); - expect(deleteButton).toBeInTheDocument(); - expect(deleteButton).toHaveAttribute('title', 'Delete notification'); - - await fireEvent.click(deleteButton); - - expect(removeNotificationMock).toBeCalled(); -}); - -test('Test info notification style and icon', () => { - const notification: NotificationCard = { - id: 1, - extensionId: 'extension', - title: 'Info notification title', - body: 'Info notification description', - type: 'info', - }; - const { getByTitle } = render(NotificationCardItem, { - notification, - }); - const iconTitle = getByTitle('Notification icon', { exact: false }); - const icon = iconTitle.parentElement; - // check icon shape - const pdIconPath = icon?.querySelector('path')?.getAttribute('d'); - const faIconPath = faCircleInfo.icon[4]; // index 4 is the actual path as per FA IconDefinition - expect(pdIconPath).toBe(faIconPath); - // check icon color - expect(icon).toHaveClass('text-[var(--pd-state-info)]'); - // check icon title contains the text from nested title element - expect(iconTitle.textContent).toBe('Notification icon - info'); - // check region top border - expect(screen.getByRole('region', { name: 'id: 1' })).toHaveClass('border-[var(--pd-state-info)]'); -}); - -test('Test warning notification style and icon', () => { - const notification: NotificationCard = { - id: 1, - extensionId: 'extension', - title: 'Warning notification title', - body: 'Warning notification description', - type: 'warn', - }; - const { getByTitle } = render(NotificationCardItem, { - notification, - }); - const iconTitle = getByTitle('Notification icon', { exact: false }); - const icon = iconTitle.parentElement; - // check icon shape - const pdIconPath = icon?.querySelector('path')?.getAttribute('d'); - const faIconPath = faExclamationTriangle.icon[4]; // index 4 is the actual path as per FA IconDefinition - expect(pdIconPath).toBe(faIconPath); - // check icon color - expect(icon).toHaveClass('text-[var(--pd-state-warning)]'); - // check icon title - expect(iconTitle.textContent).toBe('Notification icon - warn'); - // check region top border - expect(screen.getByRole('region', { name: 'id: 1' })).toHaveClass('border-[var(--pd-state-warning)]'); -}); - -test('Test error notification style and icon', () => { - const notification: NotificationCard = { - id: 1, - extensionId: 'extension', - title: 'Error notification title', - body: 'Error notification description', - type: 'error', - }; - const { getByTitle } = render(NotificationCardItem, { - notification, - }); - const iconTitle = getByTitle('Notification icon', { exact: false }); - const icon = iconTitle.parentElement; - // check icon shape - const pdIconPath = icon?.querySelector('path')?.getAttribute('d'); - const faIconPath = faCircleExclamation.icon[4]; // index 4 is the actual path as per FA IconDefinition - expect(pdIconPath).toBe(faIconPath); - // check icon color - expect(icon).toHaveClass('text-[var(--pd-state-error)]'); - // check icon title - expect(iconTitle.textContent).toBe('Notification icon - error'); - // check region top border - expect(screen.getByRole('region', { name: 'id: 1' })).toHaveClass('border-[var(--pd-state-error)]'); -}); diff --git a/packages/renderer/src/lib/dashboard/ProviderCard.spec.ts b/packages/renderer/src/lib/dashboard/ProviderCard.spec.ts index ab3f9df2c6..2e1c248b30 100644 --- a/packages/renderer/src/lib/dashboard/ProviderCard.spec.ts +++ b/packages/renderer/src/lib/dashboard/ProviderCard.spec.ts @@ -18,12 +18,12 @@ import '@testing-library/jest-dom/vitest'; -import type { ProviderImages } from '@podman-desktop/api'; +import type { ProviderImages } from '@kortex-app/api'; import { render, screen } from '@testing-library/svelte'; import { tick } from 'svelte'; import { expect, test } from 'vitest'; -import type { ProviderInfo } from '/@api/provider-info'; +import { type ProviderInfo } from '/@api/provider-info'; import ProviderCard from './ProviderCard.svelte'; @@ -49,6 +49,13 @@ test('Expect provider region', async () => { images: {} as ProviderImages, installationSupport: false, cleanupSupport: false, + inferenceConnections: [], + ragConnections: [], + flowConnections: [], + inferenceProviderConnectionCreation: false, + inferenceProviderConnectionInitialization: false, + ragProviderConnectionCreation: false, + ragProviderConnectionInitialization: false, }; render(ProviderCard, { provider }); @@ -78,6 +85,13 @@ test('Expect provider name', async () => { images: {} as ProviderImages, installationSupport: false, cleanupSupport: false, + inferenceConnections: [], + ragConnections: [], + flowConnections: [], + inferenceProviderConnectionCreation: false, + inferenceProviderConnectionInitialization: false, + ragProviderConnectionCreation: false, + ragProviderConnectionInitialization: false, }; render(ProviderCard, { provider }); @@ -108,6 +122,13 @@ test('Expect provider icon', async () => { images: { icon: 'test.png' } as ProviderImages, installationSupport: false, cleanupSupport: false, + inferenceConnections: [], + ragConnections: [], + flowConnections: [], + inferenceProviderConnectionCreation: false, + inferenceProviderConnectionInitialization: false, + ragProviderConnectionCreation: false, + ragProviderConnectionInitialization: false, }; render(ProviderCard, { provider }); @@ -142,7 +163,13 @@ test('Expect no provider version', async () => { images: {} as ProviderImages, installationSupport: false, cleanupSupport: false, - // no version + inferenceConnections: [], + ragConnections: [], + flowConnections: [], + inferenceProviderConnectionCreation: false, + inferenceProviderConnectionInitialization: false, + ragProviderConnectionCreation: false, + ragProviderConnectionInitialization: false, }; render(ProviderCard, { provider }); @@ -173,6 +200,13 @@ test('Expect provider version', async () => { installationSupport: false, version: '1.2.3', cleanupSupport: false, + inferenceConnections: [], + ragConnections: [], + flowConnections: [], + inferenceProviderConnectionCreation: false, + inferenceProviderConnectionInitialization: false, + ragProviderConnectionCreation: false, + ragProviderConnectionInitialization: false, }; render(ProviderCard, { provider }); @@ -203,6 +237,13 @@ test('Expect provider state', async () => { images: {} as ProviderImages, installationSupport: false, cleanupSupport: false, + inferenceConnections: [], + ragConnections: [], + flowConnections: [], + inferenceProviderConnectionCreation: false, + inferenceProviderConnectionInitialization: false, + ragProviderConnectionCreation: false, + ragProviderConnectionInitialization: false, }; render(ProviderCard, { provider }); diff --git a/packages/renderer/src/lib/dashboard/ProviderDetectionChecksButton.svelte b/packages/renderer/src/lib/dashboard/ProviderDetectionChecksButton.svelte index 5944fd825e..1ff5f771ba 100644 --- a/packages/renderer/src/lib/dashboard/ProviderDetectionChecksButton.svelte +++ b/packages/renderer/src/lib/dashboard/ProviderDetectionChecksButton.svelte @@ -1,6 +1,6 @@ -
    -
    + icon={faTrash} + state={{ status: extension.type === 'dd' ? 'stopped' : extension.removable ? extension.state : '', inProgress }} /> diff --git a/packages/renderer/src/lib/extensions/InstalledExtensionCardLeftLifecycleStart.svelte b/packages/renderer/src/lib/extensions/InstalledExtensionCardLeftLifecycleStart.svelte index 035354c6cb..5046825e81 100644 --- a/packages/renderer/src/lib/extensions/InstalledExtensionCardLeftLifecycleStart.svelte +++ b/packages/renderer/src/lib/extensions/InstalledExtensionCardLeftLifecycleStart.svelte @@ -26,5 +26,5 @@ async function startExtension(): Promise { action="start" icon={faPlay} state={{ status: extension.state, inProgress }} - leftPosition="left-[0.15rem]" /> + /> {/if} diff --git a/packages/renderer/src/lib/extensions/InstalledExtensionCardLeftLifecycleStop.svelte b/packages/renderer/src/lib/extensions/InstalledExtensionCardLeftLifecycleStop.svelte index 2ae91daed1..fabd761946 100644 --- a/packages/renderer/src/lib/extensions/InstalledExtensionCardLeftLifecycleStop.svelte +++ b/packages/renderer/src/lib/extensions/InstalledExtensionCardLeftLifecycleStop.svelte @@ -25,6 +25,5 @@ async function stopExtension(): Promise { clickAction={stopExtension} action="stop" icon={faStop} - state={{ status: extension.type === 'dd' ? 'unsupported' : extension.state, inProgress }} - leftPosition="left-[0.15rem]" /> + state={{ status: extension.type === 'dd' ? 'unsupported' : extension.state, inProgress }} /> {/if} diff --git a/packages/renderer/src/lib/featured/FeaturedExtensionDownload.svelte b/packages/renderer/src/lib/featured/FeaturedExtensionDownload.svelte index 6b1f91aca0..ceb8fadf53 100644 --- a/packages/renderer/src/lib/featured/FeaturedExtensionDownload.svelte +++ b/packages/renderer/src/lib/featured/FeaturedExtensionDownload.svelte @@ -81,10 +81,6 @@ async function installExtension(): Promise { { + >How was your experience with Kortex ?