tyhuynh-laya-cmd 1.0.5 → 1.0.7
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 -0
- package/dist/index.js +1 -1
- package/dist/scripts/migrate-stray-assets.js +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +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)
|
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"),{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,
|
|
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"),{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,r=o;for(;r!==a;){const o=path.join(r,"laya");if(e(o))return o;a=r,r=path.dirname(r)}return null}async function runUI(e){const{project:o,atlas:t,code:a,clear:r,watch:n}=e,s=a||!t&&!a,l=t||!t&&!a,c=path.resolve(o);fs.existsSync(c)||(console.error(`[ERROR] Project directory not found: ${c}`.red),process.exit(1)),n?(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: ${c.gray}`),console.log(`[UI] Mode: ${[s&&"CODE",l&&"ATLAS"].filter(Boolean).join(" + ").yellow}`),r&&console.log(`[UI] Cache: ${"CLEARED".red}`),console.log(""),await build(c,{doCode:s,doAtlas:l,clear:r}),n&&await startWatchMode(c,{doCode:s,doAtlas:l})}async function build(e,{doCode:o,doAtlas:t,clear:a,hint:r={}}){const n=Date.now();let s;try{s=loadProjectConfig(e)}catch(e){return void console.error(`[ERROR] Cannot load .laya config: ${e.message}`.red)}const l=path.dirname(e),c=path.join(e,"pages"),i=path.join(l,s.codeExportPath,"layaMaxUI.ts"),d=path.join(e,".custom-cmd-cache"),p=path.join(d,"code-cache.json"),h=path.join(d,"atlas-cache.json"),u=o&&!r.atlasOnly,g=t&&!r.codeOnly,y=[],m=!!r&&Object.keys(r).length>0?()=>{}:e=>console.log(` ${e}`);u&&y.push(generateUICode({pagesDir:c,outputPath:i,cachePath:p,projectPath:l,resExportPath:path.join(l,s.resExportPath),clear:a,changedScenes:r.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}`)})),g&&y.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(l,s.resExportPath),cachePath:h,clear:a,changedDirs:r.changedDirs||null,log:m}),console.log(`[UI] 🖼 Atlas: ${"done".green}`)}catch(e){console.error(`[UI] ${"❌ ATLAS ERROR".red}: ${e.message}`)}})()),await Promise.all(y);const f=((Date.now()-n)/1e3).toFixed(2),j=(new Date).toLocaleTimeString();console.log(`[UI] ✅ ${j} — Build complete in ${f}s\n`)}async function startWatchMode(e,{doCode:o,doAtlas:t}){let a;try{a=require("chokidar")}catch{console.error("[WATCH] chokidar not installed. Run: npm install chokidar".red),process.exit(1)}const r=path.join(e,"pages"),n=path.join(e,"assets"),s=path.join(e,"styles.xml"),l=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 c=null,i=new Set,d=new Set,p=!1;function h(){clearTimeout(c),c=setTimeout(async()=>{const a=[...i],r=[...d],l=p;i.clear(),d.clear(),p=!1;const c=[...a,...r,...l?[s]:[]];if(0===c.length)return;if(console.log("\n[UI] 📝 Đã sửa:"),c.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 h=o&&a.length>0,u=t&&r.length>0;if(!h&&!u)return;let g=null;u&&(g=new Set(r.map(e=>path.relative(n,path.dirname(e)).replace(/\\/g,"/"))));let y=null;h&&(y=new Set(a)),await build(e,{doCode:o,doAtlas:t,clear:!1,hint:{codeOnly:h&&!u,atlasOnly:u&&!h,changedDirs:g,changedScenes:y}})},300)}function u(e){i.add(e),h()}function g(e){d.add(e),h()}function y(e){e===s&&invalidateStyles(),e===l&&invalidatePages(),p=!0,h()}const m={ignoreInitial:!0,usePolling:!1};o&&a.watch(path.join(r,"**","*.scene"),m).on("add",u).on("change",u).on("unlink",u).on("error",e=>console.error(`[WATCH ERROR] ${e}`.red)),t&&a.watch([`${n}/**/*.png`,`${n}/**/*.jpg`],m).on("add",g).on("change",g).on("unlink",g).on("error",e=>console.error(`[WATCH ERROR] ${e}`.red)),a.watch([s,l],m).on("add",y).on("change",y).on("error",e=>console.error(`[WATCH ERROR] ${e}`.red))}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("-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"),r=t(process.execPath,[a,...o],{stdio:"inherit"});process.exit(r.status??0)}),program.parse(process.argv);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";const path=require("path"),fs=require("fs-extra"),args=process.argv.slice(2),dryRun=args.includes("--dry-run"),pIdx=args.indexOf("-p")
|
|
2
|
+
"use strict";const path=require("path"),fs=require("fs-extra");function autoDetectLayaDir(){const e=e=>{const s=path.join(e,".laya");return fs.existsSync(s)&&fs.statSync(s).isFile()};let s=process.cwd();if(e(s))return s;const t=path.join(s,"laya");if(e(t))return t;let n=null,o=s;for(;o!==n;){const s=path.join(o,"laya");if(e(s))return s;n=o,o=path.dirname(o)}return null}const args=process.argv.slice(2),dryRun=args.includes("--dry-run"),pIdx=args.indexOf("-p");let layaDir;-1!==pIdx?layaDir=path.resolve(args[pIdx+1]):(layaDir=autoDetectLayaDir(),layaDir||(console.error("\n[migrate] ❌ Không tìm thấy thư mục laya/."),console.error("[migrate] Hãy chạy lệnh này từ thư mục dự án client/"),console.error("[migrate] hoặc dùng: tyhuynh-laya-cmd migrate -p <path/to/laya>\n"),process.exit(1)),console.log(`[migrate] Auto-detected: ${layaDir}`));const assetsDir=path.join(layaDir,"assets"),binDir=path.join(layaDir,"../bin"),stylesXml=path.join(layaDir,"styles.xml");console.log("\n🔍 Migrate stray assets"+(dryRun?" [DRY RUN]":"")),console.log(` laya : ${layaDir}`),console.log(` assets : ${assetsDir}`),console.log(` bin : ${binDir}\n`);const IMAGE_EXTS=new Set([".png",".jpg",".jpeg",".webp"]);function collectImages(e){const s=[];return function e(t){let n;try{n=fs.readdirSync(t,{withFileTypes:!0})}catch{return}for(const o of n){const n=path.join(t,o.name);o.isDirectory()?e(n):IMAGE_EXTS.has(path.extname(o.name).toLowerCase())&&s.push(n)}}(e),s}const binResDir=path.join(binDir,"res","image");fs.existsSync(binResDir)||(console.error(`❌ bin/res/image not found: ${binResDir}`),process.exit(1));const binImages=collectImages(binResDir),strayFiles=[],atlasBasenamesByDir=new Map;function collectAtlasInfo(e){let s;try{s=fs.readdirSync(e,{withFileTypes:!0})}catch{return}for(const t of s){const s=path.join(e,t.name);if(t.isDirectory())collectAtlasInfo(s);else if(".atlas"===path.extname(t.name).toLowerCase()){const s=path.basename(t.name,".atlas");atlasBasenamesByDir.has(e)||atlasBasenamesByDir.set(e,new Set),atlasBasenamesByDir.get(e).add(s)}}}function isAtlasPage(e){const s=path.dirname(e),t=atlasBasenamesByDir.get(s);if(!t)return!1;const n=path.basename(e,path.extname(e));for(const e of t){if(n===e)return!0;if(n.startsWith(e)&&/^\d+$/.test(n.slice(e.length)))return!0}return!1}collectAtlasInfo(binDir);for(const e of binImages){const s=path.relative(binDir,e).replace(/\\/g,"/"),t=path.join(assetsDir,s);fs.existsSync(t)||(isAtlasPage(e)||strayFiles.push({rel:s,binAbs:e,assetsAbs:t}))}0===strayFiles.length&&(console.log("✅ No stray files found — everything is already in assets/"),process.exit(0)),console.log(`Found ${strayFiles.length} stray file(s):\n`);for(const{rel:e}of strayFiles)console.log(` + ${e}`);console.log("");const dirGroups=new Map,preExistingAssetDirs=new Set;for(const{rel:e}of strayFiles){const s=e.substring(0,e.lastIndexOf("/"));dirGroups.has(s)||dirGroups.set(s,[]),dirGroups.get(s).push(e),fs.existsSync(path.join(assetsDir,s))&&preExistingAssetDirs.add(s)}if(dryRun)console.log("📦 (Dry run) Skipping moving files...");else{console.log("📦 Moving files to laya/assets/ ...");for(const{rel:e,binAbs:s,assetsAbs:t}of strayFiles)fs.ensureDirSync(path.dirname(t)),fs.copyFileSync(s,t),console.log(` ✓ ${e}`)}console.log("\n📝 "+(dryRun?"(Dry run preview) ":"")+'Updating styles.xml with pack="2" entries ...'),fs.existsSync(stylesXml)||(console.error(`❌ styles.xml not found: ${stylesXml}`),process.exit(1));let xmlContent=fs.readFileSync(stylesXml,"utf-8");const existingNames=new Set;for(const e of xmlContent.matchAll(/name="([^"]+)"/g))existingNames.add(e[1]);const newItems=[];function makeItem(e){return` <item name="${e}" type="Sprite" compress="0" pack="2" quality="80" props="" picType="0" scale="0"/>`}for(const[e,s]of dirGroups){if(!preExistingAssetDirs.has(e))existingNames.has(e)?console.log(` ℹ Skipped dir (already in styles.xml): ${e}`):(newItems.push(makeItem(e)),console.log(` + ${e}/ (new directory → dir rule)`));else for(const e of s)existingNames.has(e)?console.log(` ℹ Skipped (already in styles.xml): ${e}`):(newItems.push(makeItem(e)),console.log(` + ${e}`))}if(newItems.length>0){const e=xmlContent.lastIndexOf("</res>");-1===e&&(console.error("❌ Could not find </res> in styles.xml"),process.exit(1));const s=newItems.join("\n")+"\n";xmlContent=xmlContent.slice(0,e)+s+xmlContent.slice(e),dryRun&&(console.log(`\n✅ (Dry run preview) styles.xml WOULD BE updated with ${newItems.length} new entries.`),process.exit(0)),fs.writeFileSync(stylesXml,xmlContent,"utf-8"),console.log(`\n✅ styles.xml updated with ${newItems.length} new entries.`)}else console.log("\n✅ styles.xml already up-to-date (all entries existed)."),dryRun&&process.exit(0);console.log("\nDone! Next steps:\n 1. Run: npm run build:clean\n → AtlasGen will copy the new assets to bin/ going forward\n 2. Verify the game still works (files are already in bin/)\n 3. Commit laya/assets/ and styles.xml to version control\n");
|