tyhuynh-laya-cmd 1.0.12 → 1.0.17
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 +171 -171
- package/dist/index.js +1 -1
- package/dist/src/generators/AniGen.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,171 +1,171 @@
|
|
|
1
|
-
# tyhuynh-laya-cmd
|
|
2
|
-
|
|
3
|
-
> Fast, incremental UI build tool for **LayaAir 2** projects — replaces `layaair2-cmd`.
|
|
4
|
-
|
|
5
|
-
Thay thế `layaair2-cmd` với build nhanh hơn, hỗ trợ incremental build, watch mode thông minh và migrate assets tự động.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Tính năng
|
|
10
|
-
|
|
11
|
-
- ⚡ **Incremental build** — chỉ rebuild những file thay đổi, không rebuild toàn bộ
|
|
12
|
-
- 🎨 **Atlas generation** — đóng gói sprite atlas tối ưu với `maxrects-packer` + `sharp`
|
|
13
|
-
- 📄 **UI code generation** — tạo `layaMaxUI.ts` từ `.scene` files
|
|
14
|
-
- 👀 **Watch mode thông minh** — phân biệt `.scene` (code only) vs ảnh (atlas only) vs XML (full rebuild)
|
|
15
|
-
- 🔍 **Auto-detect** — tự tìm thư mục `laya/` từ thư mục hiện tại, không cần chỉ định `-p`
|
|
16
|
-
- 📦 **Migrate assets** — phát hiện và di chuyển ảnh lạc loài trong `bin/` vào `laya/assets/`
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Cài đặt
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npm install --save-dev tyhuynh-laya-cmd
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Sử dụng
|
|
29
|
-
|
|
30
|
-
Chạy từ thư mục **gốc dự án** (nơi chứa thư mục `laya/`). Tool sẽ tự tìm `laya/`.
|
|
31
|
-
|
|
32
|
-
### Build UI (atlas + code)
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
# Build một lần
|
|
36
|
-
tyhuynh-laya-cmd ui -a -d
|
|
37
|
-
|
|
38
|
-
# Build + clear cache (full rebuild)
|
|
39
|
-
tyhuynh-laya-cmd ui -a -d -c
|
|
40
|
-
|
|
41
|
-
# Chỉ build atlas
|
|
42
|
-
tyhuynh-laya-cmd ui -a
|
|
43
|
-
|
|
44
|
-
# Chỉ build UI code (layaMaxUI.ts)
|
|
45
|
-
tyhuynh-laya-cmd ui -d
|
|
46
|
-
|
|
47
|
-
# Watch mode — tự rebuild khi có thay đổi
|
|
48
|
-
tyhuynh-laya-cmd ui -a -d -w
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
| Flag | Tác dụng |
|
|
52
|
-
|------|----------|
|
|
53
|
-
| `-a` | Generate atlas sprite sheets |
|
|
54
|
-
| `-d` | Generate TypeScript UI code (`layaMaxUI.ts`) |
|
|
55
|
-
| `-c` | Clear cache, force full rebuild |
|
|
56
|
-
| `-w` | Watch mode |
|
|
57
|
-
| `-p <dir>` | Chỉ định thủ công đường dẫn `laya/` (mặc định: auto-detect) |
|
|
58
|
-
|
|
59
|
-
### Migrate stray assets
|
|
60
|
-
|
|
61
|
-
Dùng khi có ảnh nằm trực tiếp trong `bin/res/image/` nhưng chưa được quản lý bởi `laya/assets/`.
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
# Xem trước — không thay đổi gì
|
|
65
|
-
tyhuynh-laya-cmd migrate --dry-run
|
|
66
|
-
|
|
67
|
-
# Thực hiện migrate
|
|
68
|
-
tyhuynh-laya-cmd migrate
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Script sẽ:
|
|
72
|
-
1. Tìm ảnh trong `bin/res/image/` không có tương ứng trong `laya/assets/`
|
|
73
|
-
2. Copy chúng sang `laya/assets/`
|
|
74
|
-
3. Thêm entry vào `styles.xml`:
|
|
75
|
-
- Thư mục **mới hoàn toàn** → `pack="2"` (dir rule, raw copy)
|
|
76
|
-
- Thư mục **đã tồn tại** → từng file lẻ (giữ nguyên cấu hình atlas cũ)
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## Tích hợp vào `package.json`
|
|
81
|
-
|
|
82
|
-
```json
|
|
83
|
-
{
|
|
84
|
-
"scripts": {
|
|
85
|
-
"exportui": "tyhuynh-laya-cmd ui -a -d && npm run compile",
|
|
86
|
-
"exportuic": "tyhuynh-laya-cmd ui -c -a -d",
|
|
87
|
-
"exportui:watch": "tyhuynh-laya-cmd ui -a -d -w",
|
|
88
|
-
"export": "tyhuynh-laya-cmd ui -c -a -d && npm run compile",
|
|
89
|
-
"migrate": "tyhuynh-laya-cmd migrate",
|
|
90
|
-
"migrate:dry": "tyhuynh-laya-cmd migrate --dry-run",
|
|
91
|
-
"dev": "npm run nginx && concurrently \"npm run watch\" \"npm run exportui:watch\""
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## Watch mode — Log output
|
|
99
|
-
|
|
100
|
-
Khi chạy watch mode, output được phân loại rõ ràng với prefix `[UI]`:
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
╔════════════════════════════════════════╗
|
|
104
|
-
║ 🎨 UI Build Tool (tyhuynh-laya-cmd) ║
|
|
105
|
-
╚════════════════════════════════════════╝
|
|
106
|
-
[UI] Project: D:\project\client\laya
|
|
107
|
-
[UI] ✅ 12:00:00 — Build complete in 0.18s
|
|
108
|
-
[UI] 👀 Watching pages/ & assets/ — Ctrl+C để dừng
|
|
109
|
-
[UI] .scene → code | image → atlas | .xml → full rebuild
|
|
110
|
-
|
|
111
|
-
# Khi sửa file .scene:
|
|
112
|
-
[UI] 📝 Đã sửa:
|
|
113
|
-
[UI] • pages/res/image/com/config/copy/CopyDoor.scene
|
|
114
|
-
[UI] 📄 UI Code: 1 built, 823 skipped
|
|
115
|
-
[UI] ✅ 12:01:30 — Build complete in 0.16s
|
|
116
|
-
|
|
117
|
-
# Khi sửa ảnh:
|
|
118
|
-
[UI] 📝 Đã sửa:
|
|
119
|
-
[UI] • assets/res/image/com/bag/icon_gold.png
|
|
120
|
-
[UI] 🖼 Atlas: done
|
|
121
|
-
[UI] ✅ 12:02:00 — Build complete in 0.45s
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## Cấu trúc dự án
|
|
127
|
-
|
|
128
|
-
```
|
|
129
|
-
custom-cmd/
|
|
130
|
-
├── index.js # CLI entry point
|
|
131
|
-
├── build-dist.js # Build script (minify → dist/)
|
|
132
|
-
├── scripts/
|
|
133
|
-
│ └── migrate-stray-assets.js # Migrate tool
|
|
134
|
-
└── src/
|
|
135
|
-
├── config/
|
|
136
|
-
│ └── ProjectConfig.js # Đọc .laya config
|
|
137
|
-
├── generators/
|
|
138
|
-
│ ├── AtlasGen.js # Atlas generation
|
|
139
|
-
│ ├── UICodeGen.js # layaMaxUI.ts generation
|
|
140
|
-
│ └── SceneJsonGen.js # Scene JSON generation
|
|
141
|
-
├── parsers/
|
|
142
|
-
│ ├── StylesParser.js # Parse styles.xml
|
|
143
|
-
│ ├── PageParser.js # Parse pageStyles.xml
|
|
144
|
-
│ └── SceneParser.js # Parse .scene files
|
|
145
|
-
└── utils/
|
|
146
|
-
├── BuildCache.js # Incremental build cache
|
|
147
|
-
├── FileUtils.js
|
|
148
|
-
└── XmlUtils.js
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## Publish lên npm
|
|
154
|
-
|
|
155
|
-
```bash
|
|
156
|
-
# Bump version
|
|
157
|
-
npm version patch # hoặc minor / major
|
|
158
|
-
|
|
159
|
-
# Build (minify) + publish tự động
|
|
160
|
-
npm publish --access public
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
> Script `prepublishOnly` sẽ tự chạy `build-dist.js` để minify code vào `dist/` trước khi publish.
|
|
164
|
-
> Source gốc (`src/`, `scripts/`, `index.js`) **không được upload** lên npm.
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## Yêu cầu
|
|
169
|
-
|
|
170
|
-
- Node.js >= 16.0.0
|
|
171
|
-
- LayaAir 2 project với cấu trúc `laya/` tiêu chuẩn (có file `.laya` config)
|
|
1
|
+
# tyhuynh-laya-cmd
|
|
2
|
+
|
|
3
|
+
> Fast, incremental UI build tool for **LayaAir 2** projects — replaces `layaair2-cmd`.
|
|
4
|
+
|
|
5
|
+
Thay thế `layaair2-cmd` với build nhanh hơn, hỗ trợ incremental build, watch mode thông minh và migrate assets tự động.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Tính năng
|
|
10
|
+
|
|
11
|
+
- ⚡ **Incremental build** — chỉ rebuild những file thay đổi, không rebuild toàn bộ
|
|
12
|
+
- 🎨 **Atlas generation** — đóng gói sprite atlas tối ưu với `maxrects-packer` + `sharp`
|
|
13
|
+
- 📄 **UI code generation** — tạo `layaMaxUI.ts` từ `.scene` files
|
|
14
|
+
- 👀 **Watch mode thông minh** — phân biệt `.scene` (code only) vs ảnh (atlas only) vs XML (full rebuild)
|
|
15
|
+
- 🔍 **Auto-detect** — tự tìm thư mục `laya/` từ thư mục hiện tại, không cần chỉ định `-p`
|
|
16
|
+
- 📦 **Migrate assets** — phát hiện và di chuyển ảnh lạc loài trong `bin/` vào `laya/assets/`
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Cài đặt
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install --save-dev tyhuynh-laya-cmd
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Sử dụng
|
|
29
|
+
|
|
30
|
+
Chạy từ thư mục **gốc dự án** (nơi chứa thư mục `laya/`). Tool sẽ tự tìm `laya/`.
|
|
31
|
+
|
|
32
|
+
### Build UI (atlas + code)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Build một lần
|
|
36
|
+
tyhuynh-laya-cmd ui -a -d
|
|
37
|
+
|
|
38
|
+
# Build + clear cache (full rebuild)
|
|
39
|
+
tyhuynh-laya-cmd ui -a -d -c
|
|
40
|
+
|
|
41
|
+
# Chỉ build atlas
|
|
42
|
+
tyhuynh-laya-cmd ui -a
|
|
43
|
+
|
|
44
|
+
# Chỉ build UI code (layaMaxUI.ts)
|
|
45
|
+
tyhuynh-laya-cmd ui -d
|
|
46
|
+
|
|
47
|
+
# Watch mode — tự rebuild khi có thay đổi
|
|
48
|
+
tyhuynh-laya-cmd ui -a -d -w
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Flag | Tác dụng |
|
|
52
|
+
|------|----------|
|
|
53
|
+
| `-a` | Generate atlas sprite sheets |
|
|
54
|
+
| `-d` | Generate TypeScript UI code (`layaMaxUI.ts`) |
|
|
55
|
+
| `-c` | Clear cache, force full rebuild |
|
|
56
|
+
| `-w` | Watch mode |
|
|
57
|
+
| `-p <dir>` | Chỉ định thủ công đường dẫn `laya/` (mặc định: auto-detect) |
|
|
58
|
+
|
|
59
|
+
### Migrate stray assets
|
|
60
|
+
|
|
61
|
+
Dùng khi có ảnh nằm trực tiếp trong `bin/res/image/` nhưng chưa được quản lý bởi `laya/assets/`.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Xem trước — không thay đổi gì
|
|
65
|
+
tyhuynh-laya-cmd migrate --dry-run
|
|
66
|
+
|
|
67
|
+
# Thực hiện migrate
|
|
68
|
+
tyhuynh-laya-cmd migrate
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Script sẽ:
|
|
72
|
+
1. Tìm ảnh trong `bin/res/image/` không có tương ứng trong `laya/assets/`
|
|
73
|
+
2. Copy chúng sang `laya/assets/`
|
|
74
|
+
3. Thêm entry vào `styles.xml`:
|
|
75
|
+
- Thư mục **mới hoàn toàn** → `pack="2"` (dir rule, raw copy)
|
|
76
|
+
- Thư mục **đã tồn tại** → từng file lẻ (giữ nguyên cấu hình atlas cũ)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Tích hợp vào `package.json`
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"scripts": {
|
|
85
|
+
"exportui": "tyhuynh-laya-cmd ui -a -d && npm run compile",
|
|
86
|
+
"exportuic": "tyhuynh-laya-cmd ui -c -a -d",
|
|
87
|
+
"exportui:watch": "tyhuynh-laya-cmd ui -a -d -w",
|
|
88
|
+
"export": "tyhuynh-laya-cmd ui -c -a -d && npm run compile",
|
|
89
|
+
"migrate": "tyhuynh-laya-cmd migrate",
|
|
90
|
+
"migrate:dry": "tyhuynh-laya-cmd migrate --dry-run",
|
|
91
|
+
"dev": "npm run nginx && concurrently \"npm run watch\" \"npm run exportui:watch\""
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Watch mode — Log output
|
|
99
|
+
|
|
100
|
+
Khi chạy watch mode, output được phân loại rõ ràng với prefix `[UI]`:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
╔════════════════════════════════════════╗
|
|
104
|
+
║ 🎨 UI Build Tool (tyhuynh-laya-cmd) ║
|
|
105
|
+
╚════════════════════════════════════════╝
|
|
106
|
+
[UI] Project: D:\project\client\laya
|
|
107
|
+
[UI] ✅ 12:00:00 — Build complete in 0.18s
|
|
108
|
+
[UI] 👀 Watching pages/ & assets/ — Ctrl+C để dừng
|
|
109
|
+
[UI] .scene → code | image → atlas | .xml → full rebuild
|
|
110
|
+
|
|
111
|
+
# Khi sửa file .scene:
|
|
112
|
+
[UI] 📝 Đã sửa:
|
|
113
|
+
[UI] • pages/res/image/com/config/copy/CopyDoor.scene
|
|
114
|
+
[UI] 📄 UI Code: 1 built, 823 skipped
|
|
115
|
+
[UI] ✅ 12:01:30 — Build complete in 0.16s
|
|
116
|
+
|
|
117
|
+
# Khi sửa ảnh:
|
|
118
|
+
[UI] 📝 Đã sửa:
|
|
119
|
+
[UI] • assets/res/image/com/bag/icon_gold.png
|
|
120
|
+
[UI] 🖼 Atlas: done
|
|
121
|
+
[UI] ✅ 12:02:00 — Build complete in 0.45s
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Cấu trúc dự án
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
custom-cmd/
|
|
130
|
+
├── index.js # CLI entry point
|
|
131
|
+
├── build-dist.js # Build script (minify → dist/)
|
|
132
|
+
├── scripts/
|
|
133
|
+
│ └── migrate-stray-assets.js # Migrate tool
|
|
134
|
+
└── src/
|
|
135
|
+
├── config/
|
|
136
|
+
│ └── ProjectConfig.js # Đọc .laya config
|
|
137
|
+
├── generators/
|
|
138
|
+
│ ├── AtlasGen.js # Atlas generation
|
|
139
|
+
│ ├── UICodeGen.js # layaMaxUI.ts generation
|
|
140
|
+
│ └── SceneJsonGen.js # Scene JSON generation
|
|
141
|
+
├── parsers/
|
|
142
|
+
│ ├── StylesParser.js # Parse styles.xml
|
|
143
|
+
│ ├── PageParser.js # Parse pageStyles.xml
|
|
144
|
+
│ └── SceneParser.js # Parse .scene files
|
|
145
|
+
└── utils/
|
|
146
|
+
├── BuildCache.js # Incremental build cache
|
|
147
|
+
├── FileUtils.js
|
|
148
|
+
└── XmlUtils.js
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Publish lên npm
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Bump version
|
|
157
|
+
npm version patch # hoặc minor / major
|
|
158
|
+
|
|
159
|
+
# Build (minify) + publish tự động
|
|
160
|
+
npm publish --access public
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> Script `prepublishOnly` sẽ tự chạy `build-dist.js` để minify code vào `dist/` trước khi publish.
|
|
164
|
+
> Source gốc (`src/`, `scripts/`, `index.js`) **không được upload** lên npm.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Yêu cầu
|
|
169
|
+
|
|
170
|
+
- Node.js >= 16.0.0
|
|
171
|
+
- LayaAir 2 project với cấu trúc `laya/` tiêu chuẩn (có file `.laya` config)
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";const path=require("path"),fs=require("fs-extra"),{program:program}=require("commander");require("colors");const{loadProjectConfig:loadProjectConfig}=require("./src/config/ProjectConfig"),{loadStyles:loadStyles,invalidateCache:invalidateStyles}=require("./src/parsers/StylesParser"),{loadPageStyles:loadPageStyles,invalidateCache:invalidatePages}=require("./src/parsers/PageParser"),{generateUICode:generateUICode}=require("./src/generators/UICodeGen"),{generateAtlas:generateAtlas}=require("./src/generators/AtlasGen"),{generateGameConfig:generateGameConfig}=require("./src/generators/GameConfigGen"),{collectFiles:collectFiles}=require("./src/utils/FileUtils");function autoDetectLayaDir(){const e=e=>{const o=path.join(e,".laya");return fs.existsSync(o)&&fs.statSync(o).isFile()};let o=process.cwd();if(e(o))return o;const t=path.join(o,"laya");if(e(t))return t;let a=null,n=o;for(;n!==a;){const o=path.join(n,"laya");if(e(o))return o;a=n,n=path.dirname(n)}return null}async function runUI(e){const{project:o,atlas:t,code:a,clear:n,watch:r}=e,s=!!e.gameConfig,c=a||!t&&!a,i=t||!t&&!a,l=path.resolve(o);fs.existsSync(l)||(console.error(`[ERROR] Project directory not found: ${l}`.red),process.exit(1)),r?(console.log("\n╔════════════════════════════════════════╗"),console.log("║ 🎨 UI Build Tool (tyhuynh-laya-cmd) ║"),console.log("╚════════════════════════════════════════╝")):console.log(`\n${"⚡ Custom LayaAir2 Build Tool".cyan.bold}`),console.log(`[UI] Project: ${l.gray}`);const d=[c&&"CODE",i&&"ATLAS",s&&"GAME-CONFIG"].filter(Boolean).join(" + ");console.log(`[UI] Mode: ${d.yellow}`),n&&console.log(`[UI] Cache: ${"CLEARED".red}`),console.log(""),await build(l,{doCode:c,doAtlas:i,clear:n}),s&&await buildGameConfig(l),r&&await startWatchMode(l,{doCode:c,doAtlas:i,doGameConfig:s})}async function build(e,{doCode:o,doAtlas:t,clear:a,hint:n={}}){const r=Date.now();let s;try{s=loadProjectConfig(e)}catch(e){return void console.error(`[ERROR] Cannot load .laya config: ${e.message}`.red)}const c=path.dirname(e),i=path.join(e,"pages"),l=path.join(c,s.codeExportPath,"layaMaxUI.ts"),d=path.join(e,".custom-cmd-cache"),g=path.join(d,"code-cache.json"),p=path.join(d,"atlas-cache.json"),u=o&&!n.atlasOnly,
|
|
2
|
+
"use strict";const path=require("path"),fs=require("fs-extra"),{program:program}=require("commander");require("colors");const{loadProjectConfig:loadProjectConfig}=require("./src/config/ProjectConfig"),{loadStyles:loadStyles,invalidateCache:invalidateStyles}=require("./src/parsers/StylesParser"),{loadPageStyles:loadPageStyles,invalidateCache:invalidatePages}=require("./src/parsers/PageParser"),{generateUICode:generateUICode}=require("./src/generators/UICodeGen"),{generateAtlas:generateAtlas}=require("./src/generators/AtlasGen"),{generateGameConfig:generateGameConfig}=require("./src/generators/GameConfigGen"),{generateAni:generateAni}=require("./src/generators/AniGen"),{collectFiles:collectFiles}=require("./src/utils/FileUtils");function autoDetectLayaDir(){const e=e=>{const o=path.join(e,".laya");return fs.existsSync(o)&&fs.statSync(o).isFile()};let o=process.cwd();if(e(o))return o;const t=path.join(o,"laya");if(e(t))return t;let a=null,n=o;for(;n!==a;){const o=path.join(n,"laya");if(e(o))return o;a=n,n=path.dirname(n)}return null}async function runUI(e){const{project:o,atlas:t,code:a,clear:n,watch:r}=e,s=!!e.gameConfig,c=a||!t&&!a,i=t||!t&&!a,l=path.resolve(o);fs.existsSync(l)||(console.error(`[ERROR] Project directory not found: ${l}`.red),process.exit(1)),r?(console.log("\n╔════════════════════════════════════════╗"),console.log("║ 🎨 UI Build Tool (tyhuynh-laya-cmd) ║"),console.log("╚════════════════════════════════════════╝")):console.log(`\n${"⚡ Custom LayaAir2 Build Tool".cyan.bold}`),console.log(`[UI] Project: ${l.gray}`);const d=[c&&"CODE",i&&"ATLAS",s&&"GAME-CONFIG"].filter(Boolean).join(" + ");console.log(`[UI] Mode: ${d.yellow}`),n&&console.log(`[UI] Cache: ${"CLEARED".red}`),console.log(""),await build(l,{doCode:c,doAtlas:i,clear:n}),s&&await buildGameConfig(l),r&&await startWatchMode(l,{doCode:c,doAtlas:i,doGameConfig:s})}async function build(e,{doCode:o,doAtlas:t,clear:a,hint:n={}}){const r=Date.now();let s;try{s=loadProjectConfig(e)}catch(e){return void console.error(`[ERROR] Cannot load .laya config: ${e.message}`.red)}const c=path.dirname(e),i=path.join(e,"pages"),l=path.join(c,s.codeExportPath,"layaMaxUI.ts"),d=path.join(e,".custom-cmd-cache"),g=path.join(d,"code-cache.json"),p=path.join(d,"atlas-cache.json"),h=path.join(d,"ani-cache.json"),u=o&&!n.atlasOnly,y=t&&!n.codeOnly,f=[],m=!!n&&Object.keys(n).length>0?()=>{}:e=>console.log(` ${e}`);u&&f.push(generateUICode({pagesDir:i,outputPath:l,cachePath:g,projectPath:c,resExportPath:path.join(c,s.resExportPath),clear:a,changedScenes:n.changedScenes||null,log:m}).then(e=>{console.log(`[UI] 📄 UI Code: ${String(e.built).green} built, ${e.skipped} skipped`)}).catch(e=>{console.error(`[UI] ${"❌ CODE ERROR".red}: ${e.message}`)})),y&&f.push((async()=>{try{const o=path.join(e,"styles.xml"),t=loadStyles(o);await generateAtlas({styleMap:t,projectConfig:s,assetDir:path.join(e,"assets"),outputDir:path.join(c,s.resExportPath),cachePath:p,clear:a,changedDirs:n.changedDirs||null,log:m}),console.log(`[UI] 🖼 Atlas: ${"done".green}`)}catch(e){console.error(`[UI] ${"❌ ATLAS ERROR".red}: ${e.message}`)}})()),f.push(generateAni({pagesDir:i,outputDir:path.join(c,s.resExportPath),cachePath:h,clear:a,log:m}).then(e=>{e.copied>0&&console.log(`[UI] 🎞 Ani: ${String(e.copied).green} copied, ${e.skipped} skipped`)}).catch(e=>{console.error(`[UI] ${"❌ ANI ERROR".red}: ${e.message}`)})),await Promise.all(f);const C=((Date.now()-r)/1e3).toFixed(2),j=(new Date).toLocaleTimeString();console.log(`[UI] ✅ ${j} — Build complete in ${C}s\n`)}async function buildGameConfig(e){let o;try{o=loadProjectConfig(e)}catch(e){return void console.error(`[GameConfig] ${"❌ ERROR loading .laya".red}: ${e.message}`)}const t=path.dirname(e),a=path.join(e,"pages"),n=path.join(t,"src","GameConfig.ts");try{const e=await generateGameConfig({pagesDir:a,outputPath:n,projectConfig:o,log:()=>{}});e.written&&console.log(`[UI] ⚙ GameConfig: ${"Updated".green} (${e.total} scripts)`)}catch(e){console.error(`[UI] ${"❌ GAME-CONFIG ERROR".red}: ${e.message}`)}}async function startWatchMode(e,{doCode:o,doAtlas:t,doGameConfig:a}){let n;try{n=require("chokidar")}catch{console.error("[WATCH] chokidar not installed. Run: npm install chokidar".red),process.exit(1)}const r=path.join(e,"pages"),s=path.join(e,"assets"),c=path.join(e,"styles.xml"),i=path.join(e,"pageStyles.xml");console.log("[UI] 👀 Watching pages/ & assets/ — Ctrl+C để dừng"),console.log("[UI] .scene → code | image → atlas | .xml → full rebuild\n");let l=null,d=new Set,g=new Set,p=new Set,h=!1;function u(){clearTimeout(l),l=setTimeout(async()=>{const n=[...d],r=[...g],i=[...p],l=h;d.clear(),g.clear(),p.clear(),h=!1;const u=[...n,...r,...i,...l?[c]:[]];if(0===u.length)return;if(console.log("\n[UI] 📝 Đã sửa:"),u.map(o=>path.relative(e,o).replace(/\\/g,"/")).forEach(e=>console.log(`[UI] • ${e}`)),l)return console.log("[UI] ↻ XML config changed — full rebuild..."),void await build(e,{doCode:o,doAtlas:t,clear:!1,hint:{}});const y=o&&n.length>0,f=t&&i.length>0;if(!y&&!f)return;let m=null;f&&(m=new Set(i.map(e=>path.relative(s,path.dirname(e)).replace(/\\/g,"/"))));let C=null;y&&(C=new Set(n)),await build(e,{doCode:o,doAtlas:t,clear:!1,hint:{codeOnly:y&&!f,atlasOnly:f&&!y,changedDirs:m,changedScenes:C}}),a&&(n.length>0||r.length>0)&&await buildGameConfig(e)},300)}function y(e){d.add(e),u()}function f(e){g.add(e),u()}function m(e){p.add(e),u()}function C(e){e===c&&invalidateStyles(),e===i&&invalidatePages(),h=!0,u()}const j={ignoreInitial:!0,usePolling:!1};o&&n.watch(path.join(r,"**","*.scene"),j).on("add",y).on("change",y).on("unlink",y).on("error",e=>console.error(`[WATCH ERROR] ${e}`.red)),a&&n.watch([path.join(r,"**","*.scene"),path.join(r,"**","*.prefab")],j).on("add",e=>e.endsWith(".prefab")?f(e):y(e)).on("change",e=>e.endsWith(".prefab")?f(e):y(e)).on("unlink",e=>e.endsWith(".prefab")?f(e):y(e)).on("error",e=>console.error(`[WATCH ERROR] ${e}`.red)),t&&n.watch([`${s}/**/*.png`,`${s}/**/*.jpg`],j).on("add",m).on("change",m).on("unlink",m).on("error",e=>console.error(`[WATCH ERROR] ${e}`.red)),n.watch(`${r}/**/*.ani`,j).on("add",e=>{p.add(e),u()}).on("change",e=>{p.add(e),u()}).on("error",e=>console.error(`[WATCH ERROR] ${e}`.red)),n.watch([c,i],j).on("add",C).on("change",C).on("error",e=>console.error(`[WATCH ERROR] ${e}`.red))}async function runGameConfig(e){const o=path.resolve(e.project);let t;fs.existsSync(o)||(console.error(`[ERROR] Project directory not found: ${o}`.red),process.exit(1));try{t=loadProjectConfig(o)}catch(e){console.error(`[ERROR] Cannot load .laya config: ${e.message}`.red),process.exit(1)}const a=path.dirname(o),n=path.join(o,"pages"),r=e.output?path.resolve(e.output):path.join(a,"src","GameConfig.ts");console.log(`\n${"⚡ Custom LayaAir2 Build Tool".cyan.bold}`),console.log(`[GameConfig] Project: ${o.gray}`),console.log(`[GameConfig] Output: ${r.gray}`),console.log("");const s=Date.now();try{const e=await generateGameConfig({pagesDir:n,outputPath:r,projectConfig:t,log:e=>console.log(` ${e}`)}),o=((Date.now()-s)/1e3).toFixed(2),a=(new Date).toLocaleTimeString(),c=e.written?"Updated".green:"No changes".gray;console.log(`[GameConfig] ✅ ${a} — ${c} (${e.total} scripts) in ${o}s\n`)}catch(e){console.error(`[GameConfig] ${"❌ ERROR".red}: ${e.message}`),process.exit(1)}}program.name("tyhuynh-laya-cmd").description("Custom LayaAir 2 fast incremental UI build tool").version("1.0.1"),program.command("ui").description("Export UI code and/or atlas").option("-p, --project <dir>","Path to laya/ directory (auto-detected if omitted)",null).option("-a, --atlas","Generate atlas sprite sheets",!1).option("-d, --code","Generate TypeScript UI code",!1).option("-g, --game-config","Generate GameConfig.ts",!1).option("-c, --clear","Clear cache (force full rebuild)",!1).option("-w, --watch","Watch mode",!1).action(async e=>{e.project||(e.project=autoDetectLayaDir(),e.project||(console.error("[ERROR] Cannot find laya/ directory. Use -p <dir> to specify manually.".red),process.exit(1)),console.log(` Auto-detected: ${e.project.gray}`)),await runUI(e)}),program.command("migrate").description("Migrate stray assets from bin/ into laya/assets/ and update styles.xml").option("-p, --project <dir>","Path to laya/ directory (auto-detected if omitted)",null).option("--dry-run","Preview changes without writing anything",!1).action(e=>{const o=[];e.dryRun&&o.push("--dry-run"),e.project&&o.push("-p",e.project);const{spawnSync:t}=require("child_process"),a=require.resolve("./scripts/migrate-stray-assets.js"),n=t(process.execPath,[a,...o],{stdio:"inherit"});process.exit(n.status||0)}),program.command("game-config").description("Generate src/GameConfig.ts by scanning all .scene files for Script components").option("-p, --project <dir>","Path to laya/ directory (auto-detected if omitted)",null).option("-o, --output <file>","Output path for GameConfig.ts (default: <projectRoot>/src/GameConfig.ts)",null).action(async e=>{e.project||(e.project=autoDetectLayaDir(),e.project||(console.error("[ERROR] Cannot find laya/ directory. Use -p <dir> to specify manually.".red),process.exit(1)),console.log(` Auto-detected: ${e.project.gray}`)),await runGameConfig(e)}),program.parse(process.argv);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const path=require("path"),fs=require("fs-extra"),{getMtime:getMtime,collectFiles:collectFiles,writeIfChanged:writeIfChanged}=require("../utils/FileUtils");async function generateAni({pagesDir:e,outputDir:t,cachePath:i,clear:n=!1,log:r=console.log}){const s=collectFiles(e,".ani");if(0===s.length)return{copied:0,skipped:0};let c={};if(!n)try{c=fs.readJsonSync(i)}catch{}let a=0,l=0;for(const i of s){const s=path.relative(e,i).replace(/\\/g,"/"),o=path.join(t,s),p=getMtime(i);n||!c[s]||c[s]!==p||!fs.existsSync(o)?(fs.ensureDirSync(path.dirname(o)),fs.copyFileSync(i,o),c[s]=p,a++,r(`[AniGen] ✓ ${s}`)):l++}return fs.ensureDirSync(path.dirname(i)),writeIfChanged(i,JSON.stringify(c,null,2)),{copied:a,skipped:l}}module.exports={generateAni:generateAni};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tyhuynh-laya-cmd",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "Custom LayaAir 2 UI build tool - fast incremental atlas and UI code generation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -43,4 +43,4 @@
|
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"terser": "^5.46.1"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|