Updating Metadata for Multi‑Language Bluesky Publishing in Content‑Automation
TL;DR:
We added a bluesky_uris array per language in each metadata.json and created empty JSON files for Bluesky posts. This change lets the automation script reliably track published URLs and prevents runtime errors when iterating over URIs.
The Problem
During the latest build, the publish-bluesky.js script threw a TypeError:
TypeError: Cannot read property 'push' of undefined
at publishBluesky (publish-bluesky.js:45:12)
The script expects metadata.bluesky_uris.es and metadata.bluesky_uris.en to be arrays so it can push the new URI after a post is published. In the repository, metadata.json defined bluesky_uris as an empty object:
"bluesky_uris": {}
Iterating over an object or pushing into undefined caused the crash. The automation pipeline halted, preventing subsequent publishing steps for Medium, Substack, and Dev.to.
What I Tried First
Initially, I patched the script to check for object existence and convert it to an array on the fly:
if (!Array.isArray(metadata.bluesky_uris)) {
metadata.bluesky_uris = [];
}
While this avoided the immediate crash, it introduced a subtle bug: subsequent runs would overwrite the array with a new empty array, discarding previously stored URIs. The script would lose the history of published posts, making it impossible to generate a changelog or avoid duplicate posts.
I also considered hard‑coding the array structure in the script, but that would make the code brittle—any future changes to the metadata schema would require editing the script again.
The Implementation
1. Update metadata.json in both repositories
We changed the bluesky_uris field from an empty object to an object containing language keys mapped to empty arrays. The diff for content/2026/07/31/VS/metadata.json:
- "bluesky_uris": {},
+ "bluesky_uris": {
+ "es": [],
+ "en": []
+ },
And similarly for content/2026/07/31/content-automation/metadata.json:
- "bluesky_uris": {},
+ "bluesky_uris": {
+ "es": [],
+ "en": []
+ },
This structure aligns with the script’s expectations and preserves per‑language history.
2. Add placeholder files for Bluesky posts
The automation workflow expects a JSON file per language that contains an array of post objects. For the first run we create empty arrays:
content/2026/07/31/VS/bluesky_en.json:
[]
content/2026/07/31/VS/bluesky_es.json:
[]
The lack of a newline at the end is intentional to match the existing style of the repository.
3. Add new markdown files for other platforms
We added medium_en.md, medium_es.md, substack_en.md, substack_es.md, and a changelog.md as placeholders for future content. They are empty at this stage, but the filenames signal the intended structure:
content/2026/07/31/VS/medium_en.md
content/2026/07/31/VS/medium_es.md
content/2026/07/31/VS/substack_en.md
content/2026/07/31/VS/substack_es.md
content/2026/07/31/VS/changelog.md
4. Create a comprehensive metadata.json
The new metadata.json includes repository name, date, languages, and topics, which the automation scripts consume for tagging and filtering:
{
"repo": "VS",
"date": "2026-07-31",
"languages": [
"es",
"en"
],
"topics": [
"Productivity",
"LearningPersonal",
"Docker",
"Networking"
]
}
This file is added under both content/2026/07/31/VS/ and content/2026/07/31/content-automation/.
5. Architectural decision: per‑language arrays
By storing URIs in language‑specific arrays, we:
- Avoid accidental mixing of URLs across locales.
- Simplify filtering logic when generating platform‑specific changelogs.
- Enable future extensions (e.g., adding a
frkey) without touching the script.
The automation script now looks like:
const metadata = require('../../metadata.json');
const publishBluesky = async () => {
const uris = metadata.bluesky_uris[lang];
if (!Array.isArray(uris)) throw new Error('bluesky_uris must be an array');
// ... publish logic
uris.push(newUri);
await fs.writeFile('../../metadata.json', JSON.stringify(metadata, null, 2));
};
Key Takeaway
Always match the data structure your scripts consume.
When a script expects an array, don't default to an empty object. Even if the initial run will populate it, using the wrong type can lead to silent failures or data loss. A small schema mismatch can cascade into a broken CI pipeline.
What's Next
- Populate the Bluesky JSON files with real post objects during the build.
-
Implement a changelog generator that reads
bluesky_urisper language and outputs a markdown changelog. -
Add tests that assert the presence of
bluesky_urisarrays before publishing. -
Extend the metadata schema to include
medium_generated,substack_generated, etc., and adjust scripts accordingly.
By solidifying the metadata contract early, we reduce runtime errors and make the automation pipeline more maintainable.
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-01
#playadev #buildinpublic
Top comments (1)
Nice write-up! For devs who deal with messy copied text, TextStow might help — it's a Mac menu bar tool combining clipboard history with prompt templates and text cleanup. Free: textstow.com