asset-gen-cli 1.0.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/LICENSE +21 -0
- package/README.md +118 -0
- package/dist/index.js +331 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 김지후
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# asset-gen
|
|
2
|
+
|
|
3
|
+
TypeScript 프론트엔드 프로젝트에서 이미지 파일을 스캔하여 import 문과 타입 안전한 상수 객체를 자동 생성하는 CLI 도구입니다.
|
|
4
|
+
|
|
5
|
+
## 설치
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g asset-gen-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 빠른 시작
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 1. 설정 파일 생성 (대화형)
|
|
15
|
+
asset-gen init
|
|
16
|
+
|
|
17
|
+
# 2. 에셋 파일 생성
|
|
18
|
+
asset-gen gen
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 명령어
|
|
22
|
+
|
|
23
|
+
### `asset-gen init`
|
|
24
|
+
|
|
25
|
+
대화형으로 설정 파일(`asset-gen.config.json`)을 생성하고 에셋 파일을 즉시 생성합니다.
|
|
26
|
+
|
|
27
|
+
- 언어 선택 (English / 한국어 / 中文)
|
|
28
|
+
- 에셋 폴더 경로 입력 및 확인
|
|
29
|
+
- 출력 파일 경로 입력 및 확인
|
|
30
|
+
- 네이밍 규칙 선택
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
asset-gen init # 대화형 설정
|
|
34
|
+
asset-gen init --force # 기존 설정 파일 덮어쓰기
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `asset-gen gen` / `asset-gen generate`
|
|
38
|
+
|
|
39
|
+
설정 파일을 읽어 에셋 인덱스 파일과 TypeScript 타입 선언 파일을 생성합니다.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
asset-gen gen
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 생성 결과
|
|
46
|
+
|
|
47
|
+
`src/assets/` 폴더에 아래 파일들이 있을 때:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/assets/
|
|
51
|
+
├── logo.png
|
|
52
|
+
├── user-profile.png
|
|
53
|
+
└── banner.webp
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`asset-gen gen` 실행 시:
|
|
57
|
+
|
|
58
|
+
**`src/assets/index.ts`** (자동 생성)
|
|
59
|
+
```ts
|
|
60
|
+
import banner from './banner.webp';
|
|
61
|
+
import logo from './logo.png';
|
|
62
|
+
import userProfile from './user-profile.png';
|
|
63
|
+
|
|
64
|
+
export const assets = {
|
|
65
|
+
banner,
|
|
66
|
+
logo,
|
|
67
|
+
userProfile,
|
|
68
|
+
} as const;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**`src/assets/asset-gen.d.ts`** (자동 생성 — TypeScript 오류 방지)
|
|
72
|
+
```ts
|
|
73
|
+
declare module '*.png' {
|
|
74
|
+
const src: string;
|
|
75
|
+
export default src;
|
|
76
|
+
}
|
|
77
|
+
// ...
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 설정 파일
|
|
81
|
+
|
|
82
|
+
`asset-gen.config.json`
|
|
83
|
+
|
|
84
|
+
| 필드 | 타입 | 기본값 | 설명 |
|
|
85
|
+
|------|------|--------|------|
|
|
86
|
+
| `language` | `"en" \| "ko" \| "zh"` | `"en"` | CLI 출력 언어 |
|
|
87
|
+
| `input` | `string` | `"./src/assets"` | 스캔할 에셋 폴더 |
|
|
88
|
+
| `output` | `string` | `"./src/assets/index.ts"` | 생성할 파일 경로 |
|
|
89
|
+
| `extensions` | `string[]` | `["png","jpg","jpeg","webp","svg"]` | 추적할 확장자 |
|
|
90
|
+
| `naming` | `string` | `"camelCase"` | 변수 네이밍 규칙 |
|
|
91
|
+
|
|
92
|
+
### 네이밍 규칙
|
|
93
|
+
|
|
94
|
+
| 옵션 | 결과 예시 |
|
|
95
|
+
|------|----------|
|
|
96
|
+
| `camelCase` | `userProfile` |
|
|
97
|
+
| `PascalCase` | `UserProfile` |
|
|
98
|
+
| `snake_case` | `user_profile` |
|
|
99
|
+
| `SCREAMING_SNAKE_CASE` | `USER_PROFILE` |
|
|
100
|
+
| `original` | `user-profile` |
|
|
101
|
+
|
|
102
|
+
## 중복 키 감지
|
|
103
|
+
|
|
104
|
+
같은 변수명으로 변환되는 파일이 있으면 에러를 출력하고 중단합니다.
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
Duplicate asset key detected: userProfile
|
|
108
|
+
- user-profile.png
|
|
109
|
+
- user_profile.png
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 요구사항
|
|
113
|
+
|
|
114
|
+
- Node.js >= 18.0.0
|
|
115
|
+
|
|
116
|
+
## 라이선스
|
|
117
|
+
|
|
118
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
8
|
+
|
|
9
|
+
// src/utils/config.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
|
|
13
|
+
// src/utils/i18n.ts
|
|
14
|
+
var messages = {
|
|
15
|
+
en: {
|
|
16
|
+
selectLanguage: "Select language",
|
|
17
|
+
inputDir: "Enter the assets folder path",
|
|
18
|
+
confirmInput: (p) => `Assets folder: "${p}" \u2014 is this correct?`,
|
|
19
|
+
outputFile: "Enter the output file path",
|
|
20
|
+
confirmOutput: (p) => `Output file: "${p}" \u2014 is this correct?`,
|
|
21
|
+
selectNaming: "Select naming convention",
|
|
22
|
+
namingLabels: {
|
|
23
|
+
camelCase: "camelCase \u2192 userProfile",
|
|
24
|
+
PascalCase: "PascalCase \u2192 UserProfile",
|
|
25
|
+
snake_case: "snake_case \u2192 user_profile",
|
|
26
|
+
SCREAMING_SNAKE_CASE: "SCREAMING_SNAKE_CASE \u2192 USER_PROFILE",
|
|
27
|
+
original: "original \u2192 user-profile"
|
|
28
|
+
},
|
|
29
|
+
initSuccess: (p) => `Created ${p}`,
|
|
30
|
+
alreadyExists: "asset-gen.config.json already exists. Use --force to overwrite.",
|
|
31
|
+
scanning: (p) => `Scanning: ${p}`,
|
|
32
|
+
generated: (p) => `Generated: ${p}`,
|
|
33
|
+
assetAdded: (f) => `${f} added`,
|
|
34
|
+
assetRemoved: (f) => `${f} removed`,
|
|
35
|
+
regenerated: (p) => `${p} regenerated
|
|
36
|
+
`,
|
|
37
|
+
duplicateKey: (key, a, b) => `
|
|
38
|
+
Duplicate asset key detected: ${key}
|
|
39
|
+
- ${a}
|
|
40
|
+
- ${b}
|
|
41
|
+
`,
|
|
42
|
+
generatedDeclarations: (p) => `Generated type declarations: ${p}`,
|
|
43
|
+
configNotFound: 'asset-gen.config.json not found. Run "asset-gen init" first.',
|
|
44
|
+
configInvalid: "asset-gen.config.json is not valid JSON."
|
|
45
|
+
},
|
|
46
|
+
ko: {
|
|
47
|
+
selectLanguage: "\uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694",
|
|
48
|
+
inputDir: "\uC5D0\uC14B \uD3F4\uB354 \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694",
|
|
49
|
+
confirmInput: (p) => `\uC5D0\uC14B \uD3F4\uB354: "${p}" \u2014 \uB9DE\uB098\uC694?`,
|
|
50
|
+
outputFile: "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694",
|
|
51
|
+
confirmOutput: (p) => `\uCD9C\uB825 \uD30C\uC77C: "${p}" \u2014 \uB9DE\uB098\uC694?`,
|
|
52
|
+
selectNaming: "\uB124\uC774\uBC0D \uADDC\uCE59\uC744 \uC120\uD0DD\uD558\uC138\uC694",
|
|
53
|
+
namingLabels: {
|
|
54
|
+
camelCase: "camelCase \u2192 userProfile",
|
|
55
|
+
PascalCase: "PascalCase \u2192 UserProfile",
|
|
56
|
+
snake_case: "snake_case \u2192 user_profile",
|
|
57
|
+
SCREAMING_SNAKE_CASE: "SCREAMING_SNAKE_CASE \u2192 USER_PROFILE",
|
|
58
|
+
original: "original \u2192 user-profile"
|
|
59
|
+
},
|
|
60
|
+
initSuccess: (p) => `${p} \uC0DD\uC131 \uC644\uB8CC`,
|
|
61
|
+
alreadyExists: "asset-gen.config.json \uD30C\uC77C\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. --force \uC635\uC158\uC73C\uB85C \uB36E\uC5B4\uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
62
|
+
scanning: (p) => `\uC2A4\uCE94 \uC911: ${p}`,
|
|
63
|
+
generated: (p) => `\uC0DD\uC131 \uC644\uB8CC: ${p}`,
|
|
64
|
+
assetAdded: (f) => `${f} \uCD94\uAC00\uB428`,
|
|
65
|
+
assetRemoved: (f) => `${f} \uC0AD\uC81C\uB428`,
|
|
66
|
+
regenerated: (p) => `${p} \uC7AC\uC0DD\uC131 \uC644\uB8CC
|
|
67
|
+
`,
|
|
68
|
+
duplicateKey: (key, a, b) => `
|
|
69
|
+
\uC911\uBCF5 \uC5D0\uC14B \uD0A4 \uAC10\uC9C0: ${key}
|
|
70
|
+
- ${a}
|
|
71
|
+
- ${b}
|
|
72
|
+
`,
|
|
73
|
+
generatedDeclarations: (p) => `\uD0C0\uC785 \uC120\uC5B8 \uD30C\uC77C \uC0DD\uC131 \uC644\uB8CC: ${p}`,
|
|
74
|
+
configNotFound: 'asset-gen.config.json \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. "asset-gen init"\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.',
|
|
75
|
+
configInvalid: "asset-gen.config.json \uD30C\uC77C\uC774 \uC62C\uBC14\uB978 JSON \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4."
|
|
76
|
+
},
|
|
77
|
+
zh: {
|
|
78
|
+
selectLanguage: "\u8BF7\u9009\u62E9\u8BED\u8A00",
|
|
79
|
+
inputDir: "\u8BF7\u8F93\u5165\u8D44\u6E90\u6587\u4EF6\u5939\u8DEF\u5F84",
|
|
80
|
+
confirmInput: (p) => `\u8D44\u6E90\u6587\u4EF6\u5939: "${p}" \u2014 \u786E\u8BA4\u6B63\u786E?`,
|
|
81
|
+
outputFile: "\u8BF7\u8F93\u5165\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84",
|
|
82
|
+
confirmOutput: (p) => `\u8F93\u51FA\u6587\u4EF6: "${p}" \u2014 \u786E\u8BA4\u6B63\u786E?`,
|
|
83
|
+
selectNaming: "\u8BF7\u9009\u62E9\u547D\u540D\u89C4\u5219",
|
|
84
|
+
namingLabels: {
|
|
85
|
+
camelCase: "camelCase \u2192 userProfile",
|
|
86
|
+
PascalCase: "PascalCase \u2192 UserProfile",
|
|
87
|
+
snake_case: "snake_case \u2192 user_profile",
|
|
88
|
+
SCREAMING_SNAKE_CASE: "SCREAMING_SNAKE_CASE \u2192 USER_PROFILE",
|
|
89
|
+
original: "original \u2192 user-profile"
|
|
90
|
+
},
|
|
91
|
+
initSuccess: (p) => `\u5DF2\u521B\u5EFA ${p}`,
|
|
92
|
+
alreadyExists: "asset-gen.config.json \u5DF2\u5B58\u5728\u3002\u4F7F\u7528 --force \u8986\u76D6\u3002",
|
|
93
|
+
scanning: (p) => `\u626B\u63CF\u4E2D: ${p}`,
|
|
94
|
+
generated: (p) => `\u5DF2\u751F\u6210: ${p}`,
|
|
95
|
+
assetAdded: (f) => `${f} \u5DF2\u6DFB\u52A0`,
|
|
96
|
+
assetRemoved: (f) => `${f} \u5DF2\u5220\u9664`,
|
|
97
|
+
regenerated: (p) => `${p} \u5DF2\u91CD\u65B0\u751F\u6210
|
|
98
|
+
`,
|
|
99
|
+
duplicateKey: (key, a, b) => `
|
|
100
|
+
\u68C0\u6D4B\u5230\u91CD\u590D\u7684\u8D44\u6E90\u952E: ${key}
|
|
101
|
+
- ${a}
|
|
102
|
+
- ${b}
|
|
103
|
+
`,
|
|
104
|
+
generatedDeclarations: (p) => `\u5DF2\u751F\u6210\u7C7B\u578B\u58F0\u660E\u6587\u4EF6: ${p}`,
|
|
105
|
+
configNotFound: '\u627E\u4E0D\u5230 asset-gen.config.json\u3002\u8BF7\u5148\u8FD0\u884C "asset-gen init"\u3002',
|
|
106
|
+
configInvalid: "asset-gen.config.json \u4E0D\u662F\u6709\u6548\u7684 JSON \u6587\u4EF6\u3002"
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
function getMessages(lang) {
|
|
110
|
+
return messages[lang];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/utils/config.ts
|
|
114
|
+
var CONFIG_FILENAME = "asset-gen.config.json";
|
|
115
|
+
var DEFAULT_CONFIG = {
|
|
116
|
+
language: "en",
|
|
117
|
+
input: "./src/assets",
|
|
118
|
+
output: "./src/assets/index.ts",
|
|
119
|
+
extensions: ["png", "jpg", "jpeg", "webp", "svg"],
|
|
120
|
+
naming: "camelCase"
|
|
121
|
+
};
|
|
122
|
+
function getDefaultConfig() {
|
|
123
|
+
return { ...DEFAULT_CONFIG };
|
|
124
|
+
}
|
|
125
|
+
function getConfigPath() {
|
|
126
|
+
return path.resolve(process.cwd(), CONFIG_FILENAME);
|
|
127
|
+
}
|
|
128
|
+
function configExists() {
|
|
129
|
+
return fs.existsSync(getConfigPath());
|
|
130
|
+
}
|
|
131
|
+
function loadConfig() {
|
|
132
|
+
const configPath = getConfigPath();
|
|
133
|
+
if (!fs.existsSync(configPath)) {
|
|
134
|
+
const msg = getMessages("en");
|
|
135
|
+
console.error(msg.configNotFound);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
140
|
+
const config = JSON.parse(raw);
|
|
141
|
+
return { ...DEFAULT_CONFIG, ...config };
|
|
142
|
+
} catch {
|
|
143
|
+
const msg = getMessages("en");
|
|
144
|
+
console.error(msg.configInvalid);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function saveConfig(config) {
|
|
149
|
+
fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/generators/asset-generator.ts
|
|
153
|
+
import fs2 from "fs";
|
|
154
|
+
import path4 from "path";
|
|
155
|
+
import prettier from "prettier";
|
|
156
|
+
|
|
157
|
+
// src/utils/file.ts
|
|
158
|
+
import glob from "fast-glob";
|
|
159
|
+
import path2 from "path";
|
|
160
|
+
async function scanAssets(inputDir, extensions) {
|
|
161
|
+
const patterns = extensions.map((ext) => `**/*.${ext}`);
|
|
162
|
+
const files = await glob(patterns, {
|
|
163
|
+
cwd: path2.resolve(process.cwd(), inputDir),
|
|
164
|
+
caseSensitiveMatch: false,
|
|
165
|
+
onlyFiles: true
|
|
166
|
+
});
|
|
167
|
+
return files.sort();
|
|
168
|
+
}
|
|
169
|
+
function relativeImportPath(from, to, filename) {
|
|
170
|
+
const outputDir = path2.dirname(path2.resolve(process.cwd(), from));
|
|
171
|
+
const inputDir = path2.resolve(process.cwd(), to);
|
|
172
|
+
const fullFilePath = path2.join(inputDir, filename);
|
|
173
|
+
const rel = path2.relative(outputDir, fullFilePath).replace(/\\/g, "/");
|
|
174
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/utils/naming.ts
|
|
178
|
+
import { camelCase, pascalCase, snakeCase, constantCase } from "change-case";
|
|
179
|
+
import path3 from "path";
|
|
180
|
+
function toVariableName(filename, convention) {
|
|
181
|
+
const ext = path3.extname(filename);
|
|
182
|
+
const base = filename.slice(0, filename.length - ext.length);
|
|
183
|
+
switch (convention) {
|
|
184
|
+
case "camelCase":
|
|
185
|
+
return camelCase(base);
|
|
186
|
+
case "PascalCase":
|
|
187
|
+
return pascalCase(base);
|
|
188
|
+
case "snake_case":
|
|
189
|
+
return snakeCase(base);
|
|
190
|
+
case "SCREAMING_SNAKE_CASE":
|
|
191
|
+
return constantCase(base);
|
|
192
|
+
case "original":
|
|
193
|
+
return base;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function checkDuplicates(filenames, convention, msg) {
|
|
197
|
+
const keyMap = /* @__PURE__ */ new Map();
|
|
198
|
+
for (const filename of filenames) {
|
|
199
|
+
const key = toVariableName(filename, convention);
|
|
200
|
+
if (keyMap.has(key)) {
|
|
201
|
+
const conflict = keyMap.get(key);
|
|
202
|
+
console.error(msg.duplicateKey(key, conflict, filename));
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
keyMap.set(key, filename);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/templates/asset.template.ts
|
|
210
|
+
function renderTemplate(entries) {
|
|
211
|
+
if (entries.length === 0) {
|
|
212
|
+
return `export const assets = {} as const;
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
const imports = entries.map((e) => `import ${e.variableName} from '${e.importPath}';`).join("\n");
|
|
216
|
+
const keys = entries.map((e) => ` ${e.variableName},`).join("\n");
|
|
217
|
+
return `${imports}
|
|
218
|
+
|
|
219
|
+
export const assets = {
|
|
220
|
+
${keys}
|
|
221
|
+
} as const;
|
|
222
|
+
`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/generators/asset-generator.ts
|
|
226
|
+
async function generateAssets(config) {
|
|
227
|
+
const { input: input2, output, extensions, naming, language } = config;
|
|
228
|
+
const msg = getMessages(language);
|
|
229
|
+
const files = await scanAssets(input2, extensions);
|
|
230
|
+
const basenames = files.map((f) => path4.basename(f));
|
|
231
|
+
checkDuplicates(basenames, naming, msg);
|
|
232
|
+
const entries = files.map((file) => ({
|
|
233
|
+
variableName: toVariableName(path4.basename(file), naming),
|
|
234
|
+
importPath: relativeImportPath(output, input2, file)
|
|
235
|
+
}));
|
|
236
|
+
const raw = renderTemplate(entries);
|
|
237
|
+
const formatted = await prettier.format(raw, { parser: "typescript" });
|
|
238
|
+
const outputPath = path4.resolve(process.cwd(), output);
|
|
239
|
+
fs2.mkdirSync(path4.dirname(outputPath), { recursive: true });
|
|
240
|
+
fs2.writeFileSync(outputPath, formatted, "utf-8");
|
|
241
|
+
}
|
|
242
|
+
async function generateDeclarations(config) {
|
|
243
|
+
const { output, extensions } = config;
|
|
244
|
+
const declarations = extensions.map(
|
|
245
|
+
(ext) => `declare module '*.${ext}' {
|
|
246
|
+
const src: string;
|
|
247
|
+
export default src;
|
|
248
|
+
}`
|
|
249
|
+
).join("\n\n");
|
|
250
|
+
const content = `// Generated by asset-gen \u2014 do not edit manually
|
|
251
|
+
|
|
252
|
+
${declarations}
|
|
253
|
+
`;
|
|
254
|
+
const outputDir = path4.dirname(path4.resolve(process.cwd(), output));
|
|
255
|
+
const declPath = path4.join(outputDir, "asset-gen.d.ts");
|
|
256
|
+
fs2.writeFileSync(declPath, content, "utf-8");
|
|
257
|
+
return path4.relative(process.cwd(), declPath);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/commands/init.ts
|
|
261
|
+
async function runInit(options) {
|
|
262
|
+
if (configExists() && !options.force) {
|
|
263
|
+
const lang2 = await selectLanguage();
|
|
264
|
+
const msg2 = getMessages(lang2);
|
|
265
|
+
console.log(msg2.alreadyExists);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const lang = await selectLanguage();
|
|
269
|
+
const msg = getMessages(lang);
|
|
270
|
+
const defaults = getDefaultConfig();
|
|
271
|
+
let inputDir = "";
|
|
272
|
+
while (true) {
|
|
273
|
+
inputDir = await input({ message: msg.inputDir, default: defaults.input });
|
|
274
|
+
const ok = await confirm({ message: msg.confirmInput(inputDir), default: true });
|
|
275
|
+
if (ok) break;
|
|
276
|
+
}
|
|
277
|
+
let outputFile = "";
|
|
278
|
+
while (true) {
|
|
279
|
+
outputFile = await input({ message: msg.outputFile, default: defaults.output });
|
|
280
|
+
const ok = await confirm({ message: msg.confirmOutput(outputFile), default: true });
|
|
281
|
+
if (ok) break;
|
|
282
|
+
}
|
|
283
|
+
const naming = await select({
|
|
284
|
+
message: msg.selectNaming,
|
|
285
|
+
choices: Object.keys(msg.namingLabels).map((key) => ({
|
|
286
|
+
name: msg.namingLabels[key],
|
|
287
|
+
value: key
|
|
288
|
+
}))
|
|
289
|
+
});
|
|
290
|
+
const config = { ...defaults, language: lang, input: inputDir, output: outputFile, naming };
|
|
291
|
+
saveConfig(config);
|
|
292
|
+
console.log(`
|
|
293
|
+
${msg.initSuccess(getConfigPath())}`);
|
|
294
|
+
console.log(msg.scanning(config.input));
|
|
295
|
+
await generateAssets(config);
|
|
296
|
+
console.log(msg.generated(config.output));
|
|
297
|
+
const declPath = await generateDeclarations(config);
|
|
298
|
+
console.log(msg.generatedDeclarations(declPath));
|
|
299
|
+
}
|
|
300
|
+
async function selectLanguage() {
|
|
301
|
+
return select({
|
|
302
|
+
message: "Select language / \uC5B8\uC5B4 \uC120\uD0DD / \u9009\u62E9\u8BED\u8A00",
|
|
303
|
+
choices: [
|
|
304
|
+
{ name: "English", value: "en" },
|
|
305
|
+
{ name: "\uD55C\uAD6D\uC5B4", value: "ko" },
|
|
306
|
+
{ name: "\u4E2D\u6587", value: "zh" }
|
|
307
|
+
]
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/commands/generate.ts
|
|
312
|
+
async function runGenerate() {
|
|
313
|
+
const config = loadConfig();
|
|
314
|
+
const msg = getMessages(config.language);
|
|
315
|
+
console.log(msg.scanning(config.input));
|
|
316
|
+
await generateAssets(config);
|
|
317
|
+
console.log(msg.generated(config.output));
|
|
318
|
+
const declPath = await generateDeclarations(config);
|
|
319
|
+
console.log(msg.generatedDeclarations(declPath));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/index.ts
|
|
323
|
+
var program = new Command();
|
|
324
|
+
program.name("asset-gen").description("Generate TypeScript asset index files from image directories").version("1.0.0");
|
|
325
|
+
program.command("init").description("Create asset-gen.config.json in the current directory").option("-f, --force", "Overwrite existing config file").action((opts) => {
|
|
326
|
+
runInit({ force: opts.force });
|
|
327
|
+
});
|
|
328
|
+
program.command("generate").alias("gen").description("Scan assets and generate the index file").action(async () => {
|
|
329
|
+
await runGenerate();
|
|
330
|
+
});
|
|
331
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "asset-gen-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Generate TypeScript asset index files from image directories",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"asset-gen": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"prepare": "tsup",
|
|
16
|
+
"dev": "tsup --watch",
|
|
17
|
+
"start": "node dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"cli",
|
|
21
|
+
"assets",
|
|
22
|
+
"typescript",
|
|
23
|
+
"codegen",
|
|
24
|
+
"images"
|
|
25
|
+
],
|
|
26
|
+
"author": "wlgn829@kakao.com",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.0.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@inquirer/prompts": "^8.5.2",
|
|
33
|
+
"change-case": "5.4.4",
|
|
34
|
+
"commander": "12.1.0",
|
|
35
|
+
"fast-glob": "3.3.3",
|
|
36
|
+
"prettier": "3.8.4"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^26.0.0",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"typescript": "^6.0.3"
|
|
42
|
+
}
|
|
43
|
+
}
|