config2ts 2.5.8 → 2.6.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 +5 -2
- package/lib/config2ts.js +143 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,9 @@ config2ts -d config -o dist -n csv.ts -a public
|
|
|
25
25
|
| Enum[] | type[] |
|
|
26
26
|
| Ref | namespace.Record |
|
|
27
27
|
| RefEnum | namespace.type |
|
|
28
|
-
|
|
|
28
|
+
| RefEnum[] | namespace.type[] |
|
|
29
|
+
| Template | namespace.Record |
|
|
30
|
+
| Template[] | namespace.Record[] |
|
|
29
31
|
| Object | type (Record) |
|
|
30
32
|
| Object[] | type[] (Record[]) |
|
|
31
33
|
|
|
@@ -33,7 +35,8 @@ config2ts -d config -o dist -n csv.ts -a public
|
|
|
33
35
|
* `Enum` support empty string type
|
|
34
36
|
* `Enum[]` union contains every value actually present in the data (including `""` for empty slots)
|
|
35
37
|
* `EnumIndex` will generate index type, and will use `Enum` type to generate interface
|
|
36
|
-
* `Ref` / `RefEnum` reference another csv's Record/
|
|
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 Object-style 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`. `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 only, and the target table must have an Index/Map (same file-ordering rule as `Ref`)
|
|
37
40
|
* `Object` parse `key:value,key:value` format, auto-infer value types (number/boolean/string), generates a dedicated `type` that merges all keys
|
|
38
41
|
* `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[]`
|
|
39
42
|
* Array convention: primitive arrays (`String[]`/`Number[]`/`Enum[]`/`RefEnum[...] []`) separate elements with `,` and `Object[]` separates objects with `;`; raw cell values are normalized (line breaks removed, 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
|
@@ -55,6 +55,12 @@ const RefPrefix = "Ref[";
|
|
|
55
55
|
const RefSuffix = "]";
|
|
56
56
|
const RefEnumPrefix = "RefEnum[";
|
|
57
57
|
const RefEnumArraySuffix = "][]";
|
|
58
|
+
const TemplatePrefix = "Template[";
|
|
59
|
+
// Inside a Template cell, '|' separates the referenced base row id from the
|
|
60
|
+
// Object-style override pairs; ';' separates multiple entries in the array
|
|
61
|
+
// form (same element separator as Object[]).
|
|
62
|
+
const TemplateOverrideSeparator = "|";
|
|
63
|
+
const TemplateEntrySeparator = ";";
|
|
58
64
|
const AutoGenHeader = "// Auto Generated by config2ts, DO NOT EDIT\n\n";
|
|
59
65
|
exports.Convert = {
|
|
60
66
|
ini: function (str, moduleName) {
|
|
@@ -77,7 +83,7 @@ function csv2ts(csvString, moduleName) {
|
|
|
77
83
|
if (i === 0) {
|
|
78
84
|
convert = d;
|
|
79
85
|
for (const k in convert) {
|
|
80
|
-
if (
|
|
86
|
+
if (parseRef(convert[k])) {
|
|
81
87
|
refFields.push(k);
|
|
82
88
|
}
|
|
83
89
|
if (parseRefEnum(convert[k])) {
|
|
@@ -107,8 +113,16 @@ function csv2ts(csvString, moduleName) {
|
|
|
107
113
|
else if (convert[k] === "Number[]") {
|
|
108
114
|
d[k] = splitStringArray(d[k]).map((val) => Number(val));
|
|
109
115
|
}
|
|
110
|
-
else if (
|
|
111
|
-
|
|
116
|
+
else if (parseRef(convert[k])) {
|
|
117
|
+
const ref = parseRef(convert[k]);
|
|
118
|
+
if (ref.isArray) {
|
|
119
|
+
const ids = splitStringArray(d[k]);
|
|
120
|
+
d[k] = ids;
|
|
121
|
+
if (ids.includes("")) {
|
|
122
|
+
console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" ref array contains an empty id`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (d[k] === "") {
|
|
112
126
|
console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" ref value is empty`);
|
|
113
127
|
}
|
|
114
128
|
}
|
|
@@ -123,6 +137,28 @@ function csv2ts(csvString, moduleName) {
|
|
|
123
137
|
}
|
|
124
138
|
}
|
|
125
139
|
}
|
|
140
|
+
else if (parseTemplate(convert[k])) {
|
|
141
|
+
const template = parseTemplate(convert[k]);
|
|
142
|
+
if (template.isArray) {
|
|
143
|
+
const entries = d[k] === "" ? [] : d[k].split(TemplateEntrySeparator).map((part) => parseTemplateEntry(part));
|
|
144
|
+
for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) {
|
|
145
|
+
if (entries[entryIndex].baseId === "") {
|
|
146
|
+
console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" template array entry ${entryIndex + 1} has an empty base id`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
d[k] = entries;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
if (d[k] === "") {
|
|
153
|
+
console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" template value is empty`);
|
|
154
|
+
}
|
|
155
|
+
const entry = parseTemplateEntry(d[k]);
|
|
156
|
+
if (d[k] !== "" && entry.baseId === "") {
|
|
157
|
+
console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" template base id is empty`);
|
|
158
|
+
}
|
|
159
|
+
d[k] = entry;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
126
162
|
else {
|
|
127
163
|
d[k] = String(d[k]);
|
|
128
164
|
}
|
|
@@ -208,16 +244,21 @@ function csv2ts(csvString, moduleName) {
|
|
|
208
244
|
else if (convert[field] == ObjectArrayString) {
|
|
209
245
|
fieldType = field + "[]";
|
|
210
246
|
}
|
|
211
|
-
else if (
|
|
212
|
-
const
|
|
213
|
-
const refModuleName = changeCase.pascalCase(refModule);
|
|
214
|
-
fieldType = `${refModuleName}.Record`;
|
|
247
|
+
else if (parseRef(convert[field])) {
|
|
248
|
+
const ref = parseRef(convert[field]);
|
|
249
|
+
const refModuleName = changeCase.pascalCase(ref.refModule);
|
|
250
|
+
fieldType = ref.isArray ? `${refModuleName}.Record[]` : `${refModuleName}.Record`;
|
|
215
251
|
}
|
|
216
252
|
else if (parseRefEnum(convert[field])) {
|
|
217
253
|
const refEnum = parseRefEnum(convert[field]);
|
|
218
254
|
const refModuleName = changeCase.pascalCase(refEnum.refModule);
|
|
219
255
|
fieldType = refEnum.isArray ? `${refModuleName}.${refEnum.refField}[]` : `${refModuleName}.${refEnum.refField}`;
|
|
220
256
|
}
|
|
257
|
+
else if (parseTemplate(convert[field])) {
|
|
258
|
+
const template = parseTemplate(convert[field]);
|
|
259
|
+
const refModuleName = changeCase.pascalCase(template.refModule);
|
|
260
|
+
fieldType = template.isArray ? `${refModuleName}.Record[]` : `${refModuleName}.Record`;
|
|
261
|
+
}
|
|
221
262
|
else {
|
|
222
263
|
fieldType = convert[field].toLowerCase();
|
|
223
264
|
}
|
|
@@ -241,10 +282,27 @@ function csv2ts(csvString, moduleName) {
|
|
|
241
282
|
template += " {\n";
|
|
242
283
|
for (const field in convert) {
|
|
243
284
|
let value = row[field];
|
|
244
|
-
if (
|
|
245
|
-
const
|
|
246
|
-
const refModuleName = changeCase.pascalCase(refModule);
|
|
247
|
-
|
|
285
|
+
if (parseRef(convert[field])) {
|
|
286
|
+
const ref = parseRef(convert[field]);
|
|
287
|
+
const refModuleName = changeCase.pascalCase(ref.refModule);
|
|
288
|
+
if (ref.isArray) {
|
|
289
|
+
const refs = value.map((id) => `${refModuleName}.Map[${JSON.stringify(id)}]`).join(",");
|
|
290
|
+
template += ` ${serializeField(field)}: [${refs}],\n`;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
template += ` ${serializeField(field)}: ${refModuleName}.Map[${JSON.stringify(value)}],\n`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else if (parseTemplate(convert[field])) {
|
|
297
|
+
const templateType = parseTemplate(convert[field]);
|
|
298
|
+
const refModuleName = changeCase.pascalCase(templateType.refModule);
|
|
299
|
+
if (templateType.isArray) {
|
|
300
|
+
const entries = value.map((entry) => serializeTemplateEntry(entry, refModuleName)).join(",");
|
|
301
|
+
template += ` ${serializeField(field)}: [${entries}],\n`;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
template += ` ${serializeField(field)}: ${serializeTemplateEntry(value, refModuleName)},\n`;
|
|
305
|
+
}
|
|
248
306
|
}
|
|
249
307
|
else {
|
|
250
308
|
template += ` ${serializeField(field)}: ${json5.stringify(value)},\n`;
|
|
@@ -344,17 +402,40 @@ function isKnownType(typeStr) {
|
|
|
344
402
|
if (knownTypes.includes(typeStr)) {
|
|
345
403
|
return true;
|
|
346
404
|
}
|
|
347
|
-
if (
|
|
405
|
+
if (parseRef(typeStr)) {
|
|
348
406
|
return true;
|
|
349
407
|
}
|
|
350
408
|
if (parseRefEnum(typeStr)) {
|
|
351
409
|
return true;
|
|
352
410
|
}
|
|
411
|
+
if (parseTemplate(typeStr)) {
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
353
414
|
if (typeof global[typeStr] === "function") {
|
|
354
415
|
return true;
|
|
355
416
|
}
|
|
356
417
|
return false;
|
|
357
418
|
}
|
|
419
|
+
// Parse Ref[module] (single) and Ref[module][] (array of row references).
|
|
420
|
+
// Note: "RefEnum[...]" starts with "RefE", so it never collides with "Ref[".
|
|
421
|
+
function parseRef(typeStr) {
|
|
422
|
+
if (!typeStr.startsWith(RefPrefix)) {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
let isArray = false;
|
|
426
|
+
let inner = typeStr;
|
|
427
|
+
if (typeStr.endsWith(RefEnumArraySuffix)) {
|
|
428
|
+
isArray = true;
|
|
429
|
+
inner = typeStr.slice(0, -RefEnumArraySuffix.length) + RefSuffix;
|
|
430
|
+
}
|
|
431
|
+
if (inner.startsWith(RefPrefix) && inner.endsWith(RefSuffix)) {
|
|
432
|
+
const refModule = inner.slice(RefPrefix.length, -RefSuffix.length);
|
|
433
|
+
if (refModule !== "") {
|
|
434
|
+
return { refModule, isArray };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
358
439
|
function parseRefEnum(typeStr) {
|
|
359
440
|
if (typeStr.startsWith(RefEnumPrefix)) {
|
|
360
441
|
let isArray = false;
|
|
@@ -375,6 +456,56 @@ function parseRefEnum(typeStr) {
|
|
|
375
456
|
}
|
|
376
457
|
return null;
|
|
377
458
|
}
|
|
459
|
+
// Parse Template[module] (single) and Template[module][] (array of templated
|
|
460
|
+
// row references). "Template[" starts with 'T', so it never collides with the
|
|
461
|
+
// "Ref[" / "RefEnum[" prefixes.
|
|
462
|
+
function parseTemplate(typeStr) {
|
|
463
|
+
if (!typeStr.startsWith(TemplatePrefix)) {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
let isArray = false;
|
|
467
|
+
let inner = typeStr;
|
|
468
|
+
if (typeStr.endsWith(RefEnumArraySuffix)) {
|
|
469
|
+
isArray = true;
|
|
470
|
+
inner = typeStr.slice(0, -RefEnumArraySuffix.length) + RefSuffix;
|
|
471
|
+
}
|
|
472
|
+
if (inner.startsWith(TemplatePrefix) && inner.endsWith(RefSuffix)) {
|
|
473
|
+
const refModule = inner.slice(TemplatePrefix.length, -RefSuffix.length);
|
|
474
|
+
if (refModule !== "") {
|
|
475
|
+
return { refModule, isArray };
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
// Parse one Template cell entry: "<baseId>|<key>:<value>,<key>:<value>".
|
|
481
|
+
// The part before the first '|' is the base row id; the part after it reuses
|
|
482
|
+
// the Object rule for the override pairs. No '|' means zero overrides, which
|
|
483
|
+
// is equivalent to a plain Ref.
|
|
484
|
+
function parseTemplateEntry(str) {
|
|
485
|
+
const separatorIndex = str.indexOf(TemplateOverrideSeparator);
|
|
486
|
+
if (separatorIndex < 0) {
|
|
487
|
+
return { baseId: str.trim(), overrides: {} };
|
|
488
|
+
}
|
|
489
|
+
const baseId = str.slice(0, separatorIndex).trim();
|
|
490
|
+
const overrides = parseObject(str.slice(separatorIndex + 1));
|
|
491
|
+
return { baseId, overrides };
|
|
492
|
+
}
|
|
493
|
+
// Serialize one parsed Template entry to runtime code. Entries without
|
|
494
|
+
// overrides emit a plain ref (same as Ref: `Module.Map["id"]`); entries with
|
|
495
|
+
// overrides spread the base record and apply the overrides on top. Because
|
|
496
|
+
// the spread result is contextually typed as the target Record, a typo'd
|
|
497
|
+
// override key or an incompatible value type fails at tsc compile time.
|
|
498
|
+
function serializeTemplateEntry(entry, refModuleName) {
|
|
499
|
+
const baseRef = `${refModuleName}.Map[${JSON.stringify(entry.baseId)}]`;
|
|
500
|
+
const overrideKeys = Object.keys(entry.overrides);
|
|
501
|
+
if (overrideKeys.length === 0) {
|
|
502
|
+
return baseRef;
|
|
503
|
+
}
|
|
504
|
+
const overrideStr = overrideKeys
|
|
505
|
+
.map((key) => `${serializeField(key)}:${json5.stringify(entry.overrides[key])}`)
|
|
506
|
+
.join(",");
|
|
507
|
+
return `{...${baseRef},${overrideStr}}`;
|
|
508
|
+
}
|
|
378
509
|
function GetFileExt(filePath) {
|
|
379
510
|
const pathObject = path.parse(filePath);
|
|
380
511
|
return pathObject.ext.slice(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "config2ts",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.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",
|