From 086a98050c5d359434fbe518306c1630589a4193 Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Sat, 6 Dec 2025 17:17:13 -0800 Subject: [PATCH] lint fixes --- .github/workflows/custom_build.yml | 26 +- .github/workflows/custom_build_test.yml | 26 +- .prettierignore | 1 + README.md | 1 - assets/site.webmanifest | 2 +- components/DiscordButton.tsx | 36 +- components/Link.tsx | 12 +- components/ModuleCard.tsx | 33 +- components/ModuleToggle.tsx | 11 +- components/PluginToggle.tsx | 30 +- components/RedditButton.tsx | 107 +----- components/SourceAvailable.tsx | 5 +- components/ui/button.tsx | 53 ++- components/ui/checkbox.tsx | 14 +- components/ui/input.tsx | 10 +- components/ui/sonner.tsx | 18 +- components/ui/switch.tsx | 31 +- constants/architecture-hierarchy.json | 2 +- constants/versions.ts | 476 ++++++++++++------------ convex/README.md | 34 +- convex/actions.ts | 39 +- convex/admin.ts | 36 +- convex/auth.config.ts | 2 +- convex/auth.ts | 4 +- convex/builds.ts | 153 +++----- convex/helpers.ts | 59 ++- convex/http.ts | 30 +- convex/lib/r2.ts | 10 +- convex/plugins.ts | 24 +- convex/profiles.ts | 42 +-- convex/schema.ts | 14 +- lib/utils.ts | 80 ++-- pages/+Head.tsx | 14 +- pages/+Layout.tsx | 24 +- pages/+onPageTransitionEnd.ts | 4 +- pages/+onPageTransitionStart.ts | 8 +- pages/_error/+Page.tsx | 8 +- pages/admin/+Page.tsx | 62 +-- pages/builds/new/+Page.tsx | 4 +- pages/builds/new/@buildHash/+Page.tsx | 4 +- pages/docs/+Layout.tsx | 18 +- pages/docs/registry/+Page.mdx | 1 - prettier.config.js | 4 +- vendor/web-flasher | 2 +- vite.config.ts | 14 +- 45 files changed, 691 insertions(+), 897 deletions(-) create mode 100644 .prettierignore diff --git a/.github/workflows/custom_build.yml b/.github/workflows/custom_build.yml index 5643121..d9a4d64 100644 --- a/.github/workflows/custom_build.yml +++ b/.github/workflows/custom_build.yml @@ -4,30 +4,30 @@ on: workflow_dispatch: inputs: target: - description: 'Target board (e.g. rak4631)' + description: "Target board (e.g. rak4631)" required: true type: string flags: - description: 'Build flags (e.g. -DMESHTASTIC_EXCLUDE_MQTT)' + description: "Build flags (e.g. -DMESHTASTIC_EXCLUDE_MQTT)" required: false type: string version: - description: 'Firmware Version (Tag/Branch)' + description: "Firmware Version (Tag/Branch)" required: true build_id: - description: 'Convex Build ID' + description: "Convex Build ID" required: true type: string build_hash: - description: 'Build hash for artifact naming' + description: "Build hash for artifact naming" required: true type: string convex_url: - description: 'Convex Site URL' + description: "Convex Site URL" required: true type: string plugins: - description: 'Space-separated plugin slugs to install' + description: "Space-separated plugin slugs to install" required: false type: string @@ -71,7 +71,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.x' + python-version: "3.x" - name: Update Status - Fetching Firmware shell: bash @@ -160,13 +160,13 @@ jobs: - PlatformIO dependencies (\`.pio/\`) are not included - PlatformIO will download these as needed - The build flags above must be set exactly as shown to reproduce the build EOF - + # Create MESHFORGE.md so it gets included in the archive # (already created above, just ensuring it exists) - + # Define archive suffix for consistent naming ARTIFACT_ARCHIVE_SUFFIX="-${{ inputs.build_hash }}-${{ github.run_id }}.tar.gz" - + # Create archive from working directory to include plugins installed by mpm # Exclude .git, .pio, and build artifacts cd .. @@ -177,12 +177,12 @@ jobs: --exclude='build' \ -czf "source${ARTIFACT_ARCHIVE_SUFFIX}" \ -C firmware . - + update_status uploading_source_archive SOURCE_ARCHIVE_PATH="/source${ARTIFACT_ARCHIVE_SUFFIX}" SOURCE_OBJECT_PATH="${R2_BUCKET_NAME}/source${ARTIFACT_ARCHIVE_SUFFIX}" - + # Upload source archive to R2 wrangler r2 object put "$SOURCE_OBJECT_PATH" \ --file "source${ARTIFACT_ARCHIVE_SUFFIX}" --remote diff --git a/.github/workflows/custom_build_test.yml b/.github/workflows/custom_build_test.yml index 5643121..d9a4d64 100644 --- a/.github/workflows/custom_build_test.yml +++ b/.github/workflows/custom_build_test.yml @@ -4,30 +4,30 @@ on: workflow_dispatch: inputs: target: - description: 'Target board (e.g. rak4631)' + description: "Target board (e.g. rak4631)" required: true type: string flags: - description: 'Build flags (e.g. -DMESHTASTIC_EXCLUDE_MQTT)' + description: "Build flags (e.g. -DMESHTASTIC_EXCLUDE_MQTT)" required: false type: string version: - description: 'Firmware Version (Tag/Branch)' + description: "Firmware Version (Tag/Branch)" required: true build_id: - description: 'Convex Build ID' + description: "Convex Build ID" required: true type: string build_hash: - description: 'Build hash for artifact naming' + description: "Build hash for artifact naming" required: true type: string convex_url: - description: 'Convex Site URL' + description: "Convex Site URL" required: true type: string plugins: - description: 'Space-separated plugin slugs to install' + description: "Space-separated plugin slugs to install" required: false type: string @@ -71,7 +71,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.x' + python-version: "3.x" - name: Update Status - Fetching Firmware shell: bash @@ -160,13 +160,13 @@ jobs: - PlatformIO dependencies (\`.pio/\`) are not included - PlatformIO will download these as needed - The build flags above must be set exactly as shown to reproduce the build EOF - + # Create MESHFORGE.md so it gets included in the archive # (already created above, just ensuring it exists) - + # Define archive suffix for consistent naming ARTIFACT_ARCHIVE_SUFFIX="-${{ inputs.build_hash }}-${{ github.run_id }}.tar.gz" - + # Create archive from working directory to include plugins installed by mpm # Exclude .git, .pio, and build artifacts cd .. @@ -177,12 +177,12 @@ jobs: --exclude='build' \ -czf "source${ARTIFACT_ARCHIVE_SUFFIX}" \ -C firmware . - + update_status uploading_source_archive SOURCE_ARCHIVE_PATH="/source${ARTIFACT_ARCHIVE_SUFFIX}" SOURCE_OBJECT_PATH="${R2_BUCKET_NAME}/source${ARTIFACT_ARCHIVE_SUFFIX}" - + # Upload source archive to R2 wrangler r2 object put "$SOURCE_OBJECT_PATH" \ --file "source${ARTIFACT_ARCHIVE_SUFFIX}" --remote diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..5657f6e --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +vendor \ No newline at end of file diff --git a/README.md b/README.md index 0b7a536..3ec99e2 100644 --- a/README.md +++ b/README.md @@ -33,4 +33,3 @@ bun run lint ## License [Add your license here] - diff --git a/assets/site.webmanifest b/assets/site.webmanifest index ccf313a..981d97f 100644 --- a/assets/site.webmanifest +++ b/assets/site.webmanifest @@ -18,4 +18,4 @@ "theme_color": "#ffffff", "background_color": "#ffffff", "display": "standalone" -} \ No newline at end of file +} diff --git a/components/DiscordButton.tsx b/components/DiscordButton.tsx index e684210..af4aab4 100644 --- a/components/DiscordButton.tsx +++ b/components/DiscordButton.tsx @@ -1,47 +1,27 @@ -import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { Button } from "@/components/ui/button" +import { cn } from "@/lib/utils" function DiscordIcon(props: React.SVGProps) { return ( - + ) } interface DiscordButtonProps { - variant?: 'default' | 'outline' | 'ghost' | 'link' | 'destructive' - size?: 'default' | 'sm' | 'lg' | 'icon' + variant?: "default" | "outline" | "ghost" | "link" | "destructive" + size?: "default" | "sm" | "lg" | "icon" className?: string } -export function DiscordButton({ - variant = 'outline', - size, - className, -}: DiscordButtonProps) { +export function DiscordButton({ variant = "outline", size, className }: DiscordButtonProps) { return ( - -
@@ -29,32 +20,20 @@ export function ModuleCard({
{selected && ( - + Checkmark - + )}

{name}

-

- {description} -

+

{description}

diff --git a/components/ModuleToggle.tsx b/components/ModuleToggle.tsx index 3bd8ce8..e3e13e7 100644 --- a/components/ModuleToggle.tsx +++ b/components/ModuleToggle.tsx @@ -1,4 +1,4 @@ -import { Switch } from '@/components/ui/switch' +import { Switch } from "@/components/ui/switch" interface ModuleToggleProps { id: string @@ -8,12 +8,7 @@ interface ModuleToggleProps { onToggle: (excluded: boolean) => void } -export function ModuleToggle({ - name, - description, - isExcluded, - onToggle, -}: ModuleToggleProps) { +export function ModuleToggle({ name, description, isExcluded, onToggle }: ModuleToggleProps) { return (
@@ -26,7 +21,7 @@ export function ModuleToggle({ onCheckedChange={onToggle} labelLeft="Default" labelRight="Excluded" - className={isExcluded ? 'bg-orange-600' : 'bg-slate-600'} + className={isExcluded ? "bg-orange-600" : "bg-slate-600"} />
diff --git a/components/PluginToggle.tsx b/components/PluginToggle.tsx index 5508c30..8127dbb 100644 --- a/components/PluginToggle.tsx +++ b/components/PluginToggle.tsx @@ -1,5 +1,5 @@ -import { ExternalLink, Star } from 'lucide-react' -import { Switch } from '@/components/ui/switch' +import { Switch } from "@/components/ui/switch" +import { ExternalLink, Star } from "lucide-react" interface PluginToggleProps { id: string @@ -26,17 +26,17 @@ export function PluginToggle({ homepage, version, disabled = false, - enabledLabel = 'Add', + enabledLabel = "Add", incompatibleReason, }: PluginToggleProps) { const isIncompatible = !!incompatibleReason - + return (
{/* Flash count and homepage links in lower right */} @@ -63,7 +63,7 @@ export function PluginToggle({ target="_blank" rel="noopener noreferrer" className="flex items-center gap-1 text-slate-400 hover:text-slate-300 transition-colors" - onClick={(e) => e.stopPropagation()} + onClick={e => e.stopPropagation()} > @@ -71,20 +71,14 @@ export function PluginToggle({
-

- {name} -

- {featured && ( - - )} +

{name}

+ {featured && }
-

+

{description}

{isIncompatible && incompatibleReason && ( -

- {incompatibleReason} -

+

{incompatibleReason}

)}
@@ -94,7 +88,7 @@ export function PluginToggle({ disabled={disabled} labelLeft="Skip" labelRight={enabledLabel} - className={isEnabled ? 'bg-green-600' : 'bg-slate-600'} + className={isEnabled ? "bg-green-600" : "bg-slate-600"} />
diff --git a/components/RedditButton.tsx b/components/RedditButton.tsx index 8208153..8c1a647 100644 --- a/components/RedditButton.tsx +++ b/components/RedditButton.tsx @@ -1,16 +1,9 @@ -import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { Button } from "@/components/ui/button" +import { cn } from "@/lib/utils" function RedditIcon(props: React.SVGProps) { return ( - + ) { strokeWidth="2" d="M12 9.42c4.42 0 8 2.37 8 5.29c0 2.92 -3.58 5.29 -8 5.29c-4.42 0 -8 -2.37 -8 -5.29c0 -2.92 3.58 -5.29 8 -5.29Z" > - - + + - + - + - + @@ -85,32 +49,14 @@ function RedditIcon(props: React.SVGProps) { repeatCount="indefinite" values="M12 8.75L13.18 3.11L18.21 4.18;M12 8.75L12 2L12 4.18;M12 8.75L10.82 3.11L5.79 4.18;M12 8.75L12 2L12 4.18;M12 8.75L13.18 3.11L18.21 4.18" /> - + - + - + ) { strokeWidth=".8" d="M8.47 17.52c0 0 0.94 1.06 3.53 1.06c2.58 0 3.53 -1.06 3.53 -1.06" > - + @@ -138,31 +78,18 @@ function RedditIcon(props: React.SVGProps) { } interface RedditButtonProps { - variant?: 'default' | 'outline' | 'ghost' | 'link' | 'destructive' - size?: 'default' | 'sm' | 'lg' | 'icon' + variant?: "default" | "outline" | "ghost" | "link" | "destructive" + size?: "default" | "sm" | "lg" | "icon" className?: string } -export function RedditButton({ - variant = 'outline', - size, - className, -}: RedditButtonProps) { +export function RedditButton({ variant = "outline", size, className }: RedditButtonProps) { return ( - - - {checked && labelRight && ( - - {labelRight} - - )} - {!checked && labelLeft && ( - - {labelLeft} - - )} + {checked && labelRight && {labelRight}} + {!checked && labelLeft && {labelLeft}} ) } diff --git a/constants/architecture-hierarchy.json b/constants/architecture-hierarchy.json index 5bd3994..6cf6930 100644 --- a/constants/architecture-hierarchy.json +++ b/constants/architecture-hierarchy.json @@ -217,4 +217,4 @@ "rp2350": null, "stm32": null, "portduino": null -} \ No newline at end of file +} diff --git a/constants/versions.ts b/constants/versions.ts index 0830a76..37e0bb9 100644 --- a/constants/versions.ts +++ b/constants/versions.ts @@ -1,243 +1,243 @@ // This file is auto-generated by scripts/generate-versions.js export const VERSIONS = [ - 'v2.7.16.a597230', - 'v2.7.15.567b8ea', - 'v2.7.14.e959000', - 'v2.7.13.597fa0b', - 'v2.7.12.45f15b8', - 'v2.7.11.ee68575', - 'v2.7.10.94d4bdf', - 'v2.7.9.70724be', - 'v2.7.8.a0c0388', - 'v2.7.7.5ae4ff9', - 'v2.7.6.834c3c5', - 'v2.7.5.ddd1499', - 'v2.7.4.c1f4f79', - 'v2.7.3.cf574c7', - 'v2.7.2.f6d3782', - 'v2.7.1.f35ca81', - 'v2.7.0.705515a', - 'v2.7.0.195b7cc', - 'v2.6.13.0561f2c', - 'v2.6.12.9861e82', - 'v2.6.11.60ec05e', - 'v2.6.10.9ce4455', - 'v2.6.9.f223b8a', - 'v2.6.8.ef9d0d7', - 'v2.6.7.2d6181f', - 'v2.6.6.54c1423', - 'v2.6.5.fc3d9f2', - 'v2.6.4.b89355f', - 'v2.6.3.d28af68', - 'v2.6.3.640e731', - 'v2.6.2.31c0e8f', - 'v2.6.1.7c3edde', - 'v2.6.0.f7afa9a', - 'v2.5.23.bf958ed', - 'v2.5.22.d1fa27d', - 'v2.5.21.447533a', - 'v2.5.20.4c97351', - 'v2.5.19.f9876cf', - 'v2.5.19.d5cd6f8', - 'v2.5.18.89ebafc', - 'v2.5.17.b4b2fd6', - 'v2.5.16.f81d3b0', - 'v2.5.15.79da236', - 'v2.5.14.f2ee0df', - 'v2.5.13.295278b', - 'v2.5.13.1a06f88', - 'v2.5.12.aa184e6', - 'v2.5.11.8e2a3e5', - 'v2.5.10.0fc5c9b', - 'v2.5.9.936260f', - 'v2.5.8.6485f03', - 'v2.5.7.f77c87d', - 'v2.5.6.d55c08d', - 'v2.5.5.e182ae7', - 'v2.5.4.8d288d5', - 'v2.5.3.a70d5ee', - 'v2.5.2.771cb52', - 'v2.5.1.c13b44b', - 'v2.5.0.e470619', - 'v2.5.0.d6dac17', - 'v2.5.0.ab7de7f', - 'v2.5.0.33eb073', - 'v2.5.0.9e55e6b', - 'v2.5.0.9ac0e26', - 'v2.4.3.efc27f2', - 'v2.4.3.91d6612', - 'v2.4.2.5b45303', - 'v2.4.1.394e0e1', - 'v2.4.0.46d7b82', - 'v2.3.15.deb7c27', - 'v2.3.14.64531fa', - 'v2.3.13.83f5ba0', - 'v2.3.12.24458a7', - 'v2.3.11.2740a56', - 'v2.3.10.d19607b', - 'v2.3.9.f06c56a', - 'v2.3.8.d490a33', - 'v2.3.7.30fbcab', - 'v2.3.6.7a3570a', - 'v2.3.5.2f9b68e', - 'v2.3.4.ea61808', - 'v2.3.3.8187fa7', - 'v2.3.2.63df972', - 'v2.3.1.4fa7f5a', - 'v2.3.0.5f47ca1', - 'v2.2.24.e6a2c06', - 'v2.2.23.5672e68', - 'v2.2.22.404d0dd', - 'v2.2.21.7f7c5cb', - 'v2.2.20.af5ac32', - 'v2.2.19.8f6a283', - 'v2.2.18.e9bde80', - 'v2.2.17.dbac2b1', - 'v2.2.16.1c6acfd', - 'v2.2.15.31c4693', - 'v2.2.14.57542ce', - 'v2.2.13.f570204', - 'v2.2.12.092e6f2', - 'v2.2.11.10265aa', - 'v2.2.10.7cebd79', - 'v2.2.9.47301a5', - 'v2.2.8.61f6fb2', - 'v2.2.7.e8970ad', - 'v2.2.6.b53cb38', - 'v2.2.5.8255128', - 'v2.2.4.3bcab0e', - 'v2.2.3.282cc0b', - 'v2.2.2.f35c7be', - 'v2.2.1.fb5f2e4', - 'v2.2.0.9f6584b', - 'v2.1.23.04bbdc6', - 'v2.1.22.191a69d', - 'v2.1.21.97d7a89', - 'v2.1.20.470363d', - 'v2.1.19.eb7025f', - 'v2.1.18.de53280', - 'v2.1.17.7ca2e81', - 'v2.1.16.a2c5b92', - 'v2.1.15.cd78723', - 'v2.1.14.99a31c1', - 'v2.1.13.7475c86', - 'v2.1.12.7711b03', - 'v2.1.11.5ec624d', - 'v2.1.10.7ef12c7', - 'v2.1.9.d43ddc9', - 'v2.1.8.ee971e3', - 'v2.1.7.242f880', - 'v2.1.6.5679a82', - 'v2.1.5.23272da', - 'v2.1.4.958d2cf', - 'v2.1.3.8c68d88', - 'v2.1.2.6d20215', - 'v2.1.1.dc2ca9c', - 'v2.1.0.331a1af', - 'v2.0.23.7bb281d', - 'v2.0.22.fbfd0f1', - 'v2.0.21.83e6cea', - 'v2.0.20.7100416', - 'v2.0.19.3209aea', - 'v2.0.18.1a7991c', - 'v2.0.17.5d1c06b', - 'v2.0.16.2242b68', - 'v2.0.15.aafbde0', - 'v2.0.14.2baaad8', - 'v2.0.13.7e27729', - 'v2.0.12.2400dd4', - 'v2.0.11.8914d1a', - 'v2.0.10.e09b12c', - 'v2.0.9.6ea0963', - 'v2.0.8.090e166', - 'v2.0.7.91ff7b9', - 'v2.0.6.97fd5cf', - 'v2.0.5.65e8209', - 'v2.0.4.5417671', - 'v2.0.3.09fe616', - 'v2.0.2.8146e84', - 'v2.0.1.ad05b91', - 'v2.0.0.18ab874', - 'v1.3.48.82bcd39', - 'v1.3.47.05147c0', - 'v1.3.46.d4ea956', - 'v1.3.45.b0d0552', - 'v1.3.44.4fa8d02', - 'v1.3.43.aae9d2f', - 'v1.3.42.9bd9252', - 'v1.3.41.80ddb81', - 'v1.3.40.e87ecc2', - 'v1.3.39.ddc3727', - 'v1.3.38.1253abd', - 'v1.3.37.97712a9', - 'v1.3.36.dd720f2', - 'v1.3.36.64f852e', - 'v1.3.36.7e03019', - 'v1.3.35.3251cd5', - 'v1.3.34.401b5d9', - 'v1.3.33.ab0095c', - 'v1.3.32.7e6c22f', - 'v1.3.31.0084643', - 'v1.3.30.9fe2ddb', - 'v1.3.29.7afc149', - 'v1.3.28.41f9541', - 'v1.3.27.c88ba58', - 'v1.3.26.0010231', - 'v1.3.25.85f46d3', - 'v1.3.24.dff6915', - 'v1.3.23.5462d84', - 'v1.3.22.c725a6b', - 'v1.3.21.cf00ac5', - 'v1.3.20.9a5ff93', - 'v1.3.19.3c6a2f7', - 'v1.3.17.c9822de', - 'v1.3.16.97899ae', - 'v1.3.15.432d067', - 'v1.3.13.71a43a9', - 'v1.3.12.6306c53', - 'v1.3.11.0411401', - 'v1.3.10.cc2a84a', - 'v1.3.10.4df0e91', - 'v1.3.9.92185e7', - 'v1.3.8.90df7c2', - 'v1.3.7.bb22b6e', - 'v1.3.6.f511bab', - 'v1.3.5.e5b19fd', - 'v1.3.4.2b20bf3', - 'v1.3.3.2fe124e', - 'v1.2.testing1', - 'v1.2.65.0adc5ce', - 'v1.2.64.fc48fcd', - 'v1.2.63.9879494', - 'v1.2.62.3ddd74e', - 'v1.2.61.d551c17', - 'v1.2.60.ab959de', - 'v1.2.59.d81c1c0', - 'v1.2.58.6af1822', - 'v1.2.57.f7c6955', - 'v1.2.56.596a73c', - 'v1.2.55.9db7c62', - 'v1.2.54.288f2be', - 'v1.2.53.19c1f9f', - 'v1.2.52.b63802c', - 'v1.2.51.f9ff06b', - 'v1.2.50.41dcfdd', - 'v1.2.49.5354c49', - 'v1.2.48.371335e', - 'v1.2.47', - 'v1.2.46.dce2fe4', - 'v1.2.46.9d21e58', - 'v1.2.45.b674054', - 'v1.2.44.f2c9c55', - 'v1.2.43.a405d81', - 'v1.2.42.2759c8d', - 'v1.2.41.32f3682', - 'v1.2.39.06892c4', - 'v1.2.38.cf4e508', - 'v1.2.38.451b085', - 'v1.2.36', - 'v1.2.30.80e4bc6', - 'v1.2.29.6c95659', + "v2.7.16.a597230", + "v2.7.15.567b8ea", + "v2.7.14.e959000", + "v2.7.13.597fa0b", + "v2.7.12.45f15b8", + "v2.7.11.ee68575", + "v2.7.10.94d4bdf", + "v2.7.9.70724be", + "v2.7.8.a0c0388", + "v2.7.7.5ae4ff9", + "v2.7.6.834c3c5", + "v2.7.5.ddd1499", + "v2.7.4.c1f4f79", + "v2.7.3.cf574c7", + "v2.7.2.f6d3782", + "v2.7.1.f35ca81", + "v2.7.0.705515a", + "v2.7.0.195b7cc", + "v2.6.13.0561f2c", + "v2.6.12.9861e82", + "v2.6.11.60ec05e", + "v2.6.10.9ce4455", + "v2.6.9.f223b8a", + "v2.6.8.ef9d0d7", + "v2.6.7.2d6181f", + "v2.6.6.54c1423", + "v2.6.5.fc3d9f2", + "v2.6.4.b89355f", + "v2.6.3.d28af68", + "v2.6.3.640e731", + "v2.6.2.31c0e8f", + "v2.6.1.7c3edde", + "v2.6.0.f7afa9a", + "v2.5.23.bf958ed", + "v2.5.22.d1fa27d", + "v2.5.21.447533a", + "v2.5.20.4c97351", + "v2.5.19.f9876cf", + "v2.5.19.d5cd6f8", + "v2.5.18.89ebafc", + "v2.5.17.b4b2fd6", + "v2.5.16.f81d3b0", + "v2.5.15.79da236", + "v2.5.14.f2ee0df", + "v2.5.13.295278b", + "v2.5.13.1a06f88", + "v2.5.12.aa184e6", + "v2.5.11.8e2a3e5", + "v2.5.10.0fc5c9b", + "v2.5.9.936260f", + "v2.5.8.6485f03", + "v2.5.7.f77c87d", + "v2.5.6.d55c08d", + "v2.5.5.e182ae7", + "v2.5.4.8d288d5", + "v2.5.3.a70d5ee", + "v2.5.2.771cb52", + "v2.5.1.c13b44b", + "v2.5.0.e470619", + "v2.5.0.d6dac17", + "v2.5.0.ab7de7f", + "v2.5.0.33eb073", + "v2.5.0.9e55e6b", + "v2.5.0.9ac0e26", + "v2.4.3.efc27f2", + "v2.4.3.91d6612", + "v2.4.2.5b45303", + "v2.4.1.394e0e1", + "v2.4.0.46d7b82", + "v2.3.15.deb7c27", + "v2.3.14.64531fa", + "v2.3.13.83f5ba0", + "v2.3.12.24458a7", + "v2.3.11.2740a56", + "v2.3.10.d19607b", + "v2.3.9.f06c56a", + "v2.3.8.d490a33", + "v2.3.7.30fbcab", + "v2.3.6.7a3570a", + "v2.3.5.2f9b68e", + "v2.3.4.ea61808", + "v2.3.3.8187fa7", + "v2.3.2.63df972", + "v2.3.1.4fa7f5a", + "v2.3.0.5f47ca1", + "v2.2.24.e6a2c06", + "v2.2.23.5672e68", + "v2.2.22.404d0dd", + "v2.2.21.7f7c5cb", + "v2.2.20.af5ac32", + "v2.2.19.8f6a283", + "v2.2.18.e9bde80", + "v2.2.17.dbac2b1", + "v2.2.16.1c6acfd", + "v2.2.15.31c4693", + "v2.2.14.57542ce", + "v2.2.13.f570204", + "v2.2.12.092e6f2", + "v2.2.11.10265aa", + "v2.2.10.7cebd79", + "v2.2.9.47301a5", + "v2.2.8.61f6fb2", + "v2.2.7.e8970ad", + "v2.2.6.b53cb38", + "v2.2.5.8255128", + "v2.2.4.3bcab0e", + "v2.2.3.282cc0b", + "v2.2.2.f35c7be", + "v2.2.1.fb5f2e4", + "v2.2.0.9f6584b", + "v2.1.23.04bbdc6", + "v2.1.22.191a69d", + "v2.1.21.97d7a89", + "v2.1.20.470363d", + "v2.1.19.eb7025f", + "v2.1.18.de53280", + "v2.1.17.7ca2e81", + "v2.1.16.a2c5b92", + "v2.1.15.cd78723", + "v2.1.14.99a31c1", + "v2.1.13.7475c86", + "v2.1.12.7711b03", + "v2.1.11.5ec624d", + "v2.1.10.7ef12c7", + "v2.1.9.d43ddc9", + "v2.1.8.ee971e3", + "v2.1.7.242f880", + "v2.1.6.5679a82", + "v2.1.5.23272da", + "v2.1.4.958d2cf", + "v2.1.3.8c68d88", + "v2.1.2.6d20215", + "v2.1.1.dc2ca9c", + "v2.1.0.331a1af", + "v2.0.23.7bb281d", + "v2.0.22.fbfd0f1", + "v2.0.21.83e6cea", + "v2.0.20.7100416", + "v2.0.19.3209aea", + "v2.0.18.1a7991c", + "v2.0.17.5d1c06b", + "v2.0.16.2242b68", + "v2.0.15.aafbde0", + "v2.0.14.2baaad8", + "v2.0.13.7e27729", + "v2.0.12.2400dd4", + "v2.0.11.8914d1a", + "v2.0.10.e09b12c", + "v2.0.9.6ea0963", + "v2.0.8.090e166", + "v2.0.7.91ff7b9", + "v2.0.6.97fd5cf", + "v2.0.5.65e8209", + "v2.0.4.5417671", + "v2.0.3.09fe616", + "v2.0.2.8146e84", + "v2.0.1.ad05b91", + "v2.0.0.18ab874", + "v1.3.48.82bcd39", + "v1.3.47.05147c0", + "v1.3.46.d4ea956", + "v1.3.45.b0d0552", + "v1.3.44.4fa8d02", + "v1.3.43.aae9d2f", + "v1.3.42.9bd9252", + "v1.3.41.80ddb81", + "v1.3.40.e87ecc2", + "v1.3.39.ddc3727", + "v1.3.38.1253abd", + "v1.3.37.97712a9", + "v1.3.36.dd720f2", + "v1.3.36.64f852e", + "v1.3.36.7e03019", + "v1.3.35.3251cd5", + "v1.3.34.401b5d9", + "v1.3.33.ab0095c", + "v1.3.32.7e6c22f", + "v1.3.31.0084643", + "v1.3.30.9fe2ddb", + "v1.3.29.7afc149", + "v1.3.28.41f9541", + "v1.3.27.c88ba58", + "v1.3.26.0010231", + "v1.3.25.85f46d3", + "v1.3.24.dff6915", + "v1.3.23.5462d84", + "v1.3.22.c725a6b", + "v1.3.21.cf00ac5", + "v1.3.20.9a5ff93", + "v1.3.19.3c6a2f7", + "v1.3.17.c9822de", + "v1.3.16.97899ae", + "v1.3.15.432d067", + "v1.3.13.71a43a9", + "v1.3.12.6306c53", + "v1.3.11.0411401", + "v1.3.10.cc2a84a", + "v1.3.10.4df0e91", + "v1.3.9.92185e7", + "v1.3.8.90df7c2", + "v1.3.7.bb22b6e", + "v1.3.6.f511bab", + "v1.3.5.e5b19fd", + "v1.3.4.2b20bf3", + "v1.3.3.2fe124e", + "v1.2.testing1", + "v1.2.65.0adc5ce", + "v1.2.64.fc48fcd", + "v1.2.63.9879494", + "v1.2.62.3ddd74e", + "v1.2.61.d551c17", + "v1.2.60.ab959de", + "v1.2.59.d81c1c0", + "v1.2.58.6af1822", + "v1.2.57.f7c6955", + "v1.2.56.596a73c", + "v1.2.55.9db7c62", + "v1.2.54.288f2be", + "v1.2.53.19c1f9f", + "v1.2.52.b63802c", + "v1.2.51.f9ff06b", + "v1.2.50.41dcfdd", + "v1.2.49.5354c49", + "v1.2.48.371335e", + "v1.2.47", + "v1.2.46.dce2fe4", + "v1.2.46.9d21e58", + "v1.2.45.b674054", + "v1.2.44.f2c9c55", + "v1.2.43.a405d81", + "v1.2.42.2759c8d", + "v1.2.41.32f3682", + "v1.2.39.06892c4", + "v1.2.38.cf4e508", + "v1.2.38.451b085", + "v1.2.36", + "v1.2.30.80e4bc6", + "v1.2.29.6c95659", ] as const export type FirmwareVersion = (typeof VERSIONS)[number] diff --git a/convex/README.md b/convex/README.md index 7fda0c3..aec561e 100644 --- a/convex/README.md +++ b/convex/README.md @@ -7,8 +7,8 @@ A query function that takes two arguments looks like: ```ts // convex/myFunctions.ts -import { query } from "./_generated/server"; -import { v } from "convex/values"; +import { query } from "./_generated/server" +import { v } from "convex/values" export const myQueryFunction = query({ // Validators for arguments. @@ -21,16 +21,16 @@ export const myQueryFunction = query({ handler: async (ctx, args) => { // Read the database as many times as you need here. // See https://docs.convex.dev/database/reading-data. - const documents = await ctx.db.query("tablename").collect(); + const documents = await ctx.db.query("tablename").collect() // Arguments passed from the client are properties of the args object. - console.log(args.first, args.second); + console.log(args.first, args.second) // Write arbitrary JavaScript here: filter, aggregate, build derived data, // remove non-public properties, or create new objects. - return documents; + return documents }, -}); +}) ``` Using this query function in a React component looks like: @@ -39,15 +39,15 @@ Using this query function in a React component looks like: const data = useQuery(api.myFunctions.myQueryFunction, { first: 10, second: "hello", -}); +}) ``` A mutation function looks like: ```ts // convex/myFunctions.ts -import { mutation } from "./_generated/server"; -import { v } from "convex/values"; +import { mutation } from "./_generated/server" +import { v } from "convex/values" export const myMutationFunction = mutation({ // Validators for arguments. @@ -61,27 +61,25 @@ export const myMutationFunction = mutation({ // Insert or modify documents in the database here. // Mutations can also read from the database like queries. // See https://docs.convex.dev/database/writing-data. - const message = { body: args.first, author: args.second }; - const id = await ctx.db.insert("messages", message); + const message = { body: args.first, author: args.second } + const id = await ctx.db.insert("messages", message) // Optionally, return a value from your mutation. - return await ctx.db.get(id); + return await ctx.db.get(id) }, -}); +}) ``` Using this mutation function in a React component looks like: ```ts -const mutation = useMutation(api.myFunctions.myMutationFunction); +const mutation = useMutation(api.myFunctions.myMutationFunction) function handleButtonPress() { // fire and forget, the most common way to use mutations - mutation({ first: "Hello!", second: "me" }); + mutation({ first: "Hello!", second: "me" }) // OR // use the result once the mutation has completed - mutation({ first: "Hello!", second: "me" }).then((result) => - console.log(result), - ); + mutation({ first: "Hello!", second: "me" }).then(result => console.log(result)) } ``` diff --git a/convex/actions.ts b/convex/actions.ts index a3e4b16..34d2623 100644 --- a/convex/actions.ts +++ b/convex/actions.ts @@ -1,10 +1,10 @@ -import { v } from 'convex/values' -import { internal } from './_generated/api' -import { action } from './_generated/server' +import { v } from "convex/values" +import { internal } from "./_generated/api" +import { action } from "./_generated/server" export const dispatchGithubBuild = action({ args: { - buildId: v.id('builds'), + buildId: v.id("builds"), target: v.string(), flags: v.string(), version: v.string(), @@ -14,52 +14,49 @@ export const dispatchGithubBuild = action({ handler: async (ctx, args) => { const githubToken = process.env.GITHUB_TOKEN if (!githubToken) { - throw new Error('GITHUB_TOKEN is not set') + throw new Error("GITHUB_TOKEN is not set") } const convexUrl = process.env.CONVEX_SITE_URL if (!convexUrl) { - console.error('CONVEX_SITE_URL is not set') + console.error("CONVEX_SITE_URL is not set") // Proceeding anyway might fail if workflow requires it } - console.log('dispatchGithubBuild args:', JSON.stringify(args, null, 2)) + console.log("dispatchGithubBuild args:", JSON.stringify(args, null, 2)) if (!args.buildHash) { - throw new Error('args.buildHash is missing or empty') + throw new Error("args.buildHash is missing or empty") } // Use test workflow when running in Convex dev mode - const isDev = process.env.CONVEX_ENV === 'dev' - const workflowFile = isDev ? 'custom_build_test.yml' : 'custom_build.yml' + const isDev = process.env.CONVEX_ENV === "dev" + const workflowFile = isDev ? "custom_build_test.yml" : "custom_build.yml" const payload = { - ref: 'main', // or make this configurable + ref: "main", // or make this configurable inputs: { target: args.target, flags: args.flags, version: args.version, build_id: args.buildId, build_hash: args.buildHash, - convex_url: convexUrl || 'https://example.com', // Fallback to avoid missing input error if that's the cause - plugins: (args.plugins ?? []).join(' '), + convex_url: convexUrl || "https://example.com", // Fallback to avoid missing input error if that's the cause + plugins: (args.plugins ?? []).join(" "), }, } - console.log( - `Dispatching GitHub build to ${workflowFile} with payload:`, - JSON.stringify(payload, null, 2) - ) + console.log(`Dispatching GitHub build to ${workflowFile} with payload:`, JSON.stringify(payload, null, 2)) try { const url = `https://api.github.com/repos/MeshEnvy/mesh-forge/actions/workflows/${workflowFile}/dispatches` - console.log('GitHub API URL:', url) + console.log("GitHub API URL:", url) const response = await fetch(url, { - method: 'POST', + method: "POST", headers: { Authorization: `Bearer ${githubToken}`, - Accept: 'application/vnd.github.v3+json', - 'Content-Type': 'application/json', + Accept: "application/vnd.github.v3+json", + "Content-Type": "application/json", }, body: JSON.stringify(payload), }) diff --git a/convex/admin.ts b/convex/admin.ts index 005f6ea..44d29ef 100644 --- a/convex/admin.ts +++ b/convex/admin.ts @@ -1,19 +1,19 @@ -import { getAuthUserId } from '@convex-dev/auth/server' -import { v } from 'convex/values' -import { api } from './_generated/api' -import { query } from './_generated/server' -import { computeFlagsFromConfig } from './builds' -import { adminMutation, adminQuery } from './helpers' +import { getAuthUserId } from "@convex-dev/auth/server" +import { v } from "convex/values" +import { api } from "./_generated/api" +import { query } from "./_generated/server" +import { computeFlagsFromConfig } from "./builds" +import { adminMutation, adminQuery } from "./helpers" export const isAdmin = query({ args: {}, - handler: async (ctx) => { + handler: async ctx => { const userId = await getAuthUserId(ctx) if (!userId) return false const userSettings = await ctx.db - .query('userSettings') - .withIndex('by_user', (q) => q.eq('userId', userId)) + .query("userSettings") + .withIndex("by_user", q => q.eq("userId", userId)) .first() return userSettings?.isAdmin === true @@ -22,11 +22,11 @@ export const isAdmin = query({ export const listFailedBuilds = adminQuery({ args: {}, - handler: async (ctx) => { + handler: async ctx => { const failedBuilds = await ctx.db - .query('builds') - .filter((q) => q.eq(q.field('status'), 'failure')) - .order('desc') + .query("builds") + .filter(q => q.eq(q.field("status"), "failure")) + .order("desc") .collect() return failedBuilds @@ -35,8 +35,8 @@ export const listFailedBuilds = adminQuery({ export const listAllBuilds = adminQuery({ args: {}, - handler: async (ctx) => { - const allBuilds = await ctx.db.query('builds').order('desc').collect() + handler: async ctx => { + const allBuilds = await ctx.db.query("builds").order("desc").collect() return allBuilds }, @@ -44,12 +44,12 @@ export const listAllBuilds = adminQuery({ export const retryBuild = adminMutation({ args: { - buildId: v.id('builds'), + buildId: v.id("builds"), }, handler: async (ctx, args) => { const build = await ctx.db.get(args.buildId) if (!build) { - throw new Error('Build not found') + throw new Error("Build not found") } // Compute flags from config @@ -68,7 +68,7 @@ export const retryBuild = adminMutation({ // Update build status to queued and clear artifact paths await ctx.db.patch(args.buildId, { - status: 'queued', + status: "queued", updatedAt: Date.now(), firmwarePath: undefined, sourcePath: undefined, diff --git a/convex/auth.config.ts b/convex/auth.config.ts index afc6264..1f25aaf 100644 --- a/convex/auth.config.ts +++ b/convex/auth.config.ts @@ -2,7 +2,7 @@ export default { providers: [ { domain: process.env.CONVEX_SITE_URL, - applicationID: 'convex', + applicationID: "convex", }, ], } diff --git a/convex/auth.ts b/convex/auth.ts index 528be55..f9189c5 100644 --- a/convex/auth.ts +++ b/convex/auth.ts @@ -1,5 +1,5 @@ -import Google from '@auth/core/providers/google' -import { convexAuth } from '@convex-dev/auth/server' +import Google from "@auth/core/providers/google" +import { convexAuth } from "@convex-dev/auth/server" export const { auth, signIn, signOut, store } = convexAuth({ providers: [Google], diff --git a/convex/builds.ts b/convex/builds.ts index 291ca5e..af93bce 100644 --- a/convex/builds.ts +++ b/convex/builds.ts @@ -1,14 +1,14 @@ -import { v } from 'convex/values' -import { pick } from 'convex-helpers' -import { api, internal } from './_generated/api' -import type { Doc, Id } from './_generated/dataModel' -import { internalMutation, mutation, query } from './_generated/server' -import { generateSignedDownloadUrl } from './lib/r2' -import { buildFields } from './schema' +import { pick } from "convex-helpers" +import { v } from "convex/values" +import { api, internal } from "./_generated/api" +import type { Doc, Id } from "./_generated/dataModel" +import { internalMutation, mutation, query } from "./_generated/server" +import { generateSignedDownloadUrl } from "./lib/r2" +import { buildFields } from "./schema" export enum ArtifactType { - Firmware = 'firmware', - Source = 'source', + Firmware = "firmware", + Source = "source", } type BuildUpdateData = { @@ -17,7 +17,7 @@ type BuildUpdateData = { } export const get = query({ - args: { id: v.id('builds') }, + args: { id: v.id("builds") }, handler: async (ctx, args) => { return await ctx.db.get(args.id) }, @@ -27,8 +27,8 @@ export const getByHash = query({ args: { buildHash: v.string() }, handler: async (ctx, args) => { const build = await ctx.db - .query('builds') - .filter((q) => q.eq(q.field('buildHash'), args.buildHash)) + .query("builds") + .filter(q => q.eq(q.field("buildHash"), args.buildHash)) .unique() return build ?? null }, @@ -38,15 +38,13 @@ export const getByHash = query({ * Computes flags string from build config. * Only excludes modules explicitly marked as excluded (config[id] === true). */ -export function computeFlagsFromConfig( - config: Doc<'builds'>['config'] -): string { +export function computeFlagsFromConfig(config: Doc<"builds">["config"]): string { // Sort modules to ensure consistent order return Object.keys(config.modulesExcluded) .sort() - .filter((module) => config.modulesExcluded[module]) + .filter(module => config.modulesExcluded[module]) .map((moduleExcludedName: string) => `-D${moduleExcludedName}=1`) - .join(' ') + .join(" ") } /** @@ -54,7 +52,7 @@ export function computeFlagsFromConfig( * Uses characters: 0-9, a-z, A-Z (62 characters total) */ function base62Encode(bytes: Uint8Array): string { - const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // Convert bytes to a big-endian number let num = BigInt(0) @@ -63,7 +61,7 @@ function base62Encode(bytes: Uint8Array): string { } // Convert number to base62 - if (num === BigInt(0)) return '0' + if (num === BigInt(0)) return "0" const result: string[] = [] while (num > BigInt(0)) { @@ -71,7 +69,7 @@ function base62Encode(bytes: Uint8Array): string { num = num / BigInt(62) } - return result.reverse().join('') + return result.reverse().join("") } /** @@ -97,7 +95,7 @@ async function computeBuildHashInternal( // Use Web Crypto API for SHA-256 hashing const encoder = new TextEncoder() const data = encoder.encode(input) - const hashBuffer = await crypto.subtle.digest('SHA-256', data) + const hashBuffer = await crypto.subtle.digest("SHA-256", data) const hashBytes = new Uint8Array(hashBuffer) // Encode to base62 instead of hex @@ -108,17 +106,10 @@ async function computeBuildHashInternal( * Computes buildHash from build config. * This is the single source of truth for build hash computation. */ -export async function computeBuildHash( - config: Doc<'builds'>['config'] -): Promise<{ hash: string; flags: string }> { +export async function computeBuildHash(config: Doc<"builds">["config"]): Promise<{ hash: string; flags: string }> { const flags = computeFlagsFromConfig(config) const plugins = config.pluginsEnabled ?? [] - const hash = await computeBuildHashInternal( - config.version, - config.target, - flags, - plugins - ) + const hash = await computeBuildHashInternal(config.version, config.target, flags, plugins) return { hash, flags } } @@ -127,18 +118,17 @@ export async function computeBuildHash( * Uses {artifactType}--.tar.gz format. */ export function getR2ArtifactUrl( - build: Pick, 'buildHash' | 'githubRunId'>, + build: Pick, "buildHash" | "githubRunId">, artifactType: ArtifactType ): string { const r2PublicUrl = process.env.R2_PUBLIC_URL if (!r2PublicUrl) { - throw new Error('R2_PUBLIC_URL is not set') + throw new Error("R2_PUBLIC_URL is not set") } if (!build.githubRunId) { - throw new Error('githubRunId is required to construct artifact URL') + throw new Error("githubRunId is required to construct artifact URL") } - const artifactTypeStr = - artifactType === ArtifactType.Source ? 'source' : 'firmware' + const artifactTypeStr = artifactType === ArtifactType.Source ? "source" : "firmware" const path = `/${artifactTypeStr}-${build.buildHash}-${build.githubRunId}.tar.gz` return `${r2PublicUrl}${path}` } @@ -147,7 +137,7 @@ export function getR2ArtifactUrl( // This is the single source of truth for build creation export const upsertBuild = internalMutation({ args: { - ...pick(buildFields, ['buildHash', 'config']), + ...pick(buildFields, ["buildHash", "config"]), status: v.optional(v.string()), flags: v.string(), }, @@ -155,8 +145,8 @@ export const upsertBuild = internalMutation({ handler: async (ctx, args) => { // Check if build already exists with this hash const existingBuild = await ctx.db - .query('builds') - .filter((q) => q.eq(q.field('buildHash'), args.buildHash)) + .query("builds") + .filter(q => q.eq(q.field("buildHash"), args.buildHash)) .unique() const { status, buildHash, config, flags } = args @@ -170,8 +160,8 @@ export const upsertBuild = internalMutation({ } // Create new build (artifact paths are omitted, will be undefined) - const buildId = await ctx.db.insert('builds', { - status: 'queued', + const buildId = await ctx.db.insert("builds", { + status: "queued", startedAt: Date.now(), buildHash, updatedAt: Date.now(), @@ -203,7 +193,7 @@ export const ensureBuildFromConfig = mutation({ }, handler: async (ctx, args) => { // Construct config for the build - const config: Doc<'builds'>['config'] = { + const config: Doc<"builds">["config"] = { version: args.version, modulesExcluded: args.modulesExcluded ?? {}, target: args.target, @@ -214,8 +204,8 @@ export const ensureBuildFromConfig = mutation({ const { hash: buildHash, flags } = await computeBuildHash(config) const existingBuild = await ctx.db - .query('builds') - .filter((q) => q.eq(q.field('buildHash'), buildHash)) + .query("builds") + .filter(q => q.eq(q.field("buildHash"), buildHash)) .unique() if (existingBuild) { @@ -226,14 +216,11 @@ export const ensureBuildFromConfig = mutation({ } } - const buildId: Id<'builds'> = await ctx.runMutation( - internal.builds.upsertBuild, - { - buildHash, - flags, - config, - } - ) + const buildId: Id<"builds"> = await ctx.runMutation(internal.builds.upsertBuild, { + buildHash, + flags, + config, + }) return { buildId, @@ -245,7 +232,7 @@ export const ensureBuildFromConfig = mutation({ // Internal query to get build without auth checks (for webhooks) export const getInternal = internalMutation({ - args: { buildId: v.id('builds') }, + args: { buildId: v.id("builds") }, handler: async (ctx, args) => { return await ctx.db.get(args.buildId) }, @@ -254,12 +241,12 @@ export const getInternal = internalMutation({ // Internal mutation to log errors from actions export const logBuildError = internalMutation({ args: { - buildId: v.id('builds'), + buildId: v.id("builds"), error: v.string(), }, handler: async (ctx, args) => { await ctx.db.patch(args.buildId, { - status: 'failure', + status: "failure", completedAt: Date.now(), }) }, @@ -268,14 +255,8 @@ export const logBuildError = internalMutation({ // Internal mutation to update build status export const updateBuildStatus = internalMutation({ args: { - ...pick(buildFields, [ - 'status', - 'completedAt', - 'githubRunId', - 'firmwarePath', - 'sourcePath', - ]), - buildId: v.id('builds'), + ...pick(buildFields, ["status", "completedAt", "githubRunId", "firmwarePath", "sourcePath"]), + buildId: v.id("builds"), }, handler: async (ctx, args) => { const build = await ctx.db.get(args.buildId) @@ -291,12 +272,12 @@ export const updateBuildStatus = internalMutation({ } // Only set completedAt for final statuses - if (args.status === 'success' || args.status === 'failure') { + if (args.status === "success" || args.status === "failure") { updateData.completedAt = Date.now() } // Clear artifact paths when build starts (queued status) - if (args.status === 'queued') { + if (args.status === "queued") { updateData.firmwarePath = undefined updateData.sourcePath = undefined } @@ -327,9 +308,7 @@ export const updateBuildStatus = internalMutation({ updateData.githubRunId = args.githubRunId } - updateData.githubRunIdHistory = [...new Set(existingHistory)].filter( - (id) => id !== args.githubRunId - ) + updateData.githubRunIdHistory = [...new Set(existingHistory)].filter(id => id !== args.githubRunId) await ctx.db.patch(args.buildId, updateData) }, @@ -337,34 +316,28 @@ export const updateBuildStatus = internalMutation({ export const generateDownloadUrl = mutation({ args: { - buildId: v.id('builds'), - artifactType: v.union(v.literal('firmware'), v.literal('source')), - profileId: v.optional(v.id('profiles')), + buildId: v.id("builds"), + artifactType: v.union(v.literal("firmware"), v.literal("source")), + profileId: v.optional(v.id("profiles")), }, handler: async (ctx, args) => { const build = await ctx.db.get(args.buildId) - if (!build) throw new Error('Build not found') + if (!build) throw new Error("Build not found") if (!build.githubRunId) { - throw new Error('Build githubRunId is required for download') + throw new Error("Build githubRunId is required for download") } - const artifactTypeEnum = - args.artifactType === 'source' - ? ArtifactType.Source - : ArtifactType.Firmware + const artifactTypeEnum = args.artifactType === "source" ? ArtifactType.Source : ArtifactType.Firmware const isSource = artifactTypeEnum === ArtifactType.Source - const artifactTypeStr = - artifactTypeEnum === ArtifactType.Source ? 'source' : 'firmware' - const contentType = isSource - ? 'application/gzip' - : 'application/octet-stream' + const artifactTypeStr = artifactTypeEnum === ArtifactType.Source ? "source" : "firmware" + const contentType = isSource ? "application/gzip" : "application/octet-stream" // Use stored path if available, otherwise construct from buildHash and githubRunId const storedPath = isSource ? build.sourcePath : build.firmwarePath const objectKey = storedPath - ? storedPath.startsWith('/') + ? storedPath.startsWith("/") ? storedPath.slice(1) : storedPath : `${artifactTypeStr}-${build.buildHash}-${build.githubRunId}.tar.gz` @@ -373,7 +346,7 @@ export const generateDownloadUrl = mutation({ const profile = await (async () => { if (!args.profileId) return const profileDoc = await ctx.db.get(args.profileId) - if (!profileDoc) throw new Error('Profile not found') + if (!profileDoc) throw new Error("Profile not found") return profileDoc })() @@ -381,9 +354,9 @@ export const generateDownloadUrl = mutation({ const profileSlug = profile ? profile.name .toLowerCase() - .replace(/[^a-z0-9]+/g, '-') - .replace(/(^-|-$)+/g, '') - : '' + .replace(/[^a-z0-9]+/g, "-") + .replace(/(^-|-$)+/g, "") + : "" // Increment profile flash count for firmware downloads if (profile && !isSource) { @@ -395,18 +368,14 @@ export const generateDownloadUrl = mutation({ } // Increment plugin flash counts for firmware downloads (independent of profile) - if ( - !isSource && - build.config.pluginsEnabled && - build.config.pluginsEnabled.length > 0 - ) { + if (!isSource && build.config.pluginsEnabled && build.config.pluginsEnabled.length > 0) { await ctx.runMutation(internal.plugins.incrementFlashCount, { slugs: build.config.pluginsEnabled, }) } const last4Hash = build.buildHash.slice(-4) - const os = 'meshtastic' // OS/platform identifier + const os = "meshtastic" // OS/platform identifier const version = build.config.version const target = build.config.target const jobId = build.githubRunId diff --git a/convex/helpers.ts b/convex/helpers.ts index 996528b..9f5fe0a 100644 --- a/convex/helpers.ts +++ b/convex/helpers.ts @@ -1,20 +1,15 @@ -import { getAuthUserId } from '@convex-dev/auth/server' -import { ConvexError, v } from 'convex/values' -import { - customAction, - customCtx, - customMutation, - customQuery, -} from 'convex-helpers/server/customFunctions' -import { api } from './_generated/api' -import { action, mutation, query } from './_generated/server' +import { getAuthUserId } from "@convex-dev/auth/server" +import { customAction, customCtx, customMutation, customQuery } from "convex-helpers/server/customFunctions" +import { ConvexError, v } from "convex/values" +import { api } from "./_generated/api" +import { action, mutation, query } from "./_generated/server" export const authQuery = customQuery( query, - customCtx(async (ctx) => { + customCtx(async ctx => { const identity = await ctx.auth.getUserIdentity() if (identity === null) { - throw new ConvexError('Not authenticated!') + throw new ConvexError("Not authenticated!") } return {} }) @@ -22,10 +17,10 @@ export const authQuery = customQuery( export const authMutation = customMutation( mutation, - customCtx(async (ctx) => { + customCtx(async ctx => { const identity = await ctx.auth.getUserIdentity() if (identity === null) { - throw new ConvexError('Not authenticated!') + throw new ConvexError("Not authenticated!") } return {} }) @@ -33,10 +28,10 @@ export const authMutation = customMutation( export const authAction = customAction( action, - customCtx(async (ctx) => { + customCtx(async ctx => { const identity = await ctx.auth.getUserIdentity() if (identity === null) { - throw new ConvexError('Not authenticated!') + throw new ConvexError("Not authenticated!") } return {} }) @@ -44,19 +39,19 @@ export const authAction = customAction( export const adminQuery = customQuery( query, - customCtx(async (ctx) => { + customCtx(async ctx => { const userId = await getAuthUserId(ctx) if (!userId) { - throw new ConvexError('Not authenticated!') + throw new ConvexError("Not authenticated!") } const userSettings = await ctx.db - .query('userSettings') - .withIndex('by_user', (q) => q.eq('userId', userId)) + .query("userSettings") + .withIndex("by_user", q => q.eq("userId", userId)) .first() if (userSettings?.isAdmin !== true) { - throw new ConvexError('Unauthorized: Admin access required') + throw new ConvexError("Unauthorized: Admin access required") } return {} @@ -65,19 +60,19 @@ export const adminQuery = customQuery( export const adminMutation = customMutation( mutation, - customCtx(async (ctx) => { + customCtx(async ctx => { const userId = await getAuthUserId(ctx) if (!userId) { - throw new ConvexError('Not authenticated!') + throw new ConvexError("Not authenticated!") } const userSettings = await ctx.db - .query('userSettings') - .withIndex('by_user', (q) => q.eq('userId', userId)) + .query("userSettings") + .withIndex("by_user", q => q.eq("userId", userId)) .first() if (userSettings?.isAdmin !== true) { - throw new ConvexError('Unauthorized: Admin access required') + throw new ConvexError("Unauthorized: Admin access required") } return {} @@ -86,10 +81,10 @@ export const adminMutation = customMutation( export const adminAction = customAction( action, - customCtx(async (ctx) => { + customCtx(async ctx => { const userId = await getAuthUserId(ctx) if (!userId) { - throw new ConvexError('Not authenticated!') + throw new ConvexError("Not authenticated!") } // Actions can't access ctx.db directly, so we need to use a query @@ -97,7 +92,7 @@ export const adminAction = customAction( userId, }) if (!isAdmin) { - throw new ConvexError('Unauthorized: Admin access required') + throw new ConvexError("Unauthorized: Admin access required") } return {} @@ -106,11 +101,11 @@ export const adminAction = customAction( // Internal query to check if user is admin (used by action middleware) export const checkIsAdmin = query({ - args: { userId: v.id('users') }, + args: { userId: v.id("users") }, handler: async (ctx, args) => { const userSettings = await ctx.db - .query('userSettings') - .withIndex('by_user', (q) => q.eq('userId', args.userId)) + .query("userSettings") + .withIndex("by_user", q => q.eq("userId", args.userId)) .first() return userSettings?.isAdmin === true diff --git a/convex/http.ts b/convex/http.ts index 88fb4ca..773d490 100644 --- a/convex/http.ts +++ b/convex/http.ts @@ -1,39 +1,39 @@ -import { httpRouter } from 'convex/server' -import { internal } from './_generated/api' -import { httpAction } from './_generated/server' -import { auth } from './auth' +import { httpRouter } from "convex/server" +import { internal } from "./_generated/api" +import { httpAction } from "./_generated/server" +import { auth } from "./auth" const http = httpRouter() auth.addHttpRoutes(http) http.route({ - path: '/github-webhook', - method: 'POST', + path: "/github-webhook", + method: "POST", handler: httpAction(async (ctx, request) => { // Verify bearer token const buildToken = process.env.CONVEX_BUILD_TOKEN if (!buildToken) { - return new Response('CONVEX_BUILD_TOKEN not configured', { status: 500 }) + return new Response("CONVEX_BUILD_TOKEN not configured", { status: 500 }) } - const authHeader = request.headers.get('Authorization') - if (!authHeader || !authHeader.startsWith('Bearer ')) { - return new Response('Missing or invalid Authorization header', { + const authHeader = request.headers.get("Authorization") + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return new Response("Missing or invalid Authorization header", { status: 401, }) } const token = authHeader.substring(7) // Remove 'Bearer ' prefix if (token !== buildToken) { - return new Response('Invalid token', { status: 401 }) + return new Response("Invalid token", { status: 401 }) } const payload = await request.json() // Validate build_id and state are present if (!payload.build_id || !payload.state) { - return new Response('Missing build_id or state', { status: 400 }) + return new Response("Missing build_id or state", { status: 400 }) } // Verify build exists @@ -42,12 +42,10 @@ http.route({ }) if (!build) { - return new Response('Build not found', { status: 404 }) + return new Response("Build not found", { status: 404 }) } - const githubRunId = payload.github_run_id - ? Number(payload.github_run_id) - : undefined + const githubRunId = payload.github_run_id ? Number(payload.github_run_id) : undefined await ctx.runMutation(internal.builds.updateBuildStatus, { buildId: payload.build_id, diff --git a/convex/lib/r2.ts b/convex/lib/r2.ts index 19944ff..8086348 100644 --- a/convex/lib/r2.ts +++ b/convex/lib/r2.ts @@ -1,10 +1,10 @@ -import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3' -import { getSignedUrl } from '@aws-sdk/s3-request-presigner' +import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3" +import { getSignedUrl } from "@aws-sdk/s3-request-presigner" export async function generateSignedDownloadUrl( objectKey: string, filename: string, - contentType: string = 'application/octet-stream' + contentType: string = "application/octet-stream" ): Promise { const accountId = process.env.R2_ACCOUNT_ID const accessKeyId = process.env.R2_ACCESS_KEY_ID @@ -12,11 +12,11 @@ export async function generateSignedDownloadUrl( const bucketName = process.env.R2_BUCKET_NAME if (!accountId || !accessKeyId || !secretAccessKey || !bucketName) { - throw new Error('R2 credentials are not set') + throw new Error("R2 credentials are not set") } const s3 = new S3Client({ - region: 'auto', + region: "auto", endpoint: `https://${accountId}.r2.cloudflarestorage.com`, credentials: { accessKeyId, diff --git a/convex/plugins.ts b/convex/plugins.ts index 73ca346..90575c0 100644 --- a/convex/plugins.ts +++ b/convex/plugins.ts @@ -1,23 +1,21 @@ -import { v } from 'convex/values' -import { internalMutation, query } from './_generated/server' +import { v } from "convex/values" +import { internalMutation, query } from "./_generated/server" export const get = query({ args: { slug: v.string() }, handler: async (ctx, args) => { const plugin = await ctx.db - .query('plugins') - .withIndex('by_slug', (q) => q.eq('slug', args.slug)) + .query("plugins") + .withIndex("by_slug", q => q.eq("slug", args.slug)) .unique() - return plugin - ? { slug: plugin.slug, flashCount: plugin.flashCount } - : { slug: args.slug, flashCount: 0 } + return plugin ? { slug: plugin.slug, flashCount: plugin.flashCount } : { slug: args.slug, flashCount: 0 } }, }) export const getAll = query({ args: {}, - handler: async (ctx) => { - const plugins = await ctx.db.query('plugins').collect() + handler: async ctx => { + const plugins = await ctx.db.query("plugins").collect() const counts: Record = {} for (const plugin of plugins) { counts[plugin.slug] = plugin.flashCount @@ -31,11 +29,11 @@ export const incrementFlashCount = internalMutation({ handler: async (ctx, args) => { for (const pluginSpec of args.slugs) { // Extract slug from "slug@version" format, or use as-is if no @ present - const slug = pluginSpec.split('@')[0] + const slug = pluginSpec.split("@")[0] const existing = await ctx.db - .query('plugins') - .withIndex('by_slug', (q) => q.eq('slug', slug)) + .query("plugins") + .withIndex("by_slug", q => q.eq("slug", slug)) .unique() if (existing) { @@ -44,7 +42,7 @@ export const incrementFlashCount = internalMutation({ updatedAt: Date.now(), }) } else { - await ctx.db.insert('plugins', { + await ctx.db.insert("plugins", { slug, flashCount: 1, updatedAt: Date.now(), diff --git a/convex/profiles.ts b/convex/profiles.ts index b2dada5..109558e 100644 --- a/convex/profiles.ts +++ b/convex/profiles.ts @@ -1,34 +1,34 @@ -import { getAuthUserId } from '@convex-dev/auth/server' -import { v } from 'convex/values' -import { internalMutation, mutation, query } from './_generated/server' -import { buildConfigFields } from './schema' +import { getAuthUserId } from "@convex-dev/auth/server" +import { v } from "convex/values" +import { internalMutation, mutation, query } from "./_generated/server" +import { buildConfigFields } from "./schema" export const list = query({ args: {}, - handler: async (ctx) => { + handler: async ctx => { const userId = await getAuthUserId(ctx) if (!userId) return [] return await ctx.db - .query('profiles') - .filter((q) => q.eq(q.field('userId'), userId)) + .query("profiles") + .filter(q => q.eq(q.field("userId"), userId)) .collect() }, }) export const listPublic = query({ args: {}, - handler: async (ctx) => { + handler: async ctx => { const allProfiles = await ctx.db - .query('profiles') - .filter((q) => q.eq(q.field('isPublic'), true)) + .query("profiles") + .filter(q => q.eq(q.field("isPublic"), true)) .collect() return allProfiles.sort((a, b) => (b.flashCount ?? 0) - (a.flashCount ?? 0)) }, }) export const get = query({ - args: { id: v.id('profiles') }, + args: { id: v.id("profiles") }, handler: async (ctx, args) => { const profile = await ctx.db.get(args.id) if (!profile) return null @@ -46,7 +46,7 @@ export const get = query({ // Internal mutation to get a build by ID export const getBuildById = internalMutation({ - args: { buildId: v.id('builds') }, + args: { buildId: v.id("builds") }, handler: async (ctx, args) => { return await ctx.db.get(args.buildId) }, @@ -54,12 +54,12 @@ export const getBuildById = internalMutation({ export const recordFlash = mutation({ args: { - profileId: v.id('profiles'), + profileId: v.id("profiles"), }, handler: async (ctx, args) => { const profile = await ctx.db.get(args.profileId) if (!profile) { - throw new Error('Profile not found') + throw new Error("Profile not found") } const nextCount = (profile.flashCount ?? 0) + 1 @@ -75,7 +75,7 @@ export const recordFlash = mutation({ export const upsert = mutation({ args: { - id: v.optional(v.id('profiles')), + id: v.optional(v.id("profiles")), name: v.string(), description: v.string(), config: v.object(buildConfigFields), @@ -83,13 +83,13 @@ export const upsert = mutation({ }, handler: async (ctx, args) => { const userId = await getAuthUserId(ctx) - if (!userId) throw new Error('Unauthorized') + if (!userId) throw new Error("Unauthorized") if (args.id) { // Update existing profile const profile = await ctx.db.get(args.id) if (!profile || profile.userId !== userId) { - throw new Error('Unauthorized') + throw new Error("Unauthorized") } await ctx.db.patch(args.id, { @@ -104,7 +104,7 @@ export const upsert = mutation({ return args.id } else { // Create new profile - const profileId = await ctx.db.insert('profiles', { + const profileId = await ctx.db.insert("profiles", { userId, name: args.name, description: args.description, @@ -120,14 +120,14 @@ export const upsert = mutation({ }) export const remove = mutation({ - args: { id: v.id('profiles') }, + args: { id: v.id("profiles") }, handler: async (ctx, args) => { const userId = await getAuthUserId(ctx) - if (!userId) throw new Error('Unauthorized') + if (!userId) throw new Error("Unauthorized") const profile = await ctx.db.get(args.id) if (!profile || profile.userId !== userId) { - throw new Error('Unauthorized') + throw new Error("Unauthorized") } await ctx.db.delete(args.id) diff --git a/convex/schema.ts b/convex/schema.ts index 205e17a..a72c4dd 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -1,6 +1,6 @@ -import { authTables } from '@convex-dev/auth/server' -import { defineSchema, defineTable } from 'convex/server' -import { v } from 'convex/values' +import { authTables } from "@convex-dev/auth/server" +import { defineSchema, defineTable } from "convex/server" +import { v } from "convex/values" export const buildConfigFields = { version: v.string(), @@ -10,7 +10,7 @@ export const buildConfigFields = { } export const profileFields = { - userId: v.id('users'), + userId: v.id("users"), name: v.string(), description: v.string(), config: v.object(buildConfigFields), @@ -43,7 +43,7 @@ export const pluginFields = { } export const userSettingsFields = { - userId: v.id('users'), + userId: v.id("users"), isAdmin: v.boolean(), } @@ -51,8 +51,8 @@ export const schema = defineSchema({ ...authTables, profiles: defineTable(profileFields), builds: defineTable(buildFields), - plugins: defineTable(pluginFields).index('by_slug', ['slug']), - userSettings: defineTable(userSettingsFields).index('by_user', ['userId']), + plugins: defineTable(pluginFields).index("by_slug", ["slug"]), + userSettings: defineTable(userSettingsFields).index("by_user", ["userId"]), }) export const buildsDocValidator = schema.tables.builds.validator diff --git a/lib/utils.ts b/lib/utils.ts index 79f028e..b6f32ad 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,6 +1,6 @@ -import { type ClassValue, clsx } from 'clsx' -import { twMerge } from 'tailwind-merge' -import PARENT_MAP from '../constants/architecture-hierarchy.json' +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" +import PARENT_MAP from "../constants/architecture-hierarchy.json" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) @@ -34,16 +34,16 @@ export function timeAgo(date: number | string | Date): string { export function humanizeStatus(status: string): string { // Handle special statuses - if (status === 'success') return 'Success' - if (status === 'failure') return 'Failure' - if (status === 'queued') return 'Queued' - if (status === 'in_progress') return 'In Progress' + if (status === "success") return "Success" + if (status === "failure") return "Failure" + if (status === "queued") return "Queued" + if (status === "in_progress") return "In Progress" // Convert snake_case/underscore_separated to Title Case return status - .split('_') - .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) - .join(' ') + .split("_") + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(" ") } /** @@ -69,7 +69,7 @@ export function getDependedPlugins( // Process each dependency for (const [depId] of Object.entries(plugin.dependencies)) { // Skip "meshtastic" - it's a firmware version requirement, not a plugin - if (depId === 'meshtastic') continue + if (depId === "meshtastic") continue // Only include dependencies that exist in the registry if (depId in registry) { @@ -101,7 +101,7 @@ export function getImplicitDependencies( ): Set { const allDependencies = getDependedPlugins(explicitlySelectedPlugins, registry) const explicitSet = new Set(explicitlySelectedPlugins) - return new Set(allDependencies.filter((id) => !explicitSet.has(id))) + return new Set(allDependencies.filter(id => !explicitSet.has(id))) } /** @@ -116,14 +116,14 @@ export function isRequiredByOther( // Check if any explicitly selected plugin depends on this plugin for (const selectedId of explicitlySelectedPlugins) { if (selectedId === pluginId) continue // Skip self - + // Get all dependencies (including transitive) of this selected plugin const allDeps = getDependedPlugins([selectedId], registry) if (allDeps.includes(pluginId)) { return true } } - + return false } @@ -134,7 +134,7 @@ export function isRequiredByOther( * Some sources might use "esp32_s3" (with underscores) */ function normalizeArchitecture(arch: string): string { - return arch.replace(/[-_]/g, '') + return arch.replace(/[-_]/g, "") } /** @@ -144,27 +144,27 @@ function normalizeArchitecture(arch: string): string { export function getBaseArchitecture(name: string): string | null { const normalized = normalizeArchitecture(name) const parentMap = PARENT_MAP as Record - + const visited = new Set() let current = normalized - + while (current && !visited.has(current)) { visited.add(current) const parent = parentMap[current] - + // If parent is null, we've reached a base architecture if (parent === null) { return current } - + // If no parent found, return current (might be unknown) if (parent === undefined) { return current } - + current = normalizeArchitecture(parent) } - + // Circular reference or unknown, return the last known return current || normalized } @@ -176,41 +176,41 @@ export function getBaseArchitecture(name: string): string | null { export function getCompatibleArchitectures(arch: string): string[] { const normalized = normalizeArchitecture(arch) const parentMap = PARENT_MAP as Record - + const compatible = [normalized] const visited = new Set() let current = normalized - + // Follow parent chain up to base architecture while (current && !visited.has(current)) { visited.add(current) const parent = parentMap[current] - + if (parent === null) { // Reached base architecture break } - + if (parent === undefined) { // Unknown, stop here break } - + const normalizedParent = normalizeArchitecture(parent) if (!compatible.includes(normalizedParent)) { compatible.push(normalizedParent) } - + current = normalizedParent } - + return compatible } /** * Check if a plugin is compatible with a target * Plugin can specify includes/excludes arrays with targets, variant bases, or architectures - * + * * @param pluginIncludes - Array of architectures/targets the plugin explicitly supports * @param pluginExcludes - Array of architectures/targets the plugin explicitly doesn't support * @param targetName - The target name to check compatibility against @@ -226,40 +226,40 @@ export function isPluginCompatibleWithTarget( } const parentMap = PARENT_MAP as Record - + // Normalize target name first (all keys in parentMap are normalized) const normalizedTarget = normalizeArchitecture(targetName) - + // Get all compatible names for the target (target itself + all parents up to base architecture) const compatibleNames = new Set([normalizedTarget]) const visited = new Set() let current = normalizedTarget - + // Follow parent chain (all keys and values in parentMap are already normalized) while (current && !visited.has(current)) { visited.add(current) const parent = parentMap[current] - + if (parent === null) { // Reached base architecture compatibleNames.add(current) // Add the base architecture itself break } - + if (parent === undefined) { // Unknown, stop here break } - + // Parent is already normalized (from JSON) compatibleNames.add(parent) current = parent } - + // Check excludes first - if target matches any exclude, it's incompatible // compatibleNames are already normalized, normalize excludes for comparison if (pluginExcludes && pluginExcludes.length > 0) { - const isExcluded = pluginExcludes.some((exclude) => { + const isExcluded = pluginExcludes.some(exclude => { const normalizedExclude = normalizeArchitecture(exclude) return compatibleNames.has(normalizedExclude) }) @@ -267,16 +267,16 @@ export function isPluginCompatibleWithTarget( return false } } - + // If includes are specified, target must match at least one include // compatibleNames are already normalized, normalize includes for comparison if (pluginIncludes && pluginIncludes.length > 0) { - return pluginIncludes.some((include) => { + return pluginIncludes.some(include => { const normalizedInclude = normalizeArchitecture(include) return compatibleNames.has(normalizedInclude) }) } - + // If no includes/excludes specified, assume compatible with all (backward compatible) return true } diff --git a/pages/+Head.tsx b/pages/+Head.tsx index 0843be5..cb87311 100644 --- a/pages/+Head.tsx +++ b/pages/+Head.tsx @@ -1,11 +1,11 @@ // https://vike.dev/Head -import logoUrl from "../assets/logo.png"; -import faviconUrl from "../assets/favicon.svg"; -import appleTouchIconUrl from "../assets/apple-touch-icon.png"; -import siteWebmanifestUrl from "../assets/site.webmanifest"; -import favicon96x96Url from "../assets/favicon-96x96.png"; -import faviconIcoUrl from "../assets/favicon.ico"; +import appleTouchIconUrl from "../assets/apple-touch-icon.png" +import favicon96x96Url from "../assets/favicon-96x96.png" +import faviconIcoUrl from "../assets/favicon.ico" +import faviconUrl from "../assets/favicon.svg" +import logoUrl from "../assets/logo.png" +import siteWebmanifestUrl from "../assets/site.webmanifest" export function Head() { return ( @@ -18,5 +18,5 @@ export function Head() { - ); + ) } diff --git a/pages/+Layout.tsx b/pages/+Layout.tsx index c4ecfcf..d89c7e9 100644 --- a/pages/+Layout.tsx +++ b/pages/+Layout.tsx @@ -1,20 +1,18 @@ -import "./Layout.css"; -import "./tailwind.css"; -import logoUrl from "../assets/logo.png"; -import { Link } from "../components/Link"; -import { ConvexReactClient } from "convex/react"; -import { ConvexAuthProvider } from "@convex-dev/auth/react"; -import Navbar from "@/components/Navbar"; -import { usePageContext } from "vike-react/usePageContext"; +import Navbar from "@/components/Navbar" +import { ConvexAuthProvider } from "@convex-dev/auth/react" +import { ConvexReactClient } from "convex/react" +import { usePageContext } from "vike-react/usePageContext" +import "./Layout.css" +import "./tailwind.css" -const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string); +const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string) function ConditionalNavbar() { - const pageContext = usePageContext(); + const pageContext = usePageContext() if (pageContext.urlPathname === "/") { - return null; + return null } - return ; + return } export default function Layout({ children }: { children: React.ReactNode }) { @@ -23,5 +21,5 @@ export default function Layout({ children }: { children: React.ReactNode }) { {children} - ); + ) } diff --git a/pages/+onPageTransitionEnd.ts b/pages/+onPageTransitionEnd.ts index b27ef43..ce5cff6 100644 --- a/pages/+onPageTransitionEnd.ts +++ b/pages/+onPageTransitionEnd.ts @@ -1,4 +1,4 @@ export async function onPageTransitionEnd() { - console.log("Page transition end"); - document.body.classList.remove("page-transition"); + console.log("Page transition end") + document.body.classList.remove("page-transition") } diff --git a/pages/+onPageTransitionStart.ts b/pages/+onPageTransitionStart.ts index a2a4817..393afe6 100644 --- a/pages/+onPageTransitionStart.ts +++ b/pages/+onPageTransitionStart.ts @@ -1,9 +1,9 @@ // https://vike.dev/onPageTransitionStart -import type { PageContextClient } from "vike/types"; +import type { PageContextClient } from "vike/types" export async function onPageTransitionStart(pageContext: Partial) { - console.log("Page transition start"); - console.log("pageContext.isBackwardNavigation", pageContext.isBackwardNavigation); - document.body.classList.add("page-transition"); + console.log("Page transition start") + console.log("pageContext.isBackwardNavigation", pageContext.isBackwardNavigation) + document.body.classList.add("page-transition") } diff --git a/pages/_error/+Page.tsx b/pages/_error/+Page.tsx index cbfa6de..5fe4d91 100644 --- a/pages/_error/+Page.tsx +++ b/pages/_error/+Page.tsx @@ -1,19 +1,19 @@ -import { usePageContext } from "vike-react/usePageContext"; +import { usePageContext } from "vike-react/usePageContext" export default function Page() { - const { is404 } = usePageContext(); + const { is404 } = usePageContext() if (is404) { return ( <>

Page Not Found

This page could not be found.

- ); + ) } return ( <>

Internal Error

Something went wrong.

- ); + ) } diff --git a/pages/admin/+Page.tsx b/pages/admin/+Page.tsx index 6ea535c..4e6af54 100644 --- a/pages/admin/+Page.tsx +++ b/pages/admin/+Page.tsx @@ -1,23 +1,23 @@ -import { useMutation, useQuery } from "convex/react"; -import { useState } from "react"; -import { toast } from "sonner"; -import { navigate } from "vike/client/router"; -import { BuildDownloadButton } from "@/components/BuildDownloadButton"; -import { Button } from "@/components/ui/button"; -import { api } from "../../convex/_generated/api"; -import type { Id } from "../../convex/_generated/dataModel"; -import { ArtifactType } from "../../convex/builds"; +import { BuildDownloadButton } from "@/components/BuildDownloadButton" +import { Button } from "@/components/ui/button" +import { useMutation, useQuery } from "convex/react" +import { useState } from "react" +import { toast } from "sonner" +import { navigate } from "vike/client/router" +import { api } from "../../convex/_generated/api" +import type { Id } from "../../convex/_generated/dataModel" +import { ArtifactType } from "../../convex/builds" -type FilterType = "all" | "failed"; +type FilterType = "all" | "failed" export default function Admin() { - const [filter, setFilter] = useState("failed"); - const isAdmin = useQuery(api.admin.isAdmin); - const failedBuilds = useQuery(api.admin.listFailedBuilds); - const allBuilds = useQuery(api.admin.listAllBuilds); - const retryBuild = useMutation(api.admin.retryBuild); + const [filter, setFilter] = useState("failed") + const isAdmin = useQuery(api.admin.isAdmin) + const failedBuilds = useQuery(api.admin.listFailedBuilds) + const allBuilds = useQuery(api.admin.listAllBuilds) + const retryBuild = useMutation(api.admin.retryBuild) - const builds = filter === "failed" ? failedBuilds : allBuilds; + const builds = filter === "failed" ? failedBuilds : allBuilds // Show loading state if (isAdmin === undefined) { @@ -25,7 +25,7 @@ export default function Admin() {
Loading...
- ); + ) } // Redirect if not admin @@ -38,25 +38,25 @@ export default function Admin() { - ); + ) } const handleRetry = async (buildId: Id<"builds">) => { try { - await retryBuild({ buildId }); + await retryBuild({ buildId }) toast.success("Build retry initiated", { description: "The build has been queued with the latest YAML.", - }); + }) } catch (error) { toast.error("Failed to retry build", { description: String(error), - }); + }) } - }; + } const formatDate = (timestamp: number) => { - return new Date(timestamp).toLocaleString(); - }; + return new Date(timestamp).toLocaleString() + } const getStatusBadge = (status: string) => { const statusConfig = { @@ -71,14 +71,14 @@ export default function Admin() { text: "text-yellow-400", label: "Queued", }, - }; + } const config = statusConfig[status as keyof typeof statusConfig] || { bg: "bg-slate-500/20", text: "text-slate-400", label: status, - }; - return {config.label}; - }; + } + return {config.label} + } return (
@@ -112,7 +112,7 @@ export default function Admin() {
No {filter === "failed" ? "failed " : ""}builds found.
) : (
- {builds.map((build) => ( + {builds.map(build => (
{/* Header Section */}
@@ -191,7 +191,7 @@ export default function Admin() { {build.githubRunId} )} - {build.githubRunIdHistory?.map((id) => ( + {build.githubRunIdHistory?.map(id => (
- ); + ) } diff --git a/pages/builds/new/+Page.tsx b/pages/builds/new/+Page.tsx index 6afeecb..5cc12b7 100644 --- a/pages/builds/new/+Page.tsx +++ b/pages/builds/new/+Page.tsx @@ -1,5 +1,5 @@ -import Builder from "./Builder"; +import Builder from "./Builder" export default function BuildNew() { - return ; + return } diff --git a/pages/builds/new/@buildHash/+Page.tsx b/pages/builds/new/@buildHash/+Page.tsx index 2bbedda..ee47899 100644 --- a/pages/builds/new/@buildHash/+Page.tsx +++ b/pages/builds/new/@buildHash/+Page.tsx @@ -1,5 +1,5 @@ -import Builder from "../Builder"; +import Builder from "../Builder" export default function BuildNew() { - return ; + return } diff --git a/pages/docs/+Layout.tsx b/pages/docs/+Layout.tsx index f07467a..7426565 100644 --- a/pages/docs/+Layout.tsx +++ b/pages/docs/+Layout.tsx @@ -1,5 +1,5 @@ -import { Link } from "../../components/Link"; -import { usePageContext } from "vike-react/usePageContext"; +import { usePageContext } from "vike-react/usePageContext" +import { Link } from "../../components/Link" const navSections = [ { @@ -19,12 +19,12 @@ const navSections = [ { href: "/docs/nRF52", label: "nRF52" }, ], }, -]; +] function NavLink({ href, label }: { href: string; label: string }) { - const pageContext = usePageContext(); - const { urlPathname } = pageContext; - const isActive = href === "/docs" ? urlPathname === href : urlPathname.startsWith(href); + const pageContext = usePageContext() + const { urlPathname } = pageContext + const isActive = href === "/docs" ? urlPathname === href : urlPathname.startsWith(href) return ( @@ -38,7 +38,7 @@ function NavLink({ href, label }: { href: string; label: string }) { {label} - ); + ) } export default function Layout({ children }: { children: React.ReactNode }) { @@ -55,7 +55,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { )}
    - {section.items.map((item) => ( + {section.items.map(item => (
  • @@ -70,5 +70,5 @@ export default function Layout({ children }: { children: React.ReactNode }) {
    {children}
- ); + ) } diff --git a/pages/docs/registry/+Page.mdx b/pages/docs/registry/+Page.mdx index 6ee5f2e..ca5cfb0 100644 --- a/pages/docs/registry/+Page.mdx +++ b/pages/docs/registry/+Page.mdx @@ -54,4 +54,3 @@ If you want to create and contribute plugins to the registry, check out the [Plu - [MeshEnvy](https://meshenvy.org) - Built by MeshEnvy (not affiliated with Meshtastic) - [Meshtastic](https://meshtastic.org) - Learn more about Meshtastic - diff --git a/prettier.config.js b/prettier.config.js index c480660..f2dc09a 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -10,6 +10,6 @@ const config = { tabWidth: 2, useTabs: false, semi: false, -}; +} -export default config; +export default config diff --git a/vendor/web-flasher b/vendor/web-flasher index 9b96dd6..b9668e5 160000 --- a/vendor/web-flasher +++ b/vendor/web-flasher @@ -1 +1 @@ -Subproject commit 9b96dd662d5b0005d449c2d0a51d0410be2a8d34 +Subproject commit b9668e534d7138b727b743a48897d48cca782f86 diff --git a/vite.config.ts b/vite.config.ts index 85b9641..b169a72 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,9 +1,9 @@ -import tailwindcss from "@tailwindcss/vite"; -import react from "@vitejs/plugin-react"; -import vike from "vike/plugin"; -import { defineConfig } from "vite"; -import path from "node:path"; -import mdx from "@mdx-js/rollup"; +import mdx from "@mdx-js/rollup" +import tailwindcss from "@tailwindcss/vite" +import react from "@vitejs/plugin-react" +import path from "node:path" +import vike from "vike/plugin" +import { defineConfig } from "vite" export default defineConfig({ plugins: [vike(), mdx(), react(), tailwindcss()], @@ -12,4 +12,4 @@ export default defineConfig({ "@": path.resolve(__dirname, "."), }, }, -}); +})