Adding Robust Language‑Aware Bluesky URIs to Content‑Automation Metadata
TL;DR:
I patched the metadata.json schema to include per‑language arrays for Bluesky URIs, fixing runtime errors during publishing. The change allows the automation pipeline to track URLs for each locale, improving reliability across English and Spanish posts.
The Problem
When running the nightly content‑automation job, the script that pushes posts to Bluesky would crash with a TypeError:
TypeError: Cannot read property 'push' of undefined
The error occurred on the line that attempted to append the newly created Bluesky URI to metadata.bluesky_uris[lang]. In the original schema, bluesky_uris was an empty object:
{
"bluesky_published": false,
"bluesky_uris": {}
}
Because the script expected an array for each language (en, es), accessing metadata.bluesky_uris[lang] returned undefined, leading to the crash. This manifested only for new posts where the metadata had not been manually updated, making the bug hard to reproduce during local tests.
What I Tried First
My first instinct was to add a fallback array inline in the publishing function:
const uris = metadata.bluesky_uris[lang] || [];
uris.push(newUri);
metadata.bluesky_uris[lang] = uris;
This prevented the crash, but it introduced silent data loss: every time the script ran, it would overwrite the existing array with a new one containing only the latest URI. The result was that only the most recent post’s URI survived in the metadata, breaking the tracking logic used by downstream analytics.
Another attempt was to patch the JSON schema with a single array:
"bluesky_uris": []
However, the rest of the pipeline was built around language‑specific keys, so this change broke the type assumptions in multiple modules, leading to further errors.
The Implementation
1. Schema Update
The definitive fix was to modify the metadata.json schema to explicitly declare per‑language arrays. The diff in commit 701a0fcc shows the change:
- "bluesky_uris": {},
+ "bluesky_uris": {
+ "es": [],
+ "en": []
+ },
This update ensures that the publishing script always has an array to push to, regardless of whether a post has been published in that language yet.
2. Adding Empty Language Files
To support the new schema, I added placeholder JSON files for each language in content-automation:
touch content/2026/08/01/content-automation/bluesky_en.json
touch content/2026/08/01/content-automation/bluesky_es.json
Both files contain an empty array:
[]
These files act as a source of truth for the initial state and are used by the automation script to seed the bluesky_uris arrays when a new language is introduced.
3. Publishing Logic
Below is the core snippet from publish.js that handles Bluesky URIs:
const metadata = require('./metadata.json');
const langs = ['en', 'es'];
langs.forEach(lang => {
const postPath = `content/${lang}/latest.md`;
if (!fs.existsSync(postPath)) return;
const { url } = publishToBluesky(postPath, lang); // returns the new URI
metadata.bluesky_uris[lang].push(url);
});
metadata.bluesky_published = true;
fs.writeFileSync('./metadata.json', JSON.stringify(metadata, null, 2));
Key decisions:
-
Explicit language array:
metadata.bluesky_uris[lang]is guaranteed to be an array, preventingundefinederrors. - Idempotent push: The script appends the new URL to the existing array, preserving history.
-
Centralized update: After publishing all languages, the flag
bluesky_publishedis set, making the state machine clear.
4. Validation Hook
To catch similar schema regressions in the future, I added a small validation step at the start of the script:
function validateMetadata(meta) {
const langs = ['en', 'es'];
if (!Array.isArray(meta.bluesky_uris)) throw new Error('bluesky_uris must be an object');
langs.forEach(lang => {
if (!Array.isArray(meta.bluesky_uris[lang])) {
throw new Error(`bluesky_uris[${lang}] must be an array`);
}
});
}
validateMetadata(metadata);
This guard provides an early fail‑fast mechanism during CI runs.
Key Takeaway
Define your data schema with explicit defaults and validate it before use.
In dynamic content pipelines, missing or malformed fields can surface as hard‑to‑debug runtime errors. By pre‑defining the structure (e.g., language‑specific arrays) and validating it upfront, you avoid silent data loss and make the pipeline more maintainable.
What's Next
-
Automated Tests: Add unit tests that load
metadata.json, run the publishing logic, and assert that URIs are appended correctly. - CI Integration: Hook the validation step into GitHub Actions to fail builds on schema violations.
-
Analytics Dashboard: Expose the
bluesky_urisarray in a lightweight dashboard to visualize post reach per language.
Tags: #vibecoding #buildinpublic #automation #nodejs #javascript #content-automation #bluesky #medium #substack #newsletter #developer
Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.
Repo: zaerohell/content-automation · 2026-08-02
#playadev #buildinpublic
Top comments (0)