config2ts 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -7
- package/lib/config2ts.d.ts +1 -1
- package/lib/config2ts.js +77 -2
- package/lib/main.js +8 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,9 +6,72 @@ convert config to ts file.
|
|
|
6
6
|
|
|
7
7
|
run command: `npm install -g config2ts`
|
|
8
8
|
|
|
9
|
-
#
|
|
9
|
+
# documentation
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Full documentation is published via GitHub Pages (this repo has Pages enabled):
|
|
12
|
+
|
|
13
|
+
- English: https://livingyang.github.io/config2ts/
|
|
14
|
+
- 中文: https://livingyang.github.io/config2ts/index-zh.html
|
|
15
|
+
|
|
16
|
+
# usage
|
|
17
|
+
|
|
18
|
+
`config2ts` supports two output modes selected by `-m, --mode`:
|
|
19
|
+
|
|
20
|
+
- `single` (default): merge every table into one file (`-n`, default `csv.ts`).
|
|
21
|
+
- `split`: write one `.ts` file per table plus an auto-generated `index.ts`.
|
|
22
|
+
|
|
23
|
+
This repo's `config/` folder ships example outputs generated from the same source
|
|
24
|
+
tables, so you can compare both layouts directly:
|
|
25
|
+
|
|
26
|
+
- single mode example: [`config/total.ts`](config/total.ts)
|
|
27
|
+
- split mode example: [`config/split/`](config/split/) (one file per table + `index.ts`)
|
|
28
|
+
|
|
29
|
+
## single mode
|
|
30
|
+
|
|
31
|
+
Merge all config tables into a single TypeScript file.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
config2ts -d config -o dist -m single -n csv.ts -a public
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- `-n` is the merged output file name (default `csv.ts`).
|
|
38
|
+
- `-a` is optional; omit it to skip `assets.ts`.
|
|
39
|
+
- Example output: [`config/total.ts`](config/total.ts) — all tables as `export namespace XxxCsv { ... }` in one file.
|
|
40
|
+
|
|
41
|
+
## split mode
|
|
42
|
+
|
|
43
|
+
Write one `.ts` file per config table (named after the source file, e.g.
|
|
44
|
+
`skill.csv` → `skill.ts`) plus an auto-generated `index.ts` that re-exports every
|
|
45
|
+
table and `assets.ts`. Cross-table `Ref`/`RefEnum`/`Template` references are
|
|
46
|
+
resolved with `import` statements injected at the top of the referencing file, so
|
|
47
|
+
every table is independently usable and tree-shakable.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
config2ts -d config -o dist -m split -a public
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- `-n` is reused as the index file name; the single-mode default `csv.ts` is
|
|
54
|
+
replaced by `index.ts` unless you pass `-n` explicitly.
|
|
55
|
+
- `-a` is optional; omit it to skip `assets.ts`.
|
|
56
|
+
- Example output: [`config/split/`](config/split/) — `z-base.ts`, `a-user.ts`,
|
|
57
|
+
`skill.ts`, … , `assets.ts`, and `index.ts`.
|
|
58
|
+
|
|
59
|
+
Output layout under `dist`:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
dist/
|
|
63
|
+
z-base.ts # export namespace ZBaseCsv { ... }
|
|
64
|
+
a-user.ts # import { ZBaseCsv } from "./z-base"; export namespace AUserCsv { ... }
|
|
65
|
+
skill.ts
|
|
66
|
+
...
|
|
67
|
+
assets.ts # generated only when -a is given
|
|
68
|
+
index.ts # export * from "./z-base"; export * from "./a-user"; ...; export * from "./assets";
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Notes:
|
|
72
|
+
- Tables are emitted in topological order; a table only `import`s tables it
|
|
73
|
+
actually references, and missing/circular references print the same warnings as
|
|
74
|
+
single mode.
|
|
12
75
|
|
|
13
76
|
# support type
|
|
14
77
|
|
|
@@ -36,7 +99,7 @@ config2ts -d config -o dist -n csv.ts -a public
|
|
|
36
99
|
* `Enum[]` union contains every value actually present in the data (including `""` for empty slots)
|
|
37
100
|
* `EnumIndex` will generate index type, and will use `Enum` type to generate interface
|
|
38
101
|
* `Ref` / `RefEnum` reference 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"]]`, typed `File.Record[]`); use `Object[]` for inline private structures and `Ref[]` for shared, identity-bearing rows
|
|
39
|
-
* `Template[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 }` typed `File.Record`; with no overrides the cell is just the id and emits a plain `file.Map["id"]` like `Ref`. 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 bare `Params:6,2.07` gets 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 as `Object[]`), e.g. `101|damage:150;102|heal:500` emits `[{ ...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
|
|
102
|
+
* `Template[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 }` typed `File.Record`; with no overrides the cell is just the id and emits a plain `file.Map["id"]` like `Ref`. 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 bare `Params:6,2.07` gets 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 as `Object[]`), e.g. `101|damage:150;102|heal:500` emits `[{ ...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
|
|
40
103
|
* 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
|
|
41
104
|
* `Object` parse `key:value,key:value` format, auto-infer value types (number/boolean/string), generates a dedicated `type` that merges all keys
|
|
42
105
|
* `Object[]` separates objects with `;` while commas still separate `key:value` pairs inside each object (same syntax as the `Object` type), e.g. `num:1,,str:a;num:2` makes `[{num:1,str:'a'},{num:2}]`, a bare `;` makes `[{},{}]`; generates a dedicated element `type` merging all keys with the field typed as `type[]`
|
|
@@ -48,7 +111,7 @@ config2ts -d config -o dist -n csv.ts -a public
|
|
|
48
111
|
|
|
49
112
|
Scan assets directory and generate an `assets.ts` index file with nested resource tree.
|
|
50
113
|
|
|
51
|
-
- `-a, --assets <path>` set assets resource directory
|
|
114
|
+
- `-a, --assets <path>` set assets resource directory (optional, skip asset index if omitted)
|
|
52
115
|
- Output file: `assets.ts` (alongside the merged config file)
|
|
53
116
|
- 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
|
|
54
117
|
- `type` field uses file extension (lowercase), e.g. `'png'`, `'mp3'`, `'svg'`
|
|
@@ -103,9 +166,10 @@ const adjustMeta = ASSETS.public.image['adjust-horizontal'];
|
|
|
103
166
|
-h, --help output usage information
|
|
104
167
|
-V, --version output the version number
|
|
105
168
|
-d, --dir <path> set convert path. default: ./
|
|
106
|
-
-o, --outDir <path> set outDir path. default:
|
|
107
|
-
-n, --name <name> output file name. default: csv.ts
|
|
108
|
-
-a, --assets <path> set assets resource directory for asset index
|
|
169
|
+
-o, --outDir <path> set outDir path. default: same as -d
|
|
170
|
+
-n, --name <name> output file name (single mode) / index file name (split mode). default: csv.ts
|
|
171
|
+
-a, --assets <path> set assets resource directory for asset index (optional, skip asset index if omitted)
|
|
172
|
+
-m, --mode <mode> output mode: single (merge into one file) or split (one file per table + index.ts). default: single
|
|
109
173
|
```
|
|
110
174
|
|
|
111
175
|
## supported file formats
|
package/lib/config2ts.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export declare function assets2ts(assetsDir: string): string;
|
|
|
4
4
|
export declare function GetTsString(filePath: string): string;
|
|
5
5
|
export declare function GetTsStringFromFileList(fileList: string[]): string;
|
|
6
6
|
export declare function GetValidFileList(fileList: string[]): string[];
|
|
7
|
-
export declare function startConvert(dir: string, outDir: string, merge: string, assetsDir?: string): void;
|
|
7
|
+
export declare function startConvert(dir: string, outDir: string, merge: string, assetsDir?: string, mode?: "single" | "split"): void;
|
|
8
8
|
export {};
|
package/lib/config2ts.js
CHANGED
|
@@ -235,6 +235,17 @@ function csv2ts(csvString, moduleName) {
|
|
|
235
235
|
template += ` };\n\n`;
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
+
// A single array literal holding more than ~1000 object literals makes tsc's
|
|
239
|
+
// subtype reduction (removeSubtypes) bail out with "ts(2590) Expression
|
|
240
|
+
// produces a union type that is too complex to represent". The only thing
|
|
241
|
+
// that makes every row a distinct object literal type is the EnumIndex
|
|
242
|
+
// (key) field, whose value is a unique literal per row. Falling that field
|
|
243
|
+
// back to string for large tables makes every row share one type, so the
|
|
244
|
+
// array literal union collapses instantly and the error disappears. The
|
|
245
|
+
// `key` union and `keyList` are still emitted (they do not trigger the
|
|
246
|
+
// limit), so cross-table RefEnum references keep working.
|
|
247
|
+
const maxRowsToKeepEnumKeyType = 1000;
|
|
248
|
+
const largeTable = result.length > maxRowsToKeepEnumKeyType;
|
|
238
249
|
template += " export interface Record {\n";
|
|
239
250
|
let indexField = null;
|
|
240
251
|
for (const field in convert) {
|
|
@@ -244,7 +255,7 @@ function csv2ts(csvString, moduleName) {
|
|
|
244
255
|
fieldType = field;
|
|
245
256
|
}
|
|
246
257
|
else if (convert[field] == EnumIndexStr) {
|
|
247
|
-
fieldType = field;
|
|
258
|
+
fieldType = largeTable ? "string" : field;
|
|
248
259
|
indexField = field;
|
|
249
260
|
}
|
|
250
261
|
else if (convert[field] == IndexStr) {
|
|
@@ -860,11 +871,75 @@ function GetValidFileList(fileList) {
|
|
|
860
871
|
return exports.Convert[GetFileExt(filePath)] != null;
|
|
861
872
|
});
|
|
862
873
|
}
|
|
863
|
-
|
|
874
|
+
// Build the `import { XxxCsv } from "./xxx";` lines a split-mode file needs so
|
|
875
|
+
// its cross-table Ref/RefEnum/Template references resolve. `collectCsvReferences`
|
|
876
|
+
// returns the full base filenames (e.g. "z-base.csv") of referenced tables, which
|
|
877
|
+
// map 1:1 to the split output file "./z-base.ts" and to the namespace
|
|
878
|
+
// `pascalCase("z-base.csv")` === "ZBaseCsv".
|
|
879
|
+
function buildImportLines(filePath, validBases) {
|
|
880
|
+
const base = path.basename(filePath);
|
|
881
|
+
const refs = collectCsvReferences(filePath).filter((ref) => ref !== base);
|
|
882
|
+
const imported = new Set();
|
|
883
|
+
const lines = [];
|
|
884
|
+
for (const ref of refs) {
|
|
885
|
+
if (!validBases.has(ref)) {
|
|
886
|
+
continue; // missing referenced table; already warned by sortFilesByReferences
|
|
887
|
+
}
|
|
888
|
+
if (imported.has(ref)) {
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
imported.add(ref);
|
|
892
|
+
const ns = changeCase.pascalCase(ref);
|
|
893
|
+
const importPath = "./" + path.parse(ref).name;
|
|
894
|
+
lines.push(`import { ${ns} } from "${importPath}";`);
|
|
895
|
+
}
|
|
896
|
+
return lines.join("\n");
|
|
897
|
+
}
|
|
898
|
+
function startConvert(dir, outDir, merge, assetsDir, mode = "single") {
|
|
864
899
|
const fileList = fs.readdirSync(dir);
|
|
865
900
|
const fullFileList = GetValidFileList(fileList).map(function (filename) {
|
|
866
901
|
return path.join(dir, filename);
|
|
867
902
|
});
|
|
903
|
+
if (mode === "split") {
|
|
904
|
+
const validBases = new Set(fullFileList.map((f) => path.basename(f)));
|
|
905
|
+
const orderedFiles = sortFilesByReferences(fullFileList);
|
|
906
|
+
const exportedNames = [];
|
|
907
|
+
for (const filePath of orderedFiles) {
|
|
908
|
+
const base = path.basename(filePath);
|
|
909
|
+
const outName = path.parse(base).name + ".ts";
|
|
910
|
+
exportedNames.push(path.parse(base).name);
|
|
911
|
+
const imports = buildImportLines(filePath, validBases);
|
|
912
|
+
const body = GetTsString(filePath);
|
|
913
|
+
const content = AutoGenHeader + (imports ? imports + "\n\n" : "") + body + "\n";
|
|
914
|
+
const outPath = path.join(outDir, outName);
|
|
915
|
+
fs.writeFileSync(outPath, content, { encoding: "utf-8" });
|
|
916
|
+
console.log(`config2ts, ${base} -> ${outPath}`);
|
|
917
|
+
}
|
|
918
|
+
let assetsExport = null;
|
|
919
|
+
if (assetsDir) {
|
|
920
|
+
const assetsOutput = assets2ts(assetsDir);
|
|
921
|
+
if (assetsOutput) {
|
|
922
|
+
const assetsFile = path.join(outDir, "assets.ts");
|
|
923
|
+
fs.writeFileSync(assetsFile, AutoGenHeader + assetsOutput, { encoding: "utf-8" });
|
|
924
|
+
console.log(`assets2ts, resource dir: ${assetsDir}, output: ${assetsFile}`);
|
|
925
|
+
assetsExport = "assets";
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
// In split mode the `-n` value is reused as the index file name; fall back to
|
|
929
|
+
// "index.ts" when the caller leaves the single-mode default "csv.ts" intact.
|
|
930
|
+
const indexName = merge !== "csv.ts" ? merge : "index.ts";
|
|
931
|
+
let indexContent = AutoGenHeader;
|
|
932
|
+
for (const name of exportedNames) {
|
|
933
|
+
indexContent += `export * from "./${name}";\n`;
|
|
934
|
+
}
|
|
935
|
+
if (assetsExport) {
|
|
936
|
+
indexContent += `export * from "./${assetsExport}";\n`;
|
|
937
|
+
}
|
|
938
|
+
const indexPath = path.join(outDir, indexName);
|
|
939
|
+
fs.writeFileSync(indexPath, indexContent, { encoding: "utf-8" });
|
|
940
|
+
console.log(`config2ts, ${orderedFiles.length} config files, split mode, index: ${indexPath}`);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
868
943
|
const mergeFile = path.join(outDir, merge);
|
|
869
944
|
fs.writeFileSync(mergeFile, AutoGenHeader + GetTsStringFromFileList(fullFileList), { encoding: "utf-8" });
|
|
870
945
|
console.log(`config2ts, ${fullFileList.length} config files, merge into: ${mergeFile}`);
|
package/lib/main.js
CHANGED
|
@@ -42,10 +42,11 @@ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
|
42
42
|
const program = new commander_1.Command();
|
|
43
43
|
program
|
|
44
44
|
.version(pkg.version)
|
|
45
|
-
.option("-n, --name <name>", "output file name", "csv.ts")
|
|
45
|
+
.option("-n, --name <name>", "output file name (single mode) / index file name (split mode)", "csv.ts")
|
|
46
46
|
.option("-d, --dir <path>", "set convert path", ".")
|
|
47
47
|
.option("-o, --outDir <path>", "set outDir path")
|
|
48
|
-
.option("-a, --assets <path>", "set assets resource directory for asset index
|
|
48
|
+
.option("-a, --assets <path>", "set assets resource directory for asset index (optional, skip asset index if omitted)")
|
|
49
|
+
.option("-m, --mode <mode>", "output mode: single (merge into one file) or split (one file per table + index.ts)", "single")
|
|
49
50
|
.parse(process.argv);
|
|
50
51
|
const options = program.opts();
|
|
51
52
|
console.log(options);
|
|
@@ -60,4 +61,8 @@ const assetsDir = options.assets ? path.resolve(options.assets) : undefined;
|
|
|
60
61
|
if (assetsDir) {
|
|
61
62
|
console.log("assetsDir:", assetsDir);
|
|
62
63
|
}
|
|
63
|
-
|
|
64
|
+
const mode = options.mode === "split" ? "split" : "single";
|
|
65
|
+
if (mode === "single" && options.mode !== "single") {
|
|
66
|
+
console.warn(`[config2ts] warning: unknown mode "${options.mode}", falling back to "single"`);
|
|
67
|
+
}
|
|
68
|
+
(0, config2ts_1.startConvert)(dir, outDir, options.name, assetsDir, mode);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "config2ts",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "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.",
|
|
5
5
|
"main": "lib/config2ts.js",
|
|
6
6
|
"types": "lib/config2ts.d.ts",
|