Skip to content
Merged

Dev #51

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/auto-tag.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Auto Tag Desktop Release

on:
push:
branches:
- main
paths:
- "apps/desktop/src-tauri/tauri.conf.json"

permissions:
contents: write

jobs:
auto-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify workflow actions not pinned to 40-char SHAs
rg -nP '^\s*uses:\s*[^@]+@(?![0-9a-fA-F]{40}\b).+$' .github/workflows

Repository: BuckyMcYolo/townhall

Length of output: 230


🏁 Script executed:

# Check if auto-tag.yml exists and list workflow files
ls -la .github/workflows/ | grep -E "(auto-tag|\.yml)"

Repository: BuckyMcYolo/townhall

Length of output: 250


🏁 Script executed:

# Read the auto-tag.yml file to see the actual content around line 17
cat -n .github/workflows/auto-tag.yml | head -30

Repository: BuckyMcYolo/townhall

Length of output: 907


🏁 Script executed:

# Check for write permissions in the workflow file
grep -n "permissions:" .github/workflows/auto-tag.yml -A 10

Repository: BuckyMcYolo/townhall

Length of output: 258


Pin GitHub Action to a full commit SHA.

Line 17 uses a mutable ref (actions/checkout@v4) in a workflow with write permissions. Pin to a full SHA to reduce supply-chain risk.

🔒 Suggested change
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<full-40-char-commit-sha>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/auto-tag.yml at line 17, Replace the mutable reference
actions/checkout@v4 with the action pinned to its full commit SHA to eliminate
supply-chain risk; locate the usage of actions/checkout@v4 in the workflow (the
checkout step) and update it to the specific commit SHA from the
actions/checkout repository (replace the tag with the full SHA string), ensuring
the workflow still has the required permissions and testing the workflow after
the change.

with:
fetch-depth: 0

- name: Get version from tauri.conf.json
id: version
run: |
VERSION=$(jq -r '.version' apps/desktop/src-tauri/tauri.conf.json)
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
echo "::error::No version found in tauri.conf.json"
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"

Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: Check if tag already exists
id: check
run: |
if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Create and push tag
if: steps.check.outputs.exists == 'false'
run: |
git tag "${{ steps.version.outputs.tag }}"
git push origin "${{ steps.version.outputs.tag }}"
Comment on lines +32 to +45
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make tag check/push remote-aware and idempotent.

Lines 31–41 use a local git rev-parse check followed by push. That introduces a TOCTOU race and can fail when concurrent runs target the same version.

🛠️ Suggested reliability fix
       - name: Check if tag already exists
         id: check
         run: |
-          if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
+          TAG="${{ steps.version.outputs.tag }}"
+          if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
             echo "exists=true" >> "$GITHUB_OUTPUT"
           else
             echo "exists=false" >> "$GITHUB_OUTPUT"
           fi

       - name: Create and push tag
         if: steps.check.outputs.exists == 'false'
         run: |
-          git tag "${{ steps.version.outputs.tag }}"
-          git push origin "${{ steps.version.outputs.tag }}"
+          TAG="${{ steps.version.outputs.tag }}"
+          git tag "${TAG}"
+          if ! git push origin "refs/tags/${TAG}"; then
+            # If another run won the race, treat as success.
+            git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1
+          fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/auto-tag.yml around lines 28 - 41, The current tag
existence check uses local git rev-parse (steps.check) which causes a TOCTOU
race; replace that local check with a remote-aware check using git ls-remote
against origin for the tag name from steps.version.outputs.tag (or
steps.version.outputs.version if that's the tag) and set exists based on that
remote query, then only push if remote reports missing; additionally make the
push idempotent by handling a "remote already exists" push failure as success
(i.e., after a failed git push origin "refs/tags/${{ steps.version.outputs.tag
}}" re-run git ls-remote to confirm if the tag now exists and treat as success
if so).

13 changes: 12 additions & 1 deletion apps/desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "app"
version = "0.1.0"
version = "0.1.1"
description = "A Tauri App"
authors = ["you"]
license = ""
Expand All @@ -24,4 +24,5 @@ log = "0.4"
tauri = { version = "2.10.3", features = [] }
tauri-plugin-log = "2"
tauri-plugin-notification = "2"
tauri-plugin-process = "2"
tauri-plugin-updater = "2"
7 changes: 6 additions & 1 deletion apps/desktop/src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@
"identifier": "default",
"description": "enables the default permissions",
"windows": ["main"],
"permissions": ["core:default", "notification:default", "updater:default"]
"permissions": [
"core:default",
"notification:default",
"process:default",
"updater:default"
]
}
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.setup(|app| {
if cfg!(debug_assertions) {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Townhall",
"version": "0.1.0",
"version": "0.1.1",
"identifier": "com.townhall.desktop",
"build": {
"frontendDist": "https://app.townhall.chat",
Expand Down
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-router": "^1.120.3",
"@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-updater": "^2.10.1",
"@tiptap/extension-link": "^3.20.0",
"@tiptap/extension-mention": "^3.20.0",
"@tiptap/markdown": "^3.20.0",
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/components/guild/guild-settings-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { authClient } from "@repo/auth/client"
import { Avatar, AvatarFallback, AvatarImage } from "@repo/ui/components/avatar"
import { Button } from "@repo/ui/components/button"
import {
Expand Down
48 changes: 48 additions & 0 deletions apps/web/src/components/onboarding/onboarding-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client"

import { authClient } from "@repo/auth/client"
import { env } from "@repo/env/client"
import { Button } from "@repo/ui/components/button"
import { Checkbox } from "@repo/ui/components/checkbox"
import {
Dialog,
DialogContent,
Expand All @@ -24,6 +26,9 @@ type Step = "username" | "welcome" | "create" | "join"
const MIN_USERNAME_LENGTH = 3
const MAX_USERNAME_LENGTH = 30
const USERNAME_REGEX = /^[a-zA-Z0-9_.]+$/
// TODO: Remove hardcoded invite code once we have a proper discovery/featured guilds system
const TOWNHALL_INVITE_CODE = "k9yDieWZ"
const showTownhallJoin = !env.NEXT_PUBLIC_SELF_HOSTED

function normalizeSlugInput(value: string) {
return value
Expand Down Expand Up @@ -64,11 +69,20 @@ export function OnboardingDialog({ open }: { open: boolean }) {
const [slug, setSlug] = useState("")
const [slugEdited, setSlugEdited] = useState(false)
const [inviteLink, setInviteLink] = useState("")
const [joinTownhall, setJoinTownhall] = useState(showTownhallJoin)
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
const queryClient = useQueryClient()
const navigate = useNavigate()

const acceptTownhallInvite = useCallback(() => {
if (!joinTownhall) return
apiClient.v1.invites[":code"].accept
.$post({ param: { code: TOWNHALL_INVITE_CODE } })
.then(() => queryClient.invalidateQueries({ queryKey: ["guilds"] }))
.catch((err) => console.error("Failed to join Townhall guild:", err))
}, [joinTownhall, queryClient])
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Username step state
const [username, setUsername] = useState("")
const [usernameAvailability, setUsernameAvailability] = useState<
Expand Down Expand Up @@ -184,6 +198,8 @@ export function OnboardingDialog({ open }: { open: boolean }) {
const createdGuildSlug = res.data?.slug ?? normalizedSlug
await queryClient.invalidateQueries({ queryKey: ["guilds"] })

acceptTownhallInvite()

let firstChannelId: string | null = null
try {
firstChannelId = await getFirstChannelId(createdGuildSlug)
Expand Down Expand Up @@ -224,6 +240,10 @@ export function OnboardingDialog({ open }: { open: boolean }) {
return
}

if (inviteCode !== TOWNHALL_INVITE_CODE) {
acceptTownhallInvite()
}

await navigate({
to: "/invite/$code",
params: { code: inviteCode },
Expand Down Expand Up @@ -375,6 +395,34 @@ export function OnboardingDialog({ open }: { open: boolean }) {
</div>
</button>
</div>

{showTownhallJoin && (
// biome-ignore lint/a11y/useKeyWithClickEvents: Label + Checkbox handle keyboard a11y
<div
onClick={() => setJoinTownhall((prev) => !prev)}
className="mt-4 flex w-full cursor-pointer items-start gap-3 rounded-lg border border-border bg-card p-4 text-left transition-colors hover:bg-accent"
>
<Checkbox
id="join-townhall"
checked={joinTownhall}
onCheckedChange={(checked) =>
setJoinTownhall(checked === true)
}
onClick={(e) => e.stopPropagation()}
className="mt-0.5"
/>
<Label
htmlFor="join-townhall"
className="cursor-pointer font-normal"
>
<p className="font-medium">Join Townhall's Townhall</p>
<p className="text-sm text-muted-foreground">
Join our open-source community and help shape the
product roadmap
</p>
</Label>
</div>
)}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</>
)}

Expand Down
Loading
Loading