Taglify replaces the content between marker comments in any text file, and leaves everything else byte-for-byte alone. Point it at a README, a config file, or a source file, hand it a string, and the block is up to date.
Zero runtime dependencies. ESM, CJS, and types. Node 18+.
bun add -D taglifynpm install -D taglifyAdd this to your readme:
<!-- STATS:START -->
<!-- STATS:END -->In your build flow:
import { taglWrite } from 'taglify';
const coverageSummary = '...';
taglWrite('./README.md', { STATS: coverageSummary });after:
<!-- STATS:START -->
| Browser | Passed | Failed |
| ------- | ------ | ------ |
| Brave | 127 | 0 |
| Firefox | 127 | 0 |
| Edge | 0 | 127 |
<!-- STATS:END -->Same idea works for any file type, in a CI step, a git pre-commit hook, or a cron job.
| Param | Type | Description |
|---|---|---|
text |
string |
Source text |
tags |
Record<string, string> |
Tag name → replacement content |
options |
TaglifyOptions |
See below |
Returns { text: string, changed: boolean, diffs: BlockDiff[], write: (filePath: string) => void }.
write writes text to filePath, but only if changed — so you can call
taglText(...).write('./README.md') without importing fs yourself.
diffs lists { tag, before, after } for each block that changed.
Reads filePath, applies taglText, and writes back only if the content
changed. Internally it's just taglText(...).write(filePath).
- Returns
boolean— whether the file was modified. - On error (missing file, unreadable file, write failure), logs a clear
message (with the underlying error as
cause) viaconsole.errorand returnsfalse. Pass{ throwOnError: true }to throw instead.
Async counterpart to taglWrite, using fs/promises for non-blocking I/O.
Behaves identically otherwise.
Reads the current content between an existing tag's markers, de-indented the
same way taglText indents replacements.
import { taglRead } from 'taglify';
taglRead('<!-- STATS:START -->\nold\n<!-- STATS:END -->', 'STATS'); // -> 'old'Returns string | null — null if the tag isn't found.
Small helpers for building the replacement string for common shapes —
plain string in, plain string out, nothing else to learn.
import { formatList, formatTable } from 'taglify';
formatList(['a', 'b']); // -> '- a\n- b'
formatTable(['Browser', 'Passed'], [['Brave', '127']]);
// -> '| Browser | Passed |\n| ------- | ------ |\n| Brave | 127 |'| Option | Type | Default | Description |
|---|---|---|---|
commentStyle |
Record<open, close> |
{ '<!-- ': ' -->' } |
Marker open/close pairs to recognize |
startSuffix |
string |
'START' |
Suffix marking the start of a block, e.g. TAG:START |
endSuffix |
string |
'END' |
Suffix marking the end of a block, e.g. TAG:END |
throwOnError |
boolean |
false |
taglWrite throws on error instead of logging + false |
createMissing |
boolean |
false |
Append a new block at EOF for a tag with no match, instead of skipping |
commentStyle replaces the default entirely — pass every style you want
matched. Multiple entries are all tried, so different blocks in the same
file can use different styles:
taglText(text, tags, {
commentStyle: { '// ': '', '# ': '', '/* ': ' */' },
});Only HTML comments are matched by default because # and // are common
in ordinary content (markdown headings, URLs) and risk false-positive
matches — opt in explicitly for those.
<!-- TAG:START -->
content
<!-- TAG:END -->| Rule | Behavior |
|---|---|
| Tag name casing | Case-insensitive, normalized to uppercase |
| Content between markers | Fully replaced |
| Markers themselves | Always preserved |
| Multiple blocks, same tag | All replaced |
| Tag with no matching block | Skipped (or appended at EOF with createMissing) |
| Replacement value type | string only |
The API above is complete and stable. Taglify does one thing, and that thing is done — new versions add options, they don't change what existing calls do.
| Runtime dependencies | none |
| Versioning | semver; breaking changes only in a major |
| Formats | ESM, CJS, and .d.ts types |
| Runtimes | Node 18+ and Bun, both exercised in CI |
| Tests | full suite + typecheck + build on every push (CI) |
| Failure mode | never partially writes; returns false (or throws with throwOnError) |
Deliberately out of scope: templating, markdown generation, non-string tag values, config files. Keeping those out is what keeps the failure surface this small.
Not scheduled, and not needed for the current API to be complete. Full list in BACKLOG.md.
- CLI for running taglify against files/globs.
- Glob support for matching multiple files.
- Directory/recursive processing.
- Ignore rules.
- Support non-string values.
