Random AI Prompt: a 2026 revival — ES modules, Node 24, and full docs

A dormant 2022 prompt generator is modernised to ES modules on Node 24, reorganised, and documented end to end.

Attention this week turns to the other project in the hub: Random AI Prompt, a JavaScript prompt generator for Stable Diffusion. It was largely a late-2022 / early-2023 effort — a substantial one — and then sat dormant until now. This day was its revival: dragging the codebase onto current tooling without changing what it does.

From CommonJS to ESM

The starting point was a 2022 CommonJS codebase. The target was ES modules on Node 24, the active LTS, with every dependency moved to its current major. The entry points opt in through package.json:

{
  "type": "module",
  "engines": { "node": ">=24" }
}

The bulk of the conversion — around 123 files — was done with two throwaway codemods (module.exports = functionexport default function, require("x")import x from "x.js"), then the trickier ~28 files were finished by hand. A couple of genuine ES-module pitfalls came out of it and are worth keeping.

The sharpest one: imports are evaluated before top-level statements. The old code changed the working directory at the top of common.js, but under ESM that line would now run after the settings module had already been imported and read its config file:

// Old (CommonJS): ran early enough to matter
process.chdir(__dirname);
const settings = require("./settings"); // reads ./user-settings.json

// New (ESM): the chdir moves into its own module, imported first
import "./chdir.js";          // performs process.chdir(...) on load
import settings from "./settings.js";

A second one: the generators are loaded by path inside synchronous string-replace callbacks, so switching them to await import() would have meant rewriting the whole pipeline. createRequire preserves the synchronous behaviour exactly:

import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const generator = require(generatorPath).default;

Proving the port changed nothing

A modernisation is only worth anything if it’s behaviour-preserving, so that was checked rather than asserted. An import smoke test loads the entire module graph, pulls in all 113 dynamic-prompt generators, and expands a sample prompt:

$ npm run smoke
✓ module graph loaded (152 files, node --check clean)
✓ 113 dynamic prompts loaded
✓ promptSuggestion() ran; {#random} expanded

The tree was then reorganised so that all code lives under src/ and all prompt content under data/, with runtime and user data left at the root. That’s a careful change, because the loaders resolve paths relative to files, not the working directory. To prove nothing was lost, the original pre-revival source was pinned as a read-only snapshot and diffed against the current tree:

Category Original Current Result
dynamic prompts 113 113 none dropped
prompt modules 5 5 none dropped
lists 61 61 none dropped
expansions 9 9 none dropped
presets 26 26 none dropped

All 96 curated data files matched the original line-for-line; of 158 shared code files, the differences were either formatting or understood, deliberate refactors.

Documentation, end to end

Finally, the project was brought up to the same management system as its sibling — living notes, a single-source version number, a documentation site, and CI/release workflows. The documentation went all the way to per-function coverage across the server engine, all 113 generators, the classic frontend, and the React web app, settling on JSDoc with the notes wired in as tutorials — one site of around 244 pages.

References


← All updates