@magicpixelart/cli 0.5.31 → 0.5.34
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 +18 -0
- package/dist/api.js +9 -0
- package/dist/commands/push.js +1 -0
- package/dist/commands/sync.js +12 -5
- package/dist/util/pushPlan.js +1 -0
- package/dist/util/watchCopy.js +18 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ All notable changes to `@magicpixelart/cli` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [0.5.34] - 2026-10-06
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `push --force` no longer fails with "Too many sprites (max 20 per request)" when more than 20 files are retried; the retry round is now sent in small batches.
|
|
12
|
+
|
|
13
|
+
## [0.5.33] - 2026-10-06
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Local edits to game files that were never changed in MagicPixel no longer fail as "also changed in MagicPixel". Push now sends the cloud timestamp it last saw, and sync no longer records the local file as MagicPixel's copy when the server has no cached hash.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Watch header now reads "N game sprites connected" — it counts game PNGs matched by `connect`, not files waiting in MagicPixel.
|
|
20
|
+
|
|
21
|
+
## [0.5.32] - 2026-10-05
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- `sync --watch` status lines are clipped to the terminal width, so a long sprite path no longer wraps and leaves the previous status piled on screen.
|
|
25
|
+
|
|
8
26
|
## [0.5.31] - 2026-10-01
|
|
9
27
|
|
|
10
28
|
### Added
|
package/dist/api.js
CHANGED
|
@@ -423,6 +423,15 @@ function isTransientPushError(e) {
|
|
|
423
423
|
export async function pushSpritesAdaptive(sprites, retryOpts) {
|
|
424
424
|
if (sprites.length === 0)
|
|
425
425
|
return [];
|
|
426
|
+
// Never send more than one batch per request — callers (e.g. the --force
|
|
427
|
+
// retry round) may hand over every refused sprite at once.
|
|
428
|
+
if (sprites.length > PUSH_BATCH_SIZE) {
|
|
429
|
+
const out = [];
|
|
430
|
+
for (let i = 0; i < sprites.length; i += PUSH_BATCH_SIZE) {
|
|
431
|
+
out.push(...(await pushSpritesAdaptive(sprites.slice(i, i + PUSH_BATCH_SIZE), retryOpts)));
|
|
432
|
+
}
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
426
435
|
try {
|
|
427
436
|
return await pushSprites(sprites, retryOpts);
|
|
428
437
|
}
|
package/dist/commands/push.js
CHANGED
|
@@ -215,6 +215,7 @@ export async function runPushWith(config, state, opts = {}) {
|
|
|
215
215
|
assetId: a.assetId,
|
|
216
216
|
layerIdx: a.layerIdx,
|
|
217
217
|
baseSha256: a.baseSha256,
|
|
218
|
+
...(a.baseUpdatedAt ? { baseUpdatedAt: a.baseUpdatedAt } : {}),
|
|
218
219
|
flatten: opts.flatten === true,
|
|
219
220
|
...(opts.replace ? { replace: true } : {}),
|
|
220
221
|
});
|
package/dist/commands/sync.js
CHANGED
|
@@ -23,7 +23,7 @@ import { maxIsoTimestamp } from '../util/iso.js';
|
|
|
23
23
|
import { formatBytes } from '../util/format.js';
|
|
24
24
|
import { computePreviousKeyOrphans } from '../util/previousKeyOrphans.js';
|
|
25
25
|
import { cmd } from '../util/invoke.js';
|
|
26
|
-
import { formatSlowTickLine, formatWatchSpriteLine, SLOW_TICK_HEARTBEAT_MS } from '../util/watchCopy.js';
|
|
26
|
+
import { fitStatusLine, formatSlowTickLine, formatWatchSpriteLine, SLOW_TICK_HEARTBEAT_MS } from '../util/watchCopy.js';
|
|
27
27
|
import { canSkipWithoutHashing, decidePull, hasNewExplicitRelease } from '../util/pullDecision.js';
|
|
28
28
|
import { hasUnpushedLocalEdit } from '../util/localEdit.js';
|
|
29
29
|
import { shouldReconcile } from '../util/reconcile.js';
|
|
@@ -227,7 +227,7 @@ async function watchLoop(opts) {
|
|
|
227
227
|
const onStatus = (msg) => {
|
|
228
228
|
if (opts.quiet)
|
|
229
229
|
return;
|
|
230
|
-
|
|
230
|
+
writeStatusLine(msg);
|
|
231
231
|
};
|
|
232
232
|
// Heartbeat: a tick that legitimately takes minutes (huge first pull, slow
|
|
233
233
|
// link) must not look like the silent hang a timeout-less fetch used to
|
|
@@ -307,7 +307,7 @@ async function watchLoop(opts) {
|
|
|
307
307
|
printChanges(r, /* indent */ ' ');
|
|
308
308
|
}
|
|
309
309
|
else {
|
|
310
|
-
|
|
310
|
+
writeStatusLine(`Waiting for edits… (${r.unchanged} up to date)`);
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
313
|
catch (e) {
|
|
@@ -1377,12 +1377,14 @@ async function runOnce(opts, runOpts = {}) {
|
|
|
1377
1377
|
// After a write, the actual downloaded bytes are the disk/cloud baseline.
|
|
1378
1378
|
// The manifest hash may be null or represent a cached composite while the
|
|
1379
1379
|
// download endpoint serves storage bytes for a single-artboard document.
|
|
1380
|
+
const prev = nextSynced[entry.key];
|
|
1381
|
+
// Never let the local game file stand in for MagicPixel's composite when
|
|
1382
|
+
// the manifest has no cached hash: keep the last real cloud baseline.
|
|
1380
1383
|
const sha = writtenKeys.has(entry.key)
|
|
1381
1384
|
? await fileSha256(pathFor(entry))
|
|
1382
|
-
: (entry.sha256 ?? (await fileSha256(pathFor(entry))));
|
|
1385
|
+
: (entry.sha256 ?? prev?.sha256 ?? (await fileSha256(pathFor(entry))));
|
|
1383
1386
|
if (!sha)
|
|
1384
1387
|
continue;
|
|
1385
|
-
const prev = nextSynced[entry.key];
|
|
1386
1388
|
const diskSha256 = writtenKeys.has(entry.key)
|
|
1387
1389
|
? sha
|
|
1388
1390
|
: (shaByEntryId.get(entry.id) ?? prev?.diskSha256 ?? sha);
|
|
@@ -1575,6 +1577,11 @@ function progressText(done, total, bytes) {
|
|
|
1575
1577
|
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
1576
1578
|
return `${bar} ${done}/${total} ${kleur.dim(formatBytes(bytes))}`;
|
|
1577
1579
|
}
|
|
1580
|
+
/** Rewrite the watcher's single status row, clipped so it never wraps. */
|
|
1581
|
+
function writeStatusLine(msg) {
|
|
1582
|
+
const line = fitStatusLine(`${timestamp()} ${msg}`, process.stdout.columns);
|
|
1583
|
+
process.stdout.write(`\r\x1b[2K${kleur.dim(line)}`);
|
|
1584
|
+
}
|
|
1578
1585
|
function timestamp() {
|
|
1579
1586
|
const d = new Date();
|
|
1580
1587
|
return `[${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}]`;
|
package/dist/util/pushPlan.js
CHANGED
|
@@ -151,6 +151,7 @@ export function planPush(candidates, synced, opts = {}) {
|
|
|
151
151
|
// A later explicit `push` can then safely keep the local copy without
|
|
152
152
|
// bypassing the server's optimistic-concurrency check.
|
|
153
153
|
baseSha256: known.pendingCloudSha256 ?? known.sha256,
|
|
154
|
+
...(known.cloudUpdatedAt && !known.pendingCloudSha256 ? { baseUpdatedAt: known.cloudUpdatedAt } : {}),
|
|
154
155
|
});
|
|
155
156
|
continue;
|
|
156
157
|
}
|
package/dist/util/watchCopy.js
CHANGED
|
@@ -13,10 +13,10 @@ export function formatWatchSpriteLine(c) {
|
|
|
13
13
|
if (set === 0 && pulled === 0)
|
|
14
14
|
return null;
|
|
15
15
|
if (set > 0 && pulled > 0 && set !== pulled) {
|
|
16
|
-
return ` Sprites: ${fmt(set)}
|
|
16
|
+
return ` Sprites: ${fmt(set)} game sprites connected · ${fmt(pulled)} last pulled from MagicPixel`;
|
|
17
17
|
}
|
|
18
18
|
if (set > 0)
|
|
19
|
-
return ` Sprites: ${fmt(set)}
|
|
19
|
+
return ` Sprites: ${fmt(set)} game sprites connected`;
|
|
20
20
|
return ` Sprites: ${fmt(pulled)}`;
|
|
21
21
|
}
|
|
22
22
|
/** How long a single watch tick may run before the heartbeat starts printing. */
|
|
@@ -39,3 +39,19 @@ export function formatSlowTickLine(elapsedSec, lastStatus) {
|
|
|
39
39
|
? `${what} — still working (${elapsed})`
|
|
40
40
|
: `Still working (${elapsed})`;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Fit a carriage-return status line to one terminal row. `\r\x1b[2K` only
|
|
44
|
+
* clears the row the cursor is on, so a line that wraps leaves its first rows
|
|
45
|
+
* behind and the next status prints after them (long "Looking through your
|
|
46
|
+
* game sprites… <folder>" lines piled up this way). No width (not a TTY) →
|
|
47
|
+
* unchanged.
|
|
48
|
+
*/
|
|
49
|
+
export function fitStatusLine(line, columns) {
|
|
50
|
+
if (!columns || columns < 2)
|
|
51
|
+
return line;
|
|
52
|
+
const max = columns - 1; // leave the last cell free so the cursor can't wrap
|
|
53
|
+
const chars = Array.from(line);
|
|
54
|
+
if (chars.length <= max)
|
|
55
|
+
return line;
|
|
56
|
+
return `${chars.slice(0, max - 1).join('')}…`;
|
|
57
|
+
}
|