populate-files
Write generated files from code, commit them, and fail CI when they are out of date or were edited by hand.
Describe the files in a module:
// src/file-content.ts
import type { PopulateFileParams } from 'load-populate-files';
export default [
{ filePath: 'schema.json', content: { type: 'object', required: ['id'] } },
{ filePath: 'version.ts', content: "export const version = '1.2.3';\n", signed: true },
] satisfies PopulateFileParams[];Run load-populate-files --file-path ./dist/file-content.js --target-dir ./out, and it writes out/schema.json, formatted with your project's Biome or Prettier config, and a signed out/version.ts:
// @generated-signature v1:cecd05739860e0fb35c62d03ed813b26 Generated file. Do not edit outside of manual sections.
export const version = '1.2.3';Commit the output. In CI, the same command checks instead of writing, and fails when a file would change.
Features
- Up to date in CI:
check(--ci) computes every file without writing and fails if any would change. It is on by default in CI. - Any content: strings and bytes are written as is. Objects are written as JSON, formatted like the rest of your project.
- Hand edits are caught: a signed file carries a hash of its content. Editing it fails the next run, in CI too, and an ESLint rule shows it in the editor.
- Manual sections: parts of a signed file can be left for people to edit. Their content survives regeneration.
- A manifest for everything else: files that can't hold a comment, like JSON, are hashed in a manifest instead. It also makes
cleanremove only files a previous run wrote. - Two packages:
populate-filesis the API.load-populate-filesadds a CLI that loads the files from a config module.
Compared to
- A script with
fs.writeFileand agit diff --exit-codestep in CI checks freshness too, but needs git, overwrites hand edits without warning, and leaves deleted outputs behind. - cog generates regions inside handwritten files, and checks them with
--check. populate-files is the inverse: the file is generated, and manual sections are the handwritten regions. - Nx generators and Plop scaffold files once, for people to edit afterwards. populate-files owns its files, and regenerates them on every run.
Related
Packages
- genever
Generate TypeScript types from JSON Schema files and OpenAPI documents, with strict schema checks and the same types Juniper infers.
- pg-dolly-schema
Keep a schema.sql of what your Postgres migrations build, for any migration tool, and fail CI when it’s stale.
- nx-lifecycle
Nx plugin that derives every dependsOn from declared lifecycle stages (build, test, …) and the targets bound to them.
- Barrelify
Generate and CI-verify TypeScript barrel (index.ts) files with the correct ESM/CJS import extensions.
- generated-signature
Sign generated files so hand edits are detected by an ESLint rule or a single function call, while manual sections stay editable.
Used in demos
- OpenAPI
A notes API from spec to database: an OpenAPI 3.1 document built from Juniper schemas, genever types for a typed client and a Hono server that validates every request and response, and Drizzle on Postgres with a database per test.