Skip to main content

Validate an Ordered Product Gallery Manifest with Node.js

Updated 4 min readMarkdown

A storefront gallery often comes from a JSON file, even when the editorial plan started in a spreadsheet. If the frontend renders the list in order, one accidental reorder can replace the lead photo. A repeated file or question can also slip through a visual review. This small Node.js check turns those editorial decisions into errors a reviewer can act on.

For this project, array order is display order. The first entry must be the only hero. Each entry names a relative image file and one stable question identifier. A spec entry also names an evidence record so a reviewer has a place to look up the underlying measurements. These are project conventions. Adapt the roles and filename pattern to your own renderer.

2. Create a fixture with three distinct questions

Save the following as gallery.json. The fictional kettle gallery answers what the product is, how the lid is built, and whether it fits the intended space. The question identifiers stand for editorial decisions, so use the same identifier if two entries address the same question.

{
  "images": [
    {
      "role": "hero",
      "asset": "images/kettle-front.webp",
      "question": "identity"
    },
    {
      "role": "detail",
      "asset": "images/kettle-lid.webp",
      "question": "lid-design"
    },
    {
      "role": "spec",
      "asset": "images/kettle-fit.webp",
      "question": "counter-fit",
      "evidence": "spec-sheet-v3"
    }
  ]
}

3. Validate the order and identifiers

Save this as gallery.mjs. The validator collects every error it can locate in one pass. The asset pattern accepts a narrow set of local-style paths so two spellings of the same path cannot pass as distinct valid values. A Set tracks repeated assets and repeated question identifiers separately.

const roles = new Set(["hero", "detail", "scene", "spec"]);
const id = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const assetPath = /^images\/[a-z0-9]+(?:-[a-z0-9]+)*\.(?:webp|png|jpg)$/;

export function validateGallery(manifest) {
  const images = manifest?.images;
  if (!Array.isArray(images) || images.length === 0) {
    return ["images must be a nonempty array"];
  }

  const errors = [];
  const assets = new Set();
  const questions = new Set();
  for (const [index, row] of images.entries()) {
    const at = "images[" + index + "]";
    if (!row || typeof row !== "object" || Array.isArray(row)) {
      errors.push(at + " must be an object");
      continue;
    }
    if (!roles.has(row.role)) errors.push(at + ".role is unknown");
    if ((index === 0) !== (row.role === "hero")) {
      errors.push(at + " must follow the rule: hero first and only once");
    }
    if (typeof row.asset !== "string" || !assetPath.test(row.asset)) {
      errors.push(at + ".asset must be an images/ filename");
    } else {
      if (assets.has(row.asset)) errors.push(at + ".asset is repeated");
      assets.add(row.asset);
    }
    if (typeof row.question !== "string" || !id.test(row.question)) {
      errors.push(at + ".question must be a lowercase identifier");
    } else {
      if (questions.has(row.question)) errors.push(at + ".question is repeated");
      questions.add(row.question);
    }
    if (row.role === "spec" &&
        (typeof row.evidence !== "string" || !id.test(row.evidence))) {
      errors.push(at + ".evidence must name a spec record");
    }
  }
  return errors;
}

4. Give your build a distinct failure code

Save this as check-gallery.mjs. The command reads one UTF-8 JSON file. A valid manifest exits with 0. A parsed manifest that violates the contract exits with 1. A missing argument, unreadable file, or invalid JSON exits with 2. A CI job can fail on any nonzero status while preserving the distinction in its logs.

import { readFile } from "node:fs/promises";
import { validateGallery } from "./gallery.mjs";

const args = process.argv.slice(2);
if (args.length !== 1) {
  console.error("Usage: node check-gallery.mjs gallery.json");
  process.exitCode = 2;
} else {
  try {
    const manifest = JSON.parse(await readFile(args[0], "utf8"));
    const errors = validateGallery(manifest);
    for (const error of errors) console.error(error);
    if (errors.length) process.exitCode = 1;
    else console.log("Gallery manifest passed");
  } catch (error) {
    console.error(error.message);
    process.exitCode = 2;
  }
}

5. Run a passing and failing example

Run the checker from the directory where you saved the three files. The fixture prints Gallery manifest passed. To exercise the duplicate question check, copy it to broken-order.json and change the second entry’s question to identity. The second invocation reports images[1].question is repeated and exits with 1.

node check-gallery.mjs gallery.json
node check-gallery.mjs broken-order.json

6. Keep the remaining review checks separate

A valid manifest tells you that the intended order and identifiers are internally consistent. Pair this check with an asset existence check if you need to catch missing image files. Review the rendered gallery for legibility and verify each spec against the evidence record. The checker reads metadata and does not open images or verify claims in the artwork.

Source: PixMind’s product listing image order guide. Reference: Node.js file system promises documentation.

AI disclosure: AI assisted the drafting. The manifest is fictional. The Node.js validator and command-line examples were tested with synthetic JSON.

About the author

  • The PixMind team shares practical AI image and video workflows. PixMind brings text-to-image, reference-based creation, editing, and video tools into one workspace. Website: https://www.pixmind.io/

No comments yet. Be the first to comment.