config2ts
v2.9.0
Published
Generate type-safe TypeScript from CSV/INI/TOML config tables — inferred types, enums, cross-table references, and a typed asset index, all from one CLI command.
Downloads
1,165
Readme
config2ts
convert config to ts file.
install
run command: npm install -g config2ts
documentation
Full documentation is published via GitHub Pages (this repo has Pages enabled):
- English: https://livingyang.github.io/config2ts/
- 中文: https://livingyang.github.io/config2ts/index-zh.html
usage
config2ts supports two output modes selected by -m, --mode:
single(default): merge every table into one file (-n, defaultcsv.ts).split: write one.tsfile per table plus an auto-generatedindex.ts.
This repo's config/ folder ships example outputs generated from the same source
tables, so you can compare both layouts directly:
- single mode example:
config/total.ts - split mode example:
config/split/(one file per table +index.ts)
single mode
Merge all config tables into a single TypeScript file.
config2ts -d config -o dist -m single -n csv.ts -a public-nis the merged output file name (defaultcsv.ts).-ais optional; omit it to skipassets.ts.- Example output:
config/total.ts— all tables asexport namespace XxxCsv { ... }in one file.
split mode
Write one .ts file per config table (named after the source file, e.g.
skill.csv → skill.ts) plus an auto-generated index.ts that re-exports every
table and assets.ts. Cross-table Ref/RefEnum/Template references are
resolved with import statements injected at the top of the referencing file, so
every table is independently usable and tree-shakable.
config2ts -d config -o dist -m split -a public-nis reused as the index file name; the single-mode defaultcsv.tsis replaced byindex.tsunless you pass-nexplicitly.-ais optional; omit it to skipassets.ts.- Example output:
config/split/—z-base.ts,a-user.ts,skill.ts, … ,assets.ts, andindex.ts.
Output layout under dist:
dist/
z-base.ts # export namespace ZBaseCsv { ... }
a-user.ts # import { ZBaseCsv } from "./z-base"; export namespace AUserCsv { ... }
skill.ts
...
assets.ts # generated only when -a is given
index.ts # export * from "./z-base"; export * from "./a-user"; ...; export * from "./assets";Notes:
- Tables are emitted in topological order; a table only
imports tables it actually references, and missing/circular references print the same warnings as single mode.
support type
| csv field | typescript type | | :--------: | :---------------: | | Index | string | | String | string | | Number | number | | Boolean | boolean | | Enum | type | | EnumIndex | type | | String[] | string[] | | Number[] | number[] | | Enum[] | type[] | | Ref | namespace.Record | | RefEnum | namespace.type | | RefEnum[] | namespace.type[] | | Template | namespace.Record | | Template[] | namespace.Record[] | | Object | type (Record) | | Object[] | type[] (Record[]) |
Numbersupport Infinity and NaNEnumsupport empty string typeEnum[]union contains every value actually present in the data (including""for empty slots)EnumIndexwill generate index type, and will useEnumtype to generate interfaceRef/RefEnumreference another csv's Record/enum by id:Ref[file]is one row (file.Map["id"]),Ref[file][]is an array of rows with comma-separated ids ([file.Map["1"],file.Map["2"]], typedFile.Record[]); useObject[]for inline private structures andRef[]for shared, identity-bearing rowsTemplate[file]inherits a whole row from another table and overrides selected fields: a cell is<baseId>|<key>:<value>,<key>:<value>— a pipe separates the base row id from override pairs (e.g.101|damage:150,range:8), emitting{ ...file.Map["101"], damage: 150, range: 8 }typedFile.Record; with no overrides the cell is just the id and emits a plainfile.Map["id"]likeRef. Override values may be bare scalars or bracket-wrapped composites mirroring the generated TS syntax — arrays as[v1,v2](e.g.Params:[6,2.07], required for array-typed fields; a bareParams:6,2.07gets chopped by the pair comma and prints a warning) and flat objects as{k:v,k:v}.Template[file][]separates entries with;(the same element separator asObject[]), e.g.101|damage:150;102|heal:500emits[{ ...file.Map["101"], damage: 150 },{ ...file.Map["102"], heal: 500 }]. Override key typos and value-type mismatches fail at tsc compile time; overrides are flat top-level fields (no dot-path deep merge), and the target table must have an Index/EnumIndex Map- Merge order is resolved automatically: tables are topologically sorted so every referenced table (
Ref/RefEnum/Template) is emitted before the tables referencing it — no filename tricks needed; tables without a dependency relation keep alphabetical order. Circular references (a.csv -> b.csv -> a.csv) and references to missing files print a warning Objectparsekey:value,key:valueformat, auto-infer value types (number/boolean/string), generates a dedicatedtypethat merges all keysObject[]separates objects with;while commas still separatekey:valuepairs inside each object (same syntax as theObjecttype), e.g.num:1,,str:a;num:2makes[{num:1,str:'a'},{num:2}], a bare;makes[{},{}]; generates a dedicated elementtypemerging all keys with the field typed astype[]- Array convention: primitive arrays (
String[]/Number[]/Enum[]/RefEnum[...] []) separate elements with,andObject[]separates objects with;; raw cell values are normalized (CRLF/CR unified to LF, whitespace trimmed); an empty cell generates[]; otherwise n separators make n+1 slots and every slot is kept (including interior or trailing ones) so parallel arrays stay index-aligned. Empty slots use the type's own zero value —''for string/enum/ref-enum,0for number,{}for empty object slots;nullis never generated - Line breaks inside a cell (quote-wrapped in CSV) are preserved for
Stringfields (emitted as\n) and act as the equivalent separator in structural fields — a line feed equals a comma between array elements / Object pairs / Template override pairs, and equals a semicolon between Object[] elements / Template[] entries (one entry per line works; a blank line is an empty slot by the n+1 rule). Scalar fields (Number/Boolean/Enum/single Ref) have no separator semantics, so a line break there prints a warning and must be removed - unrecognized field types fall back to
stringand print a warning (check for typos)
assets2ts
Scan assets directory and generate an assets.ts index file with nested resource tree.
-a, --assets <path>set assets resource directory (optional, skip asset index if omitted)- Output file:
assets.ts(alongside the merged config file) - File and directory names are used exactly as-is (e.g.,
Direction.png→Direction,adjust-horizontal.png→'adjust-horizontal'); keys with special characters are automatically quoted typefield uses file extension (lowercase), e.g.'png','mp3','svg'- Directories containing 2+ files of the same extension get a dedicated type (e.g.
PngAsset,Mp3Asset) andsatisfies Record<string, XxxAsset>annotation - Directories with ≤1 file or mixed extensions do not get a type annotation
- Nested directories are supported (e.g.
public/sub/image/)
generated example
// Auto Generated by config2ts, DO NOT EDIT
export type Mp3Asset = { path: string; type: 'mp3' };
export type PngAsset = { path: string; type: 'png' };
export type SvgAsset = { path: string; type: 'svg' };
export const ASSETS = {
public: {
image: {
'adjust-horizontal': {path:'public/image/adjust-horizontal.png',type:'png'},
Direction: {path:'public/image/Direction.png',type:'png'}
} satisfies Record<string, PngAsset>,
music: {
effect1: {path:'public/music/effect1.mp3',type:'mp3'},
effect2: {path:'public/music/effect2.mp3',type:'mp3'}
} satisfies Record<string, Mp3Asset>,
'single-file': {
effect1: {path:'public/single-file/effect1.mp3',type:'mp3'}
}
}
};usage
import { ASSETS } from "./assets";
const meta = ASSETS.public.image.Direction;
// meta.path → 'public/image/Direction.png'
// meta.type → 'png'
const adjustMeta = ASSETS.public.image['adjust-horizontal'];
// adjustMeta.path → 'public/image/adjust-horizontal.png'Options
Options:
-h, --help output usage information
-V, --version output the version number
-d, --dir <path> set convert path. default: ./
-o, --outDir <path> set outDir path. default: same as -d
-n, --name <name> output file name (single mode) / index file name (split mode). default: csv.ts
-a, --assets <path> set assets resource directory for asset index (optional, skip asset index if omitted)
-m, --mode <mode> output mode: single (merge into one file) or split (one file per table + index.ts). default: singlesupported file formats
| format | extension | | :----: | :-------: | | csv | .csv | | ini | .ini | | toml | .toml |
