Demo videos from PR builds now expire after 90 days and are posted
directly to PR threads. Uses object tagging with S3 lifecycle policies
for automatic cleanup.
## Changes
**Workflow** (`.github/workflows/tests.yml`)
- Replace `hkusu/s3-upload-action@v2` with
`ramonpaolo/action-upload-s3@main` for tagging support
- Tag uploaded objects: `expiration=90days`, `Source=github-actions`,
`Type=pr-demo-video`
- Generate unique filenames: `pr-{number}-{timestamp}.gif`
- Post PR comment with embedded video using `actions/github-script@v7`
**Documentation** (`CI_CD.md`)
- S3 lifecycle policy configuration (filters on `expiration=90days` tag)
- IAM permission requirements: `s3:PutObject`, `s3:PutObjectTagging`
## S3 Lifecycle Setup Required
```json
{
"Rules": [{
"ID": "ExpirePRDemoVideosAfter90Days",
"Status": "Enabled",
"Filter": {"Tag": {"Key": "expiration", "Value": "90days"}},
"Expiration": {"Days": 90}
}]
}
```
Apply with: `aws s3api put-bucket-lifecycle-configuration --bucket
<bucket> --lifecycle-configuration file://policy.json`
## Notes
- gh-pages `video.mp4` unaffected (served from GitHub Pages, not S3)
- Existing S3 objects without tags remain unchanged
> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `https://api.github.com/repos/ramonpaolo/action-upload-s3/tags`
> - Triggering command: `/usr/bin/curl curl -s REDACTED` (http block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/thomasnordquist/MQTT-Explorer/settings/copilot/coding_agent)
(admins only)
>
> </details>
<!-- START COPILOT CODING AGENT SUFFIX -->
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
> when a demo-video is generated from a pr, add an expiration of 90 days
to the S3 file and post the video as image to the pr thread.
</details>
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/thomasnordquist/MQTT-Explorer/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thomasnordquist <7721625+thomasnordquist@users.noreply.github.com>
Consolidates the browser mode Docker build from 3 stages to 2 by
removing the redundant intermediate `deps` stage and cleaning dev
dependencies in-place after build.
## Changes
- **Stage 1 (builder)**: Install all deps → build → remove dev deps with
`yarn install --production`
- **Stage 2 (production)**: Copy built artifacts and production
node_modules from builder (previously split across builder + deps
stages)
**Before:**
```dockerfile
# Stage 1: Build
RUN yarn install --frozen-lockfile
RUN yarn build:server
# Stage 2: Production dependencies
COPY --from=builder /build/package.json /build/yarn.lock ./
RUN yarn install --production --frozen-lockfile
# Stage 3: Production
COPY --from=builder /build/dist ./dist
COPY --from=deps /deps/node_modules ./node_modules
```
**After:**
```dockerfile
# Stage 1: Build and prepare production dependencies
RUN yarn install --frozen-lockfile
RUN yarn build:server
RUN yarn install --production --frozen-lockfile
# Stage 2: Production
COPY --from=builder /build/dist ./dist
COPY --from=builder /build/node_modules ./node_modules
```
No functional changes to final image; eliminates redundant package
resolution and copying.
<!-- START COPILOT CODING AGENT SUFFIX -->
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
> simplify the Dockerfile browser build, install dependencies and build
in the first stage, after the build remove the dev dependency
</details>
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/thomasnordquist/MQTT-Explorer/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thomasnordquist <7721625+thomasnordquist@users.noreply.github.com>
Docker build was failing at `yarn install --frozen-lockfile` because
`app/yarn.lock` and `backend/yarn.lock` weren't available during
dependency installation.
## Changes
- Simplified `Dockerfile.browser` to copy all source files and
dependencies at once before running `yarn install`
- This ensures all necessary files including `app/yarn.lock` and
`backend/yarn.lock` are available for reproducible dependency
installation
```dockerfile
# Before
COPY package.json yarn.lock ./
COPY app/package.json ./app/
COPY backend/package.json ./backend/
# Install ALL dependencies (needed for build)
RUN yarn install --frozen-lockfile --network-timeout 100000
# Copy source files
COPY tsconfig.json ./
COPY src ./src
COPY backend ./backend
COPY events ./events
COPY app ./app
# After
COPY package.json yarn.lock ./
COPY tsconfig.json ./
COPY src ./src
COPY backend ./backend
COPY events ./events
COPY app ./app
# Install ALL dependencies (needed for build)
RUN yarn install --frozen-lockfile --network-timeout 100000
```
This approach trades Docker layer caching optimization for a simpler,
more straightforward Dockerfile structure where all files are copied at
once.
<!-- START COPILOT CODING AGENT SUFFIX -->
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
>
https://github.com/thomasnordquist/MQTT-Explorer/actions/runs/20444356669/job/58744431418
build is failing, ensure all files have been added. Can't cd into app to
install packages. Build here to verify the solution.
</details>
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/thomasnordquist/MQTT-Explorer/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thomasnordquist <7721625+thomasnordquist@users.noreply.github.com>