Skip to content

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Jan 27, 2026

Summary

Adds S3 upload for staging builds and creates a web route to download the latest staging DMG.

Changes:

  1. desktop_cd.yaml: Added S3 upload step for staging channel that uploads the DMG to s3://hyprnote-build/desktop/staging/hyprnote-macos-{arch}.dmg
  2. apps/web: Created /download/staging route that redirects to /api/download/staging-dmg, which proxies the file from R2
  3. apps/web/src/env.ts: Added R2 credentials (CLOUDFLARE_R2_ENDPOINT_URL, CLOUDFLARE_R2_ACCESS_KEY_ID, CLOUDFLARE_R2_SECRET_ACCESS_KEY)
  4. apps/web/package.json: Added @aws-sdk/client-s3 for R2 access

Note: Staging builds only target aarch64 (Apple Silicon) per the existing workflow matrix.

Review & Testing Checklist for Human

  • Configure R2 credentials in Netlify - The web app now requires CLOUDFLARE_R2_ENDPOINT_URL, CLOUDFLARE_R2_ACCESS_KEY_ID, and CLOUDFLARE_R2_SECRET_ACCESS_KEY environment variables. Without these, the API route will fail.
  • Run a staging build to upload a DMG to s3://hyprnote-build/desktop/staging/hyprnote-macos-aarch64.dmg
  • Test the download route by visiting https://hyprnote.com/download/staging and verifying the DMG downloads correctly
  • Verify the S3 path structure (desktop/staging/) is the desired location for staging builds

Recommended test plan:

  1. Add R2 credentials to Netlify environment variables
  2. Trigger a staging build via the desktop_cd workflow
  3. After build completes, visit /download/staging on the deployed site
  4. Confirm the DMG file downloads with correct filename and content

Notes

The API route uses lazy S3 client initialization (inside the handler) to avoid module-level failures if credentials are missing during build.

Link to Devin run: https://app.devin.ai/sessions/200b9df8164246f4bde8d562d6909986
Requested by: @yujonglee

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Jan 27, 2026

Deploy Preview for hyprnote-storybook canceled.

Name Link
🔨 Latest commit cbf1a55
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/6978c4344c6e2a000835be95

@netlify
Copy link

netlify bot commented Jan 27, 2026

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit cbf1a55
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/6978c4340695190008858776
😎 Deploy Preview https://deploy-preview-3420--hyprnote.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Contributor Author

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional flags.

Open in Devin Review

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Copy link
Contributor Author

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View issue and 5 additional flags in Devin Review.

Open in Devin Review

Comment on lines +5 to +7
throw redirect({
href: "/api/download/staging-dmg",
} as any);
Copy link
Contributor

Choose a reason for hiding this comment

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

The redirect uses href property with an as any cast, which bypasses type checking. TanStack Router's redirect function expects a to property, not href. This will likely cause the redirect to fail silently or not work as expected.

Fix: Use the correct property name:

throw redirect({
  to: "/api/download/staging-dmg",
});
Suggested change
throw redirect({
href: "/api/download/staging-dmg",
} as any);
throw redirect({
to: "/api/download/staging-dmg",
});

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

devin-ai-integration bot and others added 2 commits January 27, 2026 13:53
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
…ase pattern)

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Copy link
Contributor Author

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View issue and 5 additional flags in Devin Review.

Open in Devin Review

Comment on lines +49 to +52
} catch (error) {
console.error("Error fetching DMG from R2:", error);
return new Response("Failed to fetch file", { status: 500 });
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🟡 S3 NoSuchKey exception returns 500 instead of 404 for missing file

When the staging DMG file doesn't exist in S3, users receive a 500 error instead of a 404.

Click to expand

Problem

The code at apps/web/src/routes/api/download/staging-dmg.ts:49-51 catches all errors from the S3 SDK and returns a generic 500 status:

catch (error) {
  console.error("Error fetching DMG from R2:", error);
  return new Response("Failed to fetch file", { status: 500 });
}

When S3's GetObjectCommand is called for a non-existent key, the AWS SDK throws a NoSuchKey exception rather than returning a response with no body. This means the if (!response.Body) check at line 30-32 is never reached for missing files.

Actual vs Expected

  • Actual: Missing file returns HTTP 500 "Failed to fetch file"
  • Expected: Missing file should return HTTP 404 "File not found"

Impact

Users visiting /download/staging before a staging build has uploaded a DMG will see a misleading 500 server error instead of a proper 404 not found response.

Recommendation: Check if the error is a NoSuchKey exception and return 404 in that case:

catch (error) {
  if (error instanceof Error && error.name === 'NoSuchKey') {
    return new Response("File not found", { status: 404 });
  }
  console.error("Error fetching DMG from R2:", error);
  return new Response("Failed to fetch file", { status: 500 });
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant