Skip to content

feat(blob): audit, clean and migrate content assets to Ocobo team store #32

@wab

Description

@wab

Problem statement

The Vercel Blob store (ocobo-blob, ~55 MB) hosting all content assets — client logos, blog/story images, team photos — lives in a personal Vercel account ("wab") while the rest of the Ocobo infrastructure has moved to the team account. The store is invisible from the team dashboard, cannot be managed collectively, and likely contains assets that no longer correspond to any active reference in the codebase or markdown content.

The content asset pipeline (posts/assets/upload-assets.js → blob) is what drives this store. To put it under proper team ownership, we tackle it filesystem-first: clean posts/assets/ first, then push the cleaned set to a new team-owned store.

Solution

Three sequential phases:

  1. Audit posts/assets/ against actual usage — scan filesystem (153 files) and cross-reference with every markdown frontmatter/body in this repo + dynamic slug patterns consumed by the website. Produces an orphans list. Initial dry-run confirms ~25 orphans.
  2. Review and clean the filesystem — delete confirmed orphans from posts/assets/, commit. The filesystem becomes the canonical, clean source of truth before anything touches the new store.
  3. Migrate to new team-owned store — create website-blob under the Ocobo team account, push cleaned posts/assets/ to it, then rewrite blob URLs in markdown frontmatter (team avatars, story images, blog images) to the new hostname.

The website consumer side (ASSETS_BASE_URL constant in app/config/assets.ts, plus cleanup of obsolete migration scripts and orphan public/images/team/) is handled separately in ocobo-revops/website milestone #3.

User stories

  1. As a developer, I want orphaned content assets removed from posts/assets/ before migration, so the new store only holds files that are actually in use.
  2. As a developer, I want an automated filesystem audit, so I can identify unused assets without manual inspection.
  3. As a developer, I want a dry-run deletion mode, so I can review what will be removed before committing.
  4. As a developer, I want the new blob store under the Ocobo team account, so the whole infra is centralised and auditable by the team.
  5. As a developer, I want asset pathnames preserved during migration, so frontmatter URL rewriting is a hostname-only swap.
  6. As a content manager, I want existing markdown URLs to keep resolving during the cutover, so there is no visual regression on live pages.
  7. As a developer, I want the frontmatter rewrite automated and idempotent, so it can be re-run safely.

Implementation decisions

  • Source of truth = filesystem, not the blob store. The blob is a downstream artifact pushed by upload-assets.js. Cleaning the filesystem first means the next push produces a clean store automatically.
  • Audit script (posts/scripts/audit-assets.js): scans posts/assets/**, cross-references against (a) any URL containing /content/... or /assets/... in any markdown under blog/, stories/, team/, jobs/, tools/, legal/, (b) team slug filename convention (team/{slug}.{jpg,jpeg} matching team/*.md filenames), (c) website dynamic patterns: clients/{slug}-white.png and clients/{slug}-avatar.png for every story slug + every hardcoded slug in ClientCarousel.tsx. Produces audit-orphans.json.
  • Delete script (posts/scripts/delete-orphan-assets.js): reads audit-orphans.json, dry-run by default, --confirm to execute. Operates on filesystem (deletes files + commits via git), not on blob.
  • New store creation: vercel blob create-store website-blob --scope ocobo-22231b32. Retrieve BLOB_READ_WRITE_TOKEN, add to .env.local.
  • Migration: re-purpose scripts/upload-assets.js --all against the new store (one-shot full upload of the cleaned filesystem). Decision deferred to the implementation issue.
  • Frontmatter rewrite: update BLOB_BASE_URL in scripts/update-asset-urls.js, run on all markdown in blog/, stories/, team/, jobs/, tools/.

Testing decisions

  • Audit / migration scripts are Node CLI utilities — no unit tests. Correctness verified by: (a) post-audit manual review of orphans (HITL), (b) post-upload HTTP HEAD check against every new store URL (0 failures required), (c) post-cutover visual smoke test on production.

Out of scope

Notes

  • Old ocobo-blob personal-account store is decommissioned in the website follow-up milestone, after one successful production deploy on the new hostname.
  • Dry-run audit (2026-05) found 25 orphans: 20 unused -color.png/-dark.png client variants, 4 legacy firstname-only team photos, 1 .DS_Store.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions