DEV Community

Roberto Luna
Roberto Luna

Posted on

Updating Metadata for Multi‑Language Bluesky Publishing in Content‑Automation

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)
Enter fullscreen mode Exit fullscreen mode

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": {}
Enter fullscreen mode Exit fullscreen mode

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 = [];
}
Enter fullscreen mode Exit fullscreen mode

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": []
+  },
Enter fullscreen mode Exit fullscreen mode

And similarly for content/2026/07/31/content-automation/metadata.json:

-  "bluesky_uris": {},
+  "bluesky_uris": {
+    "es": [],
+    "en": []
+  },
Enter fullscreen mode Exit fullscreen mode

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:

[]
Enter fullscreen mode Exit fullscreen mode

content/2026/07/31/VS/bluesky_es.json:

[]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
  ]
}
Enter fullscreen mode Exit fullscreen mode

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 fr key) 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));
};
Enter fullscreen mode Exit fullscreen mode

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

  1. Populate the Bluesky JSON files with real post objects during the build.
  2. Implement a changelog generator that reads bluesky_uris per language and outputs a markdown changelog.
  3. Add tests that assert the presence of bluesky_uris arrays before publishing.
  4. 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)

Collapse
 
vic_xie_9bed0062d5fd73d12 profile image
vic xie

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