config2ts 2.5.2 → 2.5.5

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
@@ -27,6 +27,7 @@ config2ts -d config -o dist -n csv.ts -a public
27
27
  | RefEnum | namespace.type |
28
28
  | RefEnum[] | namespace.type[] |
29
29
  | Object | type (Record) |
30
+ | Object[] | type[] (Record[]) |
30
31
 
31
32
  * `Number` support Infinity and NaN
32
33
  * `Enum` support empty string type
@@ -34,6 +35,8 @@ config2ts -d config -o dist -n csv.ts -a public
34
35
  * `EnumIndex` will generate index type, and will use `Enum` type to generate interface
35
36
  * `Ref` / `RefEnum` reference another csv's Record/type by id
36
37
  * `Object` parse `key:value,key:value` format, auto-infer value types (number/boolean/string), generates a dedicated `type` that merges all keys
38
+ * `Object[]` parse `key:value,key:value;key:value,key:value` format (`;` separates objects, `,` separates key-value pairs), empty value generates `[]`, generates a dedicated element `type` with the field typed as `type[]`
39
+ * unrecognized field types fall back to `string` and print a warning (check for typos)
37
40
 
38
41
  ## assets2ts
39
42
 
@@ -41,9 +44,9 @@ Scan assets directory and generate an `assets.ts` index file with nested resourc
41
44
 
42
45
  - `-a, --assets <path>` set assets resource directory. default: `public`
43
46
  - Output file: `assets.ts` (alongside the merged config file)
44
- - Directory/filenames are converted to camelCase keys
47
+ - 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
45
48
  - `type` field uses file extension (lowercase), e.g. `'png'`, `'mp3'`, `'svg'`
46
- - Directories containing 2+ files of the same extension get a dedicated type (e.g. `PngAsset`, `Mp3Asset`) and `as Record<string, XxxAsset>` annotation
49
+ - Directories containing 2+ files of the same extension get a dedicated type (e.g. `PngAsset`, `Mp3Asset`) and `satisfies Record<string, XxxAsset>` annotation
47
50
  - Directories with ≤1 file or mixed extensions do not get a type annotation
48
51
  - Nested directories are supported (e.g. `public/sub/image/`)
49
52
 
@@ -59,14 +62,14 @@ export type SvgAsset = { path: string; type: 'svg' };
59
62
  export const ASSETS = {
60
63
  public: {
61
64
  image: {
62
- adjustHorizontal: {path:'public/image/adjust-horizontal.png',type:'png'},
63
- direction: {path:'public/image/direction.png',type:'png'}
65
+ 'adjust-horizontal': {path:'public/image/adjust-horizontal.png',type:'png'},
66
+ Direction: {path:'public/image/Direction.png',type:'png'}
64
67
  } satisfies Record<string, PngAsset>,
65
68
  music: {
66
69
  effect1: {path:'public/music/effect1.mp3',type:'mp3'},
67
70
  effect2: {path:'public/music/effect2.mp3',type:'mp3'}
68
71
  } satisfies Record<string, Mp3Asset>,
69
- singleFile: {
72
+ 'single-file': {
70
73
  effect1: {path:'public/single-file/effect1.mp3',type:'mp3'}
71
74
  }
72
75
  }
@@ -78,9 +81,12 @@ export const ASSETS = {
78
81
  ```typescript
79
82
  import { ASSETS } from "./assets";
80
83
 
81
- const meta = ASSETS.public.image.adjustHorizontal;
82
- // meta.path → 'public/image/adjust-horizontal.png'
84
+ const meta = ASSETS.public.image.Direction;
85
+ // meta.path → 'public/image/Direction.png'
83
86
  // meta.type → 'png'
87
+
88
+ const adjustMeta = ASSETS.public.image['adjust-horizontal'];
89
+ // adjustMeta.path → 'public/image/adjust-horizontal.png'
84
90
  ```
85
91
 
86
92
  ## Options
package/lib/config2ts.js CHANGED
@@ -50,6 +50,7 @@ const EnumIndexStr = "EnumIndex";
50
50
  const EnumArrayString = "Enum[]";
51
51
  const IndexStr = "Index";
52
52
  const ObjectStr = "Object";
53
+ const ObjectArrayString = "Object[]";
53
54
  const RefPrefix = "Ref[";
54
55
  const RefSuffix = "]";
55
56
  const RefEnumPrefix = "RefEnum[";
@@ -82,6 +83,9 @@ function csv2ts(csvString, moduleName) {
82
83
  if (parseRefEnum(convert[k])) {
83
84
  refEnumFields.push(k);
84
85
  }
86
+ if (!isKnownType(convert[k])) {
87
+ console.warn(`[config2ts] warning: ${moduleName} field "${k}" has unrecognized type "${convert[k]}", values will be treated as string`);
88
+ }
85
89
  }
86
90
  return null;
87
91
  }
@@ -90,6 +94,9 @@ function csv2ts(csvString, moduleName) {
90
94
  if (convert[k] === ObjectStr) {
91
95
  d[k] = parseObject(d[k]);
92
96
  }
97
+ else if (convert[k] === ObjectArrayString) {
98
+ d[k] = parseObjectArray(d[k]);
99
+ }
93
100
  else if (typeof global[convert[k]] === "function") {
94
101
  d[k] = global[convert[k]](d[k]);
95
102
  }
@@ -172,15 +179,18 @@ function csv2ts(csvString, moduleName) {
172
179
  template += ` export type ${field} = ${enumValueStrings.join(" | ")};\n`;
173
180
  template += ` export const ${field}List: ${field}[] = [${enumValueStrings.join(", ")}];\n\n`;
174
181
  }
175
- else if (convert[field] == ObjectStr) {
182
+ else if (convert[field] == ObjectStr || convert[field] == ObjectArrayString) {
176
183
  const keyTypes = {};
184
+ const isArray = convert[field] == ObjectArrayString;
177
185
  for (const row of result) {
178
- const objValue = row[field];
179
- for (const key in objValue) {
180
- if (!keyTypes[key]) {
181
- keyTypes[key] = new Set();
186
+ const objValues = isArray ? row[field] : [row[field]];
187
+ for (const objValue of objValues) {
188
+ for (const key in objValue) {
189
+ if (!keyTypes[key]) {
190
+ keyTypes[key] = new Set();
191
+ }
192
+ keyTypes[key].add(getTypeName(objValue[key]));
182
193
  }
183
- keyTypes[key].add(getTypeName(objValue[key]));
184
194
  }
185
195
  }
186
196
  template += ` export type ${field} = {\n`;
@@ -212,6 +222,9 @@ function csv2ts(csvString, moduleName) {
212
222
  else if (convert[field] == ObjectStr) {
213
223
  fieldType = field;
214
224
  }
225
+ else if (convert[field] == ObjectArrayString) {
226
+ fieldType = field + "[]";
227
+ }
215
228
  else if (convert[field].startsWith(RefPrefix) && convert[field].endsWith(RefSuffix)) {
216
229
  const refModule = convert[field].slice(RefPrefix.length, -RefSuffix.length);
217
230
  const refModuleName = changeCase.pascalCase(refModule);
@@ -291,6 +304,13 @@ function parseObject(str) {
291
304
  }
292
305
  return result;
293
306
  }
307
+ function parseObjectArray(str) {
308
+ const trimmed = str.trim();
309
+ if (trimmed === "") {
310
+ return [];
311
+ }
312
+ return trimmed.split(";").map((part) => parseObject(part));
313
+ }
294
314
  function parseObjectValue(value) {
295
315
  if (value === "true")
296
316
  return true;
@@ -311,6 +331,25 @@ function getTypeName(value) {
311
331
  return "boolean";
312
332
  return "string";
313
333
  }
334
+ function isKnownType(typeStr) {
335
+ if (typeStr === "") {
336
+ return true;
337
+ }
338
+ const knownTypes = [EnumStr, EnumIndexStr, EnumArrayString, IndexStr, ObjectStr, ObjectArrayString, "String[]", "Number[]"];
339
+ if (knownTypes.includes(typeStr)) {
340
+ return true;
341
+ }
342
+ if (typeStr.startsWith(RefPrefix) && typeStr.endsWith(RefSuffix)) {
343
+ return true;
344
+ }
345
+ if (parseRefEnum(typeStr)) {
346
+ return true;
347
+ }
348
+ if (typeof global[typeStr] === "function") {
349
+ return true;
350
+ }
351
+ return false;
352
+ }
314
353
  function parseRefEnum(typeStr) {
315
354
  if (typeStr.startsWith(RefEnumPrefix)) {
316
355
  let isArray = false;
@@ -342,7 +381,7 @@ function scanDir(dir, basePath) {
342
381
  for (const entry of entries) {
343
382
  const fullPath = path.join(dir, entry.name);
344
383
  if (entry.isDirectory()) {
345
- const key = changeCase.camelCase(entry.name);
384
+ const key = entry.name;
346
385
  const childNode = scanDir(fullPath, path.posix.join(basePath, entry.name));
347
386
  if (childNode !== null) {
348
387
  node[key] = childNode;
@@ -352,7 +391,7 @@ function scanDir(dir, basePath) {
352
391
  else if (entry.isFile()) {
353
392
  const parsed = path.parse(entry.name);
354
393
  const ext = parsed.ext.slice(1).toLowerCase();
355
- const key = changeCase.camelCase(parsed.name);
394
+ const key = parsed.name;
356
395
  const relPath = path.posix.join(basePath, entry.name);
357
396
  node[key] = { path: relPath, type: ext };
358
397
  hasContent = true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "config2ts",
3
- "version": "2.5.2",
4
- "description": "convert config to ts file.",
3
+ "version": "2.5.5",
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",
7
7
  "bin": {