@magicpixelart/cli 0.5.11 → 0.5.12
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/CHANGELOG.md +10 -0
- package/dist/commands/connect.js +1 -1
- package/dist/commands/push.js +1 -1
- package/dist/commands/search.js +1 -1
- package/dist/commands/sync.js +8 -2
- package/dist/util/gameScan.js +51 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.12] — 2026-08-26
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Watch counting no longer looks hung.** Sprite counting used to stay on
|
|
15
|
+
`Counting sprites in your game…` with no numbers until the first PNG, which
|
|
16
|
+
on a large Unity tree can take a long time. The spinner now updates as
|
|
17
|
+
folders are visited (`0 sprites · 128 folders`) and walks `Assets/` before
|
|
18
|
+
sibling project folders so the sprite count appears sooner.
|
|
19
|
+
|
|
10
20
|
## [0.5.11] — 2026-08-26
|
|
11
21
|
|
|
12
22
|
### Changed
|
package/dist/commands/connect.js
CHANGED
|
@@ -29,7 +29,7 @@ export async function connectCommand(glob) {
|
|
|
29
29
|
}
|
|
30
30
|
const spinner = ora({ text: countingSpritesText(0), spinner: 'dots' }).start();
|
|
31
31
|
const index = await indexGamePngs(kind, process.cwd(), config.outDir, {
|
|
32
|
-
onProgress: (
|
|
32
|
+
onProgress: (p) => { spinner.text = countingSpritesText(p); },
|
|
33
33
|
});
|
|
34
34
|
spinner.stop();
|
|
35
35
|
if (index.capped) {
|
package/dist/commands/push.js
CHANGED
|
@@ -47,7 +47,7 @@ export async function runPushWith(config, state, opts = {}) {
|
|
|
47
47
|
const index = opts.gameIndex
|
|
48
48
|
?? (isEngineKind(kind)
|
|
49
49
|
? await indexGamePngs(kind, process.cwd(), config.outDir, {
|
|
50
|
-
onProgress: spinner ? (
|
|
50
|
+
onProgress: spinner ? (p) => { spinner.text = countingSpritesText(p); } : undefined,
|
|
51
51
|
})
|
|
52
52
|
: { files: [], capped: false });
|
|
53
53
|
const matched = matchConnectGlobs(index, config.connect ?? []);
|
package/dist/commands/search.js
CHANGED
|
@@ -28,7 +28,7 @@ export async function searchCommand(query) {
|
|
|
28
28
|
}
|
|
29
29
|
const spinner = ora({ text: countingSpritesText(0), spinner: 'dots' }).start();
|
|
30
30
|
const index = await indexGamePngs(kind, process.cwd(), outDir, {
|
|
31
|
-
onProgress: (
|
|
31
|
+
onProgress: (p) => { spinner.text = countingSpritesText(p); },
|
|
32
32
|
});
|
|
33
33
|
spinner.stop();
|
|
34
34
|
if (index.capped) {
|
package/dist/commands/sync.js
CHANGED
|
@@ -68,7 +68,7 @@ async function watchLoop(opts) {
|
|
|
68
68
|
/* header is cosmetic */
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
const header = await loadWatchHeader(countSpinner ? (
|
|
71
|
+
const header = await loadWatchHeader(countSpinner ? (p) => { countSpinner.text = countingSpritesText(p); } : undefined);
|
|
72
72
|
if (countSpinner) {
|
|
73
73
|
if (header.line)
|
|
74
74
|
countSpinner.succeed(header.line.trim());
|
|
@@ -410,7 +410,13 @@ async function runOnce(opts, runOpts = {}) {
|
|
|
410
410
|
const gameIndex = runOpts.gameIndex
|
|
411
411
|
?? (isEngineKind(projectKind)
|
|
412
412
|
? await indexGamePngs(projectKind, process.cwd(), config.outDir, {
|
|
413
|
-
onProgress: (
|
|
413
|
+
onProgress: (p) => {
|
|
414
|
+
const n = p.pngs.toLocaleString('en-US');
|
|
415
|
+
const f = p.folders.toLocaleString('en-US');
|
|
416
|
+
onStatus?.(p.folders > 0
|
|
417
|
+
? `Looking through your game sprites… ${n} sprites · ${f} folders`
|
|
418
|
+
: `Looking through your game sprites… ${n}`);
|
|
419
|
+
},
|
|
414
420
|
})
|
|
415
421
|
: { files: [], capped: false });
|
|
416
422
|
const connected = matchConnectGlobs(gameIndex, config.connect ?? []);
|
package/dist/util/gameScan.js
CHANGED
|
@@ -23,6 +23,10 @@ export const GAME_SCAN_SKIP_DIRS = new Set([
|
|
|
23
23
|
'streamingassets',
|
|
24
24
|
'node_modules',
|
|
25
25
|
'magicpixel',
|
|
26
|
+
'build',
|
|
27
|
+
'builds',
|
|
28
|
+
'recordings',
|
|
29
|
+
'memorycaptures',
|
|
26
30
|
]);
|
|
27
31
|
/**
|
|
28
32
|
* Dot-directories we never walk. Other hidden folders (e.g. `.SpineRaw_*`)
|
|
@@ -94,28 +98,41 @@ function toEntry(abs, cwdAbs, assetRoot) {
|
|
|
94
98
|
return null;
|
|
95
99
|
return { abs, sourceRel, adoptRel, key: adopted.path.join('/') };
|
|
96
100
|
}
|
|
101
|
+
function fmtCount(n, noun) {
|
|
102
|
+
return `${n.toLocaleString('en-US')} ${noun}${n === 1 ? '' : 's'}`;
|
|
103
|
+
}
|
|
97
104
|
/** Spinner / status copy while walking the game tree. */
|
|
98
|
-
export function countingSpritesText(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
export function countingSpritesText(progress = 0) {
|
|
106
|
+
const pngs = typeof progress === 'number' ? progress : progress.pngs;
|
|
107
|
+
const folders = typeof progress === 'number' ? 0 : progress.folders;
|
|
108
|
+
if (pngs === 0 && folders === 0)
|
|
109
|
+
return 'Counting sprites in your game…';
|
|
110
|
+
const spriteBit = fmtCount(pngs, 'sprite');
|
|
111
|
+
if (folders > 0)
|
|
112
|
+
return `Counting sprites in your game… ${spriteBit} · ${fmtCount(folders, 'folder')}`;
|
|
113
|
+
return `Counting sprites in your game… ${spriteBit}`;
|
|
102
114
|
}
|
|
103
115
|
function bindScanProgress(onProgress) {
|
|
104
116
|
if (!onProgress)
|
|
105
117
|
return () => { };
|
|
106
118
|
let lastAt = 0;
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
let lastPngs = -1;
|
|
120
|
+
let lastFolders = -1;
|
|
121
|
+
return (pngs, folders) => {
|
|
122
|
+
if (pngs === lastPngs && folders === lastFolders)
|
|
110
123
|
return;
|
|
111
124
|
const now = Date.now();
|
|
112
|
-
if (
|
|
125
|
+
if (lastPngs < 0 || now - lastAt >= 80) {
|
|
113
126
|
lastAt = now;
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
lastPngs = pngs;
|
|
128
|
+
lastFolders = folders;
|
|
129
|
+
onProgress({ pngs, folders });
|
|
116
130
|
}
|
|
117
131
|
};
|
|
118
132
|
}
|
|
133
|
+
function samePath(a, b) {
|
|
134
|
+
return a.replace(/\\/g, '/').toLowerCase() === b.replace(/\\/g, '/').toLowerCase();
|
|
135
|
+
}
|
|
119
136
|
/** Walk the engine tree and return index entries. Does not copy files. */
|
|
120
137
|
export async function indexGamePngs(kind, cwd = process.cwd(), outDir = '', opts = {}) {
|
|
121
138
|
const empty = { files: [], capped: false };
|
|
@@ -128,11 +145,11 @@ export async function indexGamePngs(kind, cwd = process.cwd(), outDir = '', opts
|
|
|
128
145
|
const namedRoot = gameScanRoot(kind);
|
|
129
146
|
const assetRoot = namedRoot ? resolveChildDir(cwd, namedRoot) : null;
|
|
130
147
|
const found = [];
|
|
148
|
+
const stats = { folders: 0 };
|
|
131
149
|
const io = createLimit(SCAN_WALK_CONCURRENCY);
|
|
132
150
|
const report = bindScanProgress(opts.onProgress);
|
|
133
|
-
await walkPngs(scanRoot, scanRoot, destNorm, found, MAX_GAME_INDEX + 1, io, report);
|
|
134
|
-
|
|
135
|
-
opts.onProgress?.(found.length);
|
|
151
|
+
await walkPngs(scanRoot, scanRoot, destNorm, found, MAX_GAME_INDEX + 1, io, report, stats, assetRoot);
|
|
152
|
+
opts.onProgress?.({ pngs: found.length, folders: stats.folders });
|
|
136
153
|
found.sort((a, b) => a.localeCompare(b));
|
|
137
154
|
const capped = found.length > MAX_GAME_INDEX;
|
|
138
155
|
if (capped)
|
|
@@ -161,9 +178,12 @@ export function searchGameIndex(index, query) {
|
|
|
161
178
|
return [];
|
|
162
179
|
return index.files.filter((e) => e.sourceRel.toLowerCase().includes(q) || e.key.includes(q) || e.adoptRel.toLowerCase().includes(q));
|
|
163
180
|
}
|
|
164
|
-
async function walkPngs(dir, root, destNorm, out, limit, io,
|
|
181
|
+
async function walkPngs(dir, root, destNorm, out, limit, io, onVisit, stats, preferFirst) {
|
|
165
182
|
if (out.length >= limit)
|
|
166
183
|
return;
|
|
184
|
+
// Count the folder *before* readdir so a huge listing still shows movement.
|
|
185
|
+
stats.folders++;
|
|
186
|
+
onVisit(out.length, stats.folders);
|
|
167
187
|
let entries;
|
|
168
188
|
try {
|
|
169
189
|
entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -172,7 +192,8 @@ async function walkPngs(dir, root, destNorm, out, limit, io, onFound) {
|
|
|
172
192
|
return;
|
|
173
193
|
}
|
|
174
194
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
175
|
-
const
|
|
195
|
+
const preferred = [];
|
|
196
|
+
const other = [];
|
|
176
197
|
for (const ent of entries) {
|
|
177
198
|
if (out.length >= limit)
|
|
178
199
|
return;
|
|
@@ -190,14 +211,25 @@ async function walkPngs(dir, root, destNorm, out, limit, io, onFound) {
|
|
|
190
211
|
continue;
|
|
191
212
|
if (destNorm && full.replace(/\\/g, '/').toLowerCase() === destNorm)
|
|
192
213
|
continue;
|
|
193
|
-
|
|
214
|
+
if (preferFirst && samePath(full, preferFirst))
|
|
215
|
+
preferred.push(full);
|
|
216
|
+
else
|
|
217
|
+
other.push(full);
|
|
194
218
|
}
|
|
195
219
|
else if (ent.isFile() && ent.name.toLowerCase().endsWith('.png')) {
|
|
196
220
|
out.push(full);
|
|
197
|
-
|
|
221
|
+
onVisit(out.length, stats.folders);
|
|
198
222
|
}
|
|
199
223
|
}
|
|
200
|
-
if (
|
|
224
|
+
if (out.length >= limit)
|
|
225
|
+
return;
|
|
226
|
+
// Engine asset tree first (Unity `Assets/`) so sprite counts appear before
|
|
227
|
+
// sibling folders like Builds/Editor. Nested walks keep preferFirst so a
|
|
228
|
+
// misplaced `Assets` dir is still prioritized if we ever see it.
|
|
229
|
+
for (const d of preferred) {
|
|
230
|
+
await walkPngs(d, root, destNorm, out, limit, io, onVisit, stats, null);
|
|
231
|
+
}
|
|
232
|
+
if (other.length === 0 || out.length >= limit)
|
|
201
233
|
return;
|
|
202
|
-
await Promise.all(
|
|
234
|
+
await Promise.all(other.map((d) => io(() => walkPngs(d, root, destNorm, out, limit, io, onVisit, stats, null))));
|
|
203
235
|
}
|