config2ts 2.4.0 → 2.5.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 +77 -16
- package/lib/config2ts.d.ts +2 -1
- package/lib/config2ts.js +129 -2
- package/lib/main.js +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,28 +8,82 @@ run command: `npm install -g config2ts`
|
|
|
8
8
|
|
|
9
9
|
# how to use
|
|
10
10
|
|
|
11
|
-
config2ts -d config -o dist -
|
|
11
|
+
config2ts -d config -o dist -n csv.ts -a public
|
|
12
12
|
|
|
13
13
|
# support type
|
|
14
14
|
|
|
15
|
-
| csv field
|
|
16
|
-
|
|
|
17
|
-
| Index
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
| Boolean
|
|
21
|
-
|
|
|
22
|
-
| EnumIndex
|
|
23
|
-
| String[]
|
|
24
|
-
| Number[]
|
|
25
|
-
| Enum[]
|
|
15
|
+
| csv field | typescript type |
|
|
16
|
+
| :--------: | :---------------: |
|
|
17
|
+
| Index | string |
|
|
18
|
+
| String | string |
|
|
19
|
+
| Number | number |
|
|
20
|
+
| Boolean | boolean |
|
|
21
|
+
| Enum | type |
|
|
22
|
+
| EnumIndex | type |
|
|
23
|
+
| String[] | string[] |
|
|
24
|
+
| Number[] | number[] |
|
|
25
|
+
| Enum[] | type[] |
|
|
26
|
+
| Ref | namespace.Record |
|
|
27
|
+
| RefEnum | namespace.type |
|
|
28
|
+
| RefEnum[] | namespace.type[] |
|
|
29
|
+
| Object | type (Record) |
|
|
26
30
|
|
|
27
31
|
* `Number` support Infinity and NaN
|
|
28
32
|
* `Enum` support empty string type
|
|
29
33
|
* `Enum[]` do not include empty string type
|
|
30
34
|
* `EnumIndex` will generate index type, and will use `Enum` type to generate interface
|
|
35
|
+
* `Ref` / `RefEnum` reference another csv's Record/type by id
|
|
36
|
+
* `Object` parse `key:value,key:value` format, auto-infer value types (number/boolean/string), generates a dedicated `type` that merges all keys
|
|
31
37
|
|
|
32
|
-
##
|
|
38
|
+
## assets2ts
|
|
39
|
+
|
|
40
|
+
Scan assets directory and generate an `assets.ts` index file with nested resource tree.
|
|
41
|
+
|
|
42
|
+
- `-a, --assets <path>` set assets resource directory. default: `public`
|
|
43
|
+
- Output file: `assets.ts` (alongside the merged config file)
|
|
44
|
+
- Directory/filenames are converted to camelCase keys
|
|
45
|
+
- `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
|
|
47
|
+
- Directories with ≤1 file or mixed extensions do not get a type annotation
|
|
48
|
+
- Nested directories are supported (e.g. `public/sub/image/`)
|
|
49
|
+
|
|
50
|
+
### generated example
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Auto Generated by config2ts, DO NOT EDIT
|
|
54
|
+
|
|
55
|
+
export type Mp3Asset = { path: string; type: 'mp3' };
|
|
56
|
+
export type PngAsset = { path: string; type: 'png' };
|
|
57
|
+
export type SvgAsset = { path: string; type: 'svg' };
|
|
58
|
+
|
|
59
|
+
export const RES = {
|
|
60
|
+
public: {
|
|
61
|
+
image: {
|
|
62
|
+
adjustHorizontal: {path:'public/image/adjust-horizontal.png',type:'png'},
|
|
63
|
+
direction: {path:'public/image/direction.png',type:'png'}
|
|
64
|
+
} as Record<string, PngAsset>,
|
|
65
|
+
music: {
|
|
66
|
+
effect1: {path:'public/music/effect1.mp3',type:'mp3'},
|
|
67
|
+
effect2: {path:'public/music/effect2.mp3',type:'mp3'}
|
|
68
|
+
} as Record<string, Mp3Asset>,
|
|
69
|
+
singleFile: {
|
|
70
|
+
effect1: {path:'public/single-file/effect1.mp3',type:'mp3'}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### usage
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { RES } from "./assets";
|
|
80
|
+
|
|
81
|
+
const meta = RES.public.image.adjustHorizontal;
|
|
82
|
+
// meta.path → 'public/image/adjust-horizontal.png'
|
|
83
|
+
// meta.type → 'png'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Options
|
|
33
87
|
|
|
34
88
|
```
|
|
35
89
|
Options:
|
|
@@ -38,7 +92,14 @@ config2ts -d config -o dist -m csv.ts
|
|
|
38
92
|
-V, --version output the version number
|
|
39
93
|
-d, --dir <path> set convert path. default: ./
|
|
40
94
|
-o, --outDir <path> set outDir path. default: ./
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-m, --merge <name> merge all to one ts file. default: csv.ts
|
|
95
|
+
-n, --name <name> output file name. default: csv.ts
|
|
96
|
+
-a, --assets <path> set assets resource directory for asset index. default: public
|
|
44
97
|
```
|
|
98
|
+
|
|
99
|
+
## supported file formats
|
|
100
|
+
|
|
101
|
+
| format | extension |
|
|
102
|
+
| :----: | :-------: |
|
|
103
|
+
| csv | .csv |
|
|
104
|
+
| ini | .ini |
|
|
105
|
+
| toml | .toml |
|
package/lib/config2ts.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
type ConvertHandler = (str: string, moduleName: string) => string;
|
|
2
2
|
export declare const Convert: Record<string, ConvertHandler>;
|
|
3
|
+
export declare function assets2ts(assetsDir: string): string;
|
|
3
4
|
export declare function GetTsString(filePath: string): string;
|
|
4
5
|
export declare function GetTsStringFromFileList(fileList: string[]): string;
|
|
5
6
|
export declare function GetValidFileList(fileList: string[]): string[];
|
|
6
|
-
export declare function startConvert(dir: string, outDir: string, merge: string): void;
|
|
7
|
+
export declare function startConvert(dir: string, outDir: string, merge: string, assetsDir?: string): void;
|
|
7
8
|
export {};
|
package/lib/config2ts.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.Convert = void 0;
|
|
37
|
+
exports.assets2ts = assets2ts;
|
|
37
38
|
exports.GetTsString = GetTsString;
|
|
38
39
|
exports.GetTsStringFromFileList = GetTsStringFromFileList;
|
|
39
40
|
exports.GetValidFileList = GetValidFileList;
|
|
@@ -53,6 +54,7 @@ const RefPrefix = "Ref[";
|
|
|
53
54
|
const RefSuffix = "]";
|
|
54
55
|
const RefEnumPrefix = "RefEnum[";
|
|
55
56
|
const RefEnumArraySuffix = "][]";
|
|
57
|
+
const AutoGenHeader = "// Auto Generated by config2ts, DO NOT EDIT\n\n";
|
|
56
58
|
exports.Convert = {
|
|
57
59
|
ini: function (str, moduleName) {
|
|
58
60
|
const obj = toml.parse(str);
|
|
@@ -333,6 +335,123 @@ function GetFileExt(filePath) {
|
|
|
333
335
|
const pathObject = path.parse(filePath);
|
|
334
336
|
return pathObject.ext.slice(1);
|
|
335
337
|
}
|
|
338
|
+
function scanDir(dir, basePath) {
|
|
339
|
+
const node = {};
|
|
340
|
+
let hasContent = false;
|
|
341
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
342
|
+
for (const entry of entries) {
|
|
343
|
+
const fullPath = path.join(dir, entry.name);
|
|
344
|
+
if (entry.isDirectory()) {
|
|
345
|
+
const key = changeCase.camelCase(entry.name);
|
|
346
|
+
const childNode = scanDir(fullPath, path.posix.join(basePath, entry.name));
|
|
347
|
+
if (childNode !== null) {
|
|
348
|
+
node[key] = childNode;
|
|
349
|
+
hasContent = true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else if (entry.isFile()) {
|
|
353
|
+
const parsed = path.parse(entry.name);
|
|
354
|
+
const ext = parsed.ext.slice(1).toLowerCase();
|
|
355
|
+
const key = changeCase.camelCase(parsed.name);
|
|
356
|
+
const relPath = path.posix.join(basePath, entry.name);
|
|
357
|
+
node[key] = { path: relPath, type: ext };
|
|
358
|
+
hasContent = true;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return hasContent ? node : null;
|
|
362
|
+
}
|
|
363
|
+
function isLeafNode(value) {
|
|
364
|
+
return "path" in value && "type" in value;
|
|
365
|
+
}
|
|
366
|
+
function getUniformType(node) {
|
|
367
|
+
const values = Object.values(node);
|
|
368
|
+
if (values.length <= 1)
|
|
369
|
+
return null;
|
|
370
|
+
let uniformType = null;
|
|
371
|
+
for (const v of values) {
|
|
372
|
+
if (!isLeafNode(v))
|
|
373
|
+
return null;
|
|
374
|
+
if (uniformType === null) {
|
|
375
|
+
uniformType = v.type;
|
|
376
|
+
}
|
|
377
|
+
else if (uniformType !== v.type) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return uniformType;
|
|
382
|
+
}
|
|
383
|
+
function collectUniformDirs(node, dirTypes, exts) {
|
|
384
|
+
const ut = getUniformType(node);
|
|
385
|
+
if (ut !== null) {
|
|
386
|
+
dirTypes.set(node, ut);
|
|
387
|
+
exts.add(ut);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
for (const v of Object.values(node)) {
|
|
391
|
+
if (!isLeafNode(v)) {
|
|
392
|
+
collectUniformDirs(v, dirTypes, exts);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function extToTypeName(ext) {
|
|
397
|
+
return ext.charAt(0).toUpperCase() + ext.slice(1) + "Asset";
|
|
398
|
+
}
|
|
399
|
+
function serializeAssetNode(node, indent, uniformDirs, extTypeMap) {
|
|
400
|
+
const entries = Object.entries(node);
|
|
401
|
+
if (entries.length === 0)
|
|
402
|
+
return `${indent}{}`;
|
|
403
|
+
const ut = uniformDirs.get(node);
|
|
404
|
+
const lines = [];
|
|
405
|
+
for (let i = 0; i < entries.length; i++) {
|
|
406
|
+
const [key, value] = entries[i];
|
|
407
|
+
const serializedKey = serializeField(key);
|
|
408
|
+
const comma = i < entries.length - 1 ? "," : "";
|
|
409
|
+
if (isLeafNode(value)) {
|
|
410
|
+
lines.push(`${indent}${serializedKey}: {path:${json5.stringify(value.path)},type:${json5.stringify(value.type)}}${comma}`);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
const childUt = uniformDirs.get(value);
|
|
414
|
+
const typeAnnotation = childUt ? ` as Record<string, ${extTypeMap.get(childUt)}>` : "";
|
|
415
|
+
lines.push(`${indent}${serializedKey}: {`);
|
|
416
|
+
lines.push(serializeAssetNode(value, indent + " ", uniformDirs, extTypeMap));
|
|
417
|
+
lines.push(`${indent}}${typeAnnotation}${comma}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return lines.join("\n");
|
|
421
|
+
}
|
|
422
|
+
function assets2ts(assetsDir) {
|
|
423
|
+
if (!fs.existsSync(assetsDir)) {
|
|
424
|
+
return "";
|
|
425
|
+
}
|
|
426
|
+
const dirName = path.basename(assetsDir);
|
|
427
|
+
const root = scanDir(assetsDir, dirName);
|
|
428
|
+
if (root === null) {
|
|
429
|
+
return "";
|
|
430
|
+
}
|
|
431
|
+
const uniformDirs = new Map();
|
|
432
|
+
const exts = new Set();
|
|
433
|
+
collectUniformDirs(root, uniformDirs, exts);
|
|
434
|
+
const extTypeMap = new Map();
|
|
435
|
+
const sortedExts = Array.from(exts).sort();
|
|
436
|
+
for (const ext of sortedExts) {
|
|
437
|
+
extTypeMap.set(ext, extToTypeName(ext));
|
|
438
|
+
}
|
|
439
|
+
let template = "";
|
|
440
|
+
for (const ext of sortedExts) {
|
|
441
|
+
const typeName = extTypeMap.get(ext);
|
|
442
|
+
template += `export type ${typeName} = { path: string; type: ${json5.stringify(ext)} };\n`;
|
|
443
|
+
}
|
|
444
|
+
if (sortedExts.length > 0)
|
|
445
|
+
template += "\n";
|
|
446
|
+
template += `export const RES = {\n`;
|
|
447
|
+
const rootUt = uniformDirs.get(root);
|
|
448
|
+
template += ` ${dirName}: {\n`;
|
|
449
|
+
template += serializeAssetNode(root, " ", uniformDirs, extTypeMap);
|
|
450
|
+
const rootAnnotation = rootUt ? ` as Record<string, ${extTypeMap.get(rootUt)}>` : "";
|
|
451
|
+
template += `\n }${rootAnnotation}\n`;
|
|
452
|
+
template += `};\n`;
|
|
453
|
+
return template;
|
|
454
|
+
}
|
|
336
455
|
function GetTsString(filePath) {
|
|
337
456
|
const pathObject = path.parse(filePath);
|
|
338
457
|
const handle = exports.Convert[GetFileExt(filePath)];
|
|
@@ -359,12 +478,20 @@ function GetValidFileList(fileList) {
|
|
|
359
478
|
return exports.Convert[GetFileExt(filePath)] != null;
|
|
360
479
|
});
|
|
361
480
|
}
|
|
362
|
-
function startConvert(dir, outDir, merge) {
|
|
481
|
+
function startConvert(dir, outDir, merge, assetsDir) {
|
|
363
482
|
const fileList = fs.readdirSync(dir);
|
|
364
483
|
const fullFileList = GetValidFileList(fileList).map(function (filename) {
|
|
365
484
|
return path.join(dir, filename);
|
|
366
485
|
});
|
|
367
486
|
const mergeFile = path.join(outDir, merge);
|
|
368
|
-
fs.writeFileSync(mergeFile, GetTsStringFromFileList(fullFileList), { encoding: "utf-8" });
|
|
487
|
+
fs.writeFileSync(mergeFile, AutoGenHeader + GetTsStringFromFileList(fullFileList), { encoding: "utf-8" });
|
|
369
488
|
console.log(`config2ts, ${fullFileList.length} config files, merge into: ${mergeFile}`);
|
|
489
|
+
if (assetsDir) {
|
|
490
|
+
const assetsOutput = assets2ts(assetsDir);
|
|
491
|
+
if (assetsOutput) {
|
|
492
|
+
const assetsFile = path.join(outDir, "assets.ts");
|
|
493
|
+
fs.writeFileSync(assetsFile, AutoGenHeader + assetsOutput, { encoding: "utf-8" });
|
|
494
|
+
console.log(`assets2ts, resource dir: ${assetsDir}, output: ${assetsFile}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
370
497
|
}
|
package/lib/main.js
CHANGED
|
@@ -45,6 +45,7 @@ program
|
|
|
45
45
|
.option("-n, --name <name>", "output file name", "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", "public")
|
|
48
49
|
.parse(process.argv);
|
|
49
50
|
const options = program.opts();
|
|
50
51
|
console.log(options);
|
|
@@ -55,4 +56,8 @@ console.log("outDir:", outDir);
|
|
|
55
56
|
if (!fs.existsSync(outDir)) {
|
|
56
57
|
fs.mkdirSync(outDir, { recursive: true });
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
+
const assetsDir = options.assets ? path.resolve(options.assets) : undefined;
|
|
60
|
+
if (assetsDir) {
|
|
61
|
+
console.log("assetsDir:", assetsDir);
|
|
62
|
+
}
|
|
63
|
+
(0, config2ts_1.startConvert)(dir, outDir, options.name, assetsDir);
|