config2ts 2.6.2 → 2.8.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 +2 -1
- package/lib/config2ts.js +93 -1
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -36,7 +36,8 @@ config2ts -d config -o dist -n csv.ts -a public
|
|
|
36
36
|
* `Enum[]` union contains every value actually present in the data (including `""` for empty slots)
|
|
37
37
|
* `EnumIndex` will generate index type, and will use `Enum` type to generate interface
|
|
38
38
|
* `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
|
|
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
|
|
40
|
+
* 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
|
|
40
41
|
* `Object` parse `key:value,key:value` format, auto-infer value types (number/boolean/string), generates a dedicated `type` that merges all keys
|
|
41
42
|
* `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[]`
|
|
42
43
|
* Array convention: primitive arrays (`String[]`/`Number[]`/`Enum[]`/`RefEnum[...] []`) separate elements with `,` and `Object[]` 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, `0` for number, `{}` for empty object slots; `null` is never generated
|
package/lib/config2ts.js
CHANGED
|
@@ -756,8 +756,100 @@ function GetTsString(filePath) {
|
|
|
756
756
|
return "";
|
|
757
757
|
}
|
|
758
758
|
}
|
|
759
|
+
// Collect the config file names a CSV references from its type row
|
|
760
|
+
// (Ref[file], RefEnum[file.field], Template[file] and their array forms).
|
|
761
|
+
// ini/toml files never reference other config tables.
|
|
762
|
+
function collectCsvReferences(filePath) {
|
|
763
|
+
if (GetFileExt(filePath) !== "csv") {
|
|
764
|
+
return [];
|
|
765
|
+
}
|
|
766
|
+
let fileString = fs.readFileSync(filePath).toString();
|
|
767
|
+
if (fileString.charAt(0) === "\uFEFF") {
|
|
768
|
+
fileString = fileString.substr(1);
|
|
769
|
+
}
|
|
770
|
+
const references = [];
|
|
771
|
+
(0, d3_dsv_1.csvParse)(fileString, function (d, i) {
|
|
772
|
+
if (i === 0) {
|
|
773
|
+
for (const k in d) {
|
|
774
|
+
const typeStr = d[k];
|
|
775
|
+
const ref = parseRef(typeStr);
|
|
776
|
+
const refEnum = parseRefEnum(typeStr);
|
|
777
|
+
const template = parseTemplate(typeStr);
|
|
778
|
+
const target = ref ? ref.refModule : refEnum ? refEnum.refModule : template ? template.refModule : null;
|
|
779
|
+
if (target && !references.includes(target)) {
|
|
780
|
+
references.push(target);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return null;
|
|
785
|
+
});
|
|
786
|
+
return references;
|
|
787
|
+
}
|
|
788
|
+
// Order files so every referenced table is emitted before the table that
|
|
789
|
+
// references it (a referenced namespace must exist when the referencing
|
|
790
|
+
// table's data initializes), so authors no longer need filename tricks (e.g.
|
|
791
|
+
// an "!" prefix) to control order. Files without a dependency relation keep
|
|
792
|
+
// their alphabetical order. References to missing files and circular
|
|
793
|
+
// references are reported; a cycle is broken at the back edge so output is
|
|
794
|
+
// still generated (the runtime data still needs the cycle fixed by hand).
|
|
795
|
+
function sortFilesByReferences(fileList) {
|
|
796
|
+
const ordered = fileList.slice().sort((a, b) => {
|
|
797
|
+
const baseA = path.basename(a);
|
|
798
|
+
const baseB = path.basename(b);
|
|
799
|
+
return baseA < baseB ? -1 : baseA > baseB ? 1 : 0;
|
|
800
|
+
});
|
|
801
|
+
const fileByBase = new Map();
|
|
802
|
+
for (const filePath of ordered) {
|
|
803
|
+
fileByBase.set(path.basename(filePath), filePath);
|
|
804
|
+
}
|
|
805
|
+
const depsByBase = new Map();
|
|
806
|
+
for (const filePath of ordered) {
|
|
807
|
+
const base = path.basename(filePath);
|
|
808
|
+
const deps = collectCsvReferences(filePath);
|
|
809
|
+
depsByBase.set(base, deps);
|
|
810
|
+
for (const dep of deps) {
|
|
811
|
+
if (!fileByBase.has(dep)) {
|
|
812
|
+
console.warn(`[config2ts] warning: ${base} references "${dep}" but no config file with that name was found in the config directory`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
const sorted = [];
|
|
817
|
+
// 0 = unvisited, 1 = visiting (on the current DFS trail), 2 = done
|
|
818
|
+
const state = new Map();
|
|
819
|
+
const reportedCycles = new Set();
|
|
820
|
+
function visit(base, trail) {
|
|
821
|
+
var _a, _b;
|
|
822
|
+
const current = (_a = state.get(base)) !== null && _a !== void 0 ? _a : 0;
|
|
823
|
+
if (current === 2) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (current === 1) {
|
|
827
|
+
const cycleStart = trail.indexOf(base);
|
|
828
|
+
const cycleKey = trail.slice(cycleStart).concat(base).join(" -> ");
|
|
829
|
+
if (!reportedCycles.has(cycleKey)) {
|
|
830
|
+
reportedCycles.add(cycleKey);
|
|
831
|
+
console.warn(`[config2ts] warning: circular table reference detected: ${cycleKey}; tables in the cycle may reference an undefined namespace at runtime, please break the reference cycle`);
|
|
832
|
+
}
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
state.set(base, 1);
|
|
836
|
+
trail.push(base);
|
|
837
|
+
for (const dep of (_b = depsByBase.get(base)) !== null && _b !== void 0 ? _b : []) {
|
|
838
|
+
if (fileByBase.has(dep)) {
|
|
839
|
+
visit(dep, trail);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
trail.pop();
|
|
843
|
+
state.set(base, 2);
|
|
844
|
+
sorted.push(fileByBase.get(base));
|
|
845
|
+
}
|
|
846
|
+
for (const filePath of ordered) {
|
|
847
|
+
visit(path.basename(filePath), []);
|
|
848
|
+
}
|
|
849
|
+
return sorted;
|
|
850
|
+
}
|
|
759
851
|
function GetTsStringFromFileList(fileList) {
|
|
760
|
-
return fileList
|
|
852
|
+
return sortFilesByReferences(fileList)
|
|
761
853
|
.map(function (filePath) {
|
|
762
854
|
return GetTsString(filePath);
|
|
763
855
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "config2ts",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.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",
|
|
@@ -27,19 +27,31 @@
|
|
|
27
27
|
"url": "https://github.com/livingyang/config2ts/issues"
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/livingyang/config2ts#readme",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
30
33
|
"dependencies": {
|
|
31
34
|
"change-case": "^4.1.2",
|
|
32
35
|
"commander": "^9.5.0",
|
|
33
36
|
"d3-dsv": "^3.0.1",
|
|
34
37
|
"ini": "^3.0.1",
|
|
35
38
|
"json5": "^2.2.3",
|
|
36
|
-
"toml": "^
|
|
39
|
+
"toml": "^4.1.2"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"@types/jest": "^30.0.0",
|
|
40
43
|
"@types/node": "^24.5.2",
|
|
41
|
-
"jest": "^
|
|
42
|
-
"ts-jest": "^29.4.
|
|
44
|
+
"jest": "^30.5.1",
|
|
45
|
+
"ts-jest": "^29.4.12",
|
|
43
46
|
"typescript": "^5.9.2"
|
|
47
|
+
},
|
|
48
|
+
"overrides": {
|
|
49
|
+
"@babel/core": "^7.29.6",
|
|
50
|
+
"browserslist": "^4.28.7",
|
|
51
|
+
"handlebars": "^4.7.9",
|
|
52
|
+
"js-yaml": "^3.14.2",
|
|
53
|
+
"minimatch@^3.0.0": "^3.1.3",
|
|
54
|
+
"picomatch@^2.0.0": "^2.3.2",
|
|
55
|
+
"brace-expansion@^1.0.0": "^1.1.16"
|
|
44
56
|
}
|
|
45
57
|
}
|