config2ts 2.6.1 → 2.6.2

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 CHANGED
@@ -39,7 +39,8 @@ config2ts -d config -o dist -n csv.ts -a public
39
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 (same file-ordering rule as `Ref`)
40
40
  * `Object` parse `key:value,key:value` format, auto-infer value types (number/boolean/string), generates a dedicated `type` that merges all keys
41
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[]`
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
42
+ * 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
43
+ * Line breaks inside a cell (quote-wrapped in CSV) are preserved for `String` fields (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
43
44
  * unrecognized field types fall back to `string` and print a warning (check for typos)
44
45
 
45
46
  ## assets2ts
package/lib/config2ts.js CHANGED
@@ -57,10 +57,9 @@ const RefEnumPrefix = "RefEnum[";
57
57
  const RefEnumArraySuffix = "][]";
58
58
  const TemplatePrefix = "Template[";
59
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[]).
60
+ // Object-style override pairs. Entries in the array form (and elements of
61
+ // Object[]/primitive arrays) are separated by ';' and line feeds.
62
62
  const TemplateOverrideSeparator = "|";
63
- const TemplateEntrySeparator = ";";
64
63
  const AutoGenHeader = "// Auto Generated by config2ts, DO NOT EDIT\n\n";
65
64
  exports.Convert = {
66
65
  ini: function (str, moduleName) {
@@ -98,6 +97,15 @@ function csv2ts(csvString, moduleName) {
98
97
  else {
99
98
  for (const k in d) {
100
99
  d[k] = normalizeCell(d[k]);
100
+ // Line feeds are preserved on purpose for free-form String fields
101
+ // (multi-line text such as i18n descriptions), and structural types
102
+ // treat them as entry/pair separators (one element or key:value pair
103
+ // per line). Scalar types (Number/Boolean/Enum/single Ref) have no
104
+ // separator semantics, so a line break there is meaningless — warn
105
+ // and let the author remove it from the cell.
106
+ if (convert[k] !== "String" && convert[k] !== "" && !isStructuralType(convert[k]) && d[k].includes("\n")) {
107
+ console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" of type "${convert[k]}" contains a line break inside the cell; line breaks are only supported in String fields and structural (array/Object/Template) fields, please remove it`);
108
+ }
101
109
  if (convert[k] === ObjectStr) {
102
110
  d[k] = parseObject(d[k]);
103
111
  }
@@ -141,11 +149,17 @@ function csv2ts(csvString, moduleName) {
141
149
  const template = parseTemplate(convert[k]);
142
150
  const templateContext = { moduleName, field: k, row: i };
143
151
  if (template.isArray) {
144
- const entries = d[k] === "" ? [] : d[k].split(TemplateEntrySeparator).map((part) => parseTemplateEntry(part, templateContext));
152
+ // Semicolons and line feeds both separate template entries (one
153
+ // entry per line works in multi-line cells); override pairs inside
154
+ // an entry stay on the same line.
155
+ const entries = d[k] === "" ? [] : d[k].split(/[;\n]/).map((part) => parseTemplateEntry(part, templateContext));
145
156
  for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) {
146
157
  if (entries[entryIndex].baseId === "") {
147
158
  console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" template array entry ${entryIndex + 1} has an empty base id`);
148
159
  }
160
+ else if (entries[entryIndex].baseId.includes(":")) {
161
+ console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" template array entry ${entryIndex + 1} "${entries[entryIndex].baseId}" looks like a key:value pair; each line/semicolon slot must be a full "<baseId>|<overrides>" entry, keep override pairs on the same line`);
162
+ }
149
163
  }
150
164
  d[k] = entries;
151
165
  }
@@ -335,7 +349,9 @@ function parseObject(str) {
335
349
  if (trimmed === "") {
336
350
  return result;
337
351
  }
338
- const pairs = trimmed.split(",");
352
+ // Commas and line feeds both separate key:value pairs, so one pair per
353
+ // line works in multi-line cells (e.g. "num:1\nstr:a").
354
+ const pairs = trimmed.split(/[,\n]/);
339
355
  for (const pair of pairs) {
340
356
  const colonIndex = pair.indexOf(":");
341
357
  if (colonIndex > 0) {
@@ -346,10 +362,13 @@ function parseObject(str) {
346
362
  }
347
363
  return result;
348
364
  }
349
- // Normalize a raw cell value: drop line-break artifacts (e.g. Excel/CSV \r\n)
350
- // and surrounding whitespace so all downstream parsing sees clean text.
365
+ // Normalize a raw cell value: convert CRLF/CR line breaks to LF and trim
366
+ // surrounding whitespace. Line feeds inside a quoted cell are intentional
367
+ // (multi-line cells commonly use LF as their entry separator, e.g. one effect
368
+ // per line), so they must be preserved — only the Windows/old-Mac line-break
369
+ // variants are unified to LF.
351
370
  function normalizeCell(str) {
352
- return String(str).replace(/\r\n?|\n/g, "").trim();
371
+ return String(str).replace(/\r\n?/g, "\n").trim();
353
372
  }
354
373
  // Split a comma-separated primitive-array cell: n commas make n+1 slots and every
355
374
  // slot is kept (including an interior or trailing empty one), so parallel arrays
@@ -362,18 +381,20 @@ function splitStringArray(str) {
362
381
  if (str === "") {
363
382
  return [];
364
383
  }
365
- return str.split(",").map((v) => v.trim());
384
+ // Commas and line feeds both separate elements, so one value per line
385
+ // works in multi-line cells (e.g. "a\nb" -> ['a','b']).
386
+ return str.split(/[,\n]/).map((v) => v.trim());
366
387
  }
367
- // Object[] separates objects with semicolons (commas are reserved for the
368
- // key:value pairs inside an object, same syntax as the Object type); each
369
- // semicolon slot is parsed by the same object rule, so stray empty pairs within
370
- // an object (e.g. "num:1,,str:a") are ignored while an empty object slot between
371
- // semicolons (e.g. "a:1;;b:2") becomes an empty object placeholder.
388
+ // Object[] separates objects with semicolons or line feeds (commas are reserved
389
+ // for the key:value pairs inside an object, same syntax as the Object type); each
390
+ // slot is parsed by the same object rule, so stray empty pairs within an object
391
+ // (e.g. "num:1,,str:a") are ignored while an empty object slot between separators
392
+ // (e.g. "a:1;;b:2" or a blank line) becomes an empty object placeholder.
372
393
  function parseObjectArray(str) {
373
394
  if (str === "") {
374
395
  return [];
375
396
  }
376
- return str.split(";").map((part) => parseObject(part));
397
+ return str.split(/[;\n]/).map((part) => parseObject(part));
377
398
  }
378
399
  function parseObjectValue(value) {
379
400
  if (value === "true")
@@ -417,6 +438,27 @@ function isKnownType(typeStr) {
417
438
  }
418
439
  return false;
419
440
  }
441
+ // Structural types split cell content on separators (commas/semicolons/pipes),
442
+ // so a line feed can act as the equivalent separator and one entry per line is
443
+ // valid in multi-line cells. Scalar types (Number/Boolean/Enum/single Ref) have
444
+ // no separator semantics, so a line feed in those cells is an error.
445
+ function isStructuralType(typeStr) {
446
+ if (typeStr === ObjectStr || typeStr === ObjectArrayString || typeStr === "String[]" || typeStr === "Number[]" || typeStr === EnumArrayString) {
447
+ return true;
448
+ }
449
+ const ref = parseRef(typeStr);
450
+ if (ref && ref.isArray) {
451
+ return true;
452
+ }
453
+ const refEnum = parseRefEnum(typeStr);
454
+ if (refEnum && refEnum.isArray) {
455
+ return true;
456
+ }
457
+ if (parseTemplate(typeStr)) {
458
+ return true;
459
+ }
460
+ return false;
461
+ }
420
462
  // Parse Ref[module] (single) and Ref[module][] (array of row references).
421
463
  // Note: "RefEnum[...]" starts with "RefE", so it never collides with "Ref[".
422
464
  function parseRef(typeStr) {
@@ -494,9 +536,11 @@ function parseTemplateEntry(str, context) {
494
536
  const overrides = parseTemplateOverrides(str.slice(separatorIndex + 1), context);
495
537
  return { baseId, overrides };
496
538
  }
497
- // Split a string on a separator while ignoring separators inside [] / {}
498
- // brackets, so commas inside composite values stay intact.
499
- function splitBracketAware(str, separator) {
539
+ // Split a string on any character in `separators` while ignoring separators
540
+ // inside [] / {} brackets, so commas inside composite values stay intact.
541
+ // Line feeds are accepted as separators too, so one override pair / array
542
+ // element per line works in multi-line cells.
543
+ function splitBracketAware(str, separators) {
500
544
  const parts = [];
501
545
  let depth = 0;
502
546
  let start = 0;
@@ -509,7 +553,7 @@ function splitBracketAware(str, separator) {
509
553
  if (depth > 0)
510
554
  depth--;
511
555
  }
512
- else if (ch === separator && depth === 0) {
556
+ else if (separators.includes(ch) && depth === 0) {
513
557
  parts.push(str.slice(start, i));
514
558
  start = i + 1;
515
559
  }
@@ -523,7 +567,7 @@ function splitBracketAware(str, separator) {
523
567
  // silently dropped.
524
568
  function parseTemplateOverrides(str, context) {
525
569
  const overrides = {};
526
- for (const pair of splitBracketAware(str, ",")) {
570
+ for (const pair of splitBracketAware(str, ",\n")) {
527
571
  const colonIndex = pair.indexOf(":");
528
572
  const trimmedPair = pair.trim();
529
573
  if (colonIndex <= 0) {
@@ -548,7 +592,7 @@ function parseOverrideValue(value) {
548
592
  if (inner === "") {
549
593
  return [];
550
594
  }
551
- return splitBracketAware(inner, ",").map((element) => {
595
+ return splitBracketAware(inner, ",\n").map((element) => {
552
596
  const elementTrimmed = element.trim();
553
597
  if (elementTrimmed.startsWith("{") && elementTrimmed.endsWith("}")) {
554
598
  return parseObject(elementTrimmed.slice(1, -1));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "config2ts",
3
- "version": "2.6.1",
3
+ "version": "2.6.2",
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",