@magicpixelart/cli 0.5.32 → 0.5.35
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 +19 -0
- package/README.md +11 -1
- package/dist/api.js +9 -0
- package/dist/commands/connect.js +4 -12
- package/dist/commands/push.js +4 -9
- package/dist/commands/resync.js +2 -7
- package/dist/commands/search.js +1 -4
- package/dist/commands/sync.js +9 -14
- package/dist/util/gameScan.js +10 -22
- package/dist/util/pushPlan.js +1 -0
- package/dist/util/watchCopy.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.35] - 2026-10-07
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- `exclude` now also stops uploads: game PNGs matching an exclude glob are never pushed, even when `connect` is `**`.
|
|
12
|
+
- Removed the 10,000-PNG game index / connect cap: every sprite in the project is now indexed, connected and pushed. The server's daily allowance still applies; re-running resumes where it stopped.
|
|
13
|
+
|
|
14
|
+
## [0.5.34] - 2026-10-06
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- `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.
|
|
18
|
+
|
|
19
|
+
## [0.5.33] - 2026-10-06
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- 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.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Watch header now reads "N game sprites connected" — it counts game PNGs matched by `connect`, not files waiting in MagicPixel.
|
|
26
|
+
|
|
8
27
|
## [0.5.32] - 2026-10-05
|
|
9
28
|
|
|
10
29
|
### Fixed
|
package/README.md
CHANGED
|
@@ -150,13 +150,23 @@ npx @magicpixelart/cli sync --watch --only Runtime/Sprites/Entities/decorations
|
|
|
150
150
|
|
|
151
151
|
Only that folder is pushed, pulled, or pruned. Every other file stays exactly as it is. Repeat `--only` for more folders, or set `"syncOnly"` in `magicpixel.json`. Changing the folder list triggers one full check. `sync <folder>` without `--only` stops with an error instead of syncing everything.
|
|
152
152
|
|
|
153
|
+
## Keep a folder out of MagicPixel
|
|
154
|
+
|
|
155
|
+
To leave game files on disk but out of your library, add them to `exclude` in `magicpixel.json`:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{ "exclude": [".SpineRaw/rabbit_nft/**"] }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Excluded files are never uploaded, even when `connect` is `**`, and the CLI never deletes them from your game. Then delete the folder in the MagicPixel library (it goes to Trash) and it stays gone.
|
|
162
|
+
|
|
153
163
|
## Config (`magicpixel.json`)
|
|
154
164
|
|
|
155
165
|
| Field | Type | Default | Meaning |
|
|
156
166
|
| ---------- | ---------- | ----------------------- | ------------------------------------------------ |
|
|
157
167
|
| `outDir` | `string` | framework-dependent | Where PNGs (and `index.ts`) are written. |
|
|
158
168
|
| `include` | `string[]` | `["**/*"]` | Globs (picomatch) matched against `folder/slug`. |
|
|
159
|
-
| `exclude` | `string[]` | `[]` | Globs to
|
|
169
|
+
| `exclude` | `string[]` | `[]` | Globs to skip in both directions: never downloaded, and game PNGs matching them are never uploaded (wins over `connect`). |
|
|
160
170
|
| `connect` | `string[]` | `["**"]` on engines | Game PNG globs to keep in Connected. Empty on JS projects. |
|
|
161
171
|
| `emitIndex`| `boolean` | `true` | Emit `<outDir>/index.ts` with typed asset map. |
|
|
162
172
|
| `unityPpu` | `number?` | `32` | Unity only: pixels-per-unit in generated `.meta`. |
|
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/connect.js
CHANGED
|
@@ -3,7 +3,7 @@ import ora from 'ora';
|
|
|
3
3
|
import { loadConfig, saveConfig } from '../config.js';
|
|
4
4
|
import { assertSafeGlob } from '../util/security.js';
|
|
5
5
|
import { detectProjectKind, isEngineKind } from '../util/framework.js';
|
|
6
|
-
import { indexGamePngs, matchConnectGlobs,
|
|
6
|
+
import { indexGamePngs, matchConnectGlobs, countingSpritesText } from '../util/gameScan.js';
|
|
7
7
|
import { isAllSpritesGlob, nextConnectGlobs } from '../util/engineConnect.js';
|
|
8
8
|
import { cmd } from '../util/invoke.js';
|
|
9
9
|
import { runPush } from './push.js';
|
|
@@ -36,23 +36,15 @@ export async function connectCommand(glob) {
|
|
|
36
36
|
onProgress: (p) => { spinner.text = countingSpritesText(p); },
|
|
37
37
|
});
|
|
38
38
|
spinner.stop();
|
|
39
|
-
|
|
40
|
-
console.log(kleur.yellow(`! ${GAME_INDEX_CAP_HINT}`));
|
|
41
|
-
}
|
|
42
|
-
const matched = matchConnectGlobs(index, config.connect);
|
|
43
|
-
if (matched.capped) {
|
|
44
|
-
console.log(kleur.yellow(`! ${connectCapMessage(matched.total, matched.entries.length)}`));
|
|
45
|
-
}
|
|
39
|
+
const matched = matchConnectGlobs(index, config.connect, config.exclude);
|
|
46
40
|
if (matched.entries.length === 0) {
|
|
47
41
|
console.log(kleur.yellow(` No PNGs matched. Try \`${cmd('search')} <name>\` to see indexed paths.`));
|
|
48
42
|
return;
|
|
49
43
|
}
|
|
50
44
|
const n = matched.entries.length;
|
|
51
|
-
console.log(kleur.dim(` ${n} sprite${n === 1 ? '' : 's'} to sync (of ${index.files.length} in your game
|
|
52
|
-
(matched.capped ? `, ${matched.total} matched` : '') +
|
|
53
|
-
`).`));
|
|
45
|
+
console.log(kleur.dim(` ${n} sprite${n === 1 ? '' : 's'} to sync (of ${index.files.length} in your game).`));
|
|
54
46
|
if (n >= 200) {
|
|
55
|
-
console.log(kleur.dim(`
|
|
47
|
+
console.log(kleur.dim(` Found ${n} sprites. Only new or changed files upload.`));
|
|
56
48
|
}
|
|
57
49
|
await runPush({ gameIndex: index });
|
|
58
50
|
}
|
package/dist/commands/push.js
CHANGED
|
@@ -19,7 +19,7 @@ import { assetDiskPathFromKey, walkOutDirPngs } from '../util/paths.js';
|
|
|
19
19
|
import { applyDiskFingerprints, indexSyncedBySourceRel, planPush, resolveSyncedKey } from '../util/pushPlan.js';
|
|
20
20
|
import { cmd } from '../util/invoke.js';
|
|
21
21
|
import { detectProjectKind, isEngineKind } from '../util/framework.js';
|
|
22
|
-
import { indexGamePngs, matchConnectGlobs,
|
|
22
|
+
import { indexGamePngs, matchConnectGlobs, countingSpritesText } from '../util/gameScan.js';
|
|
23
23
|
import { collectSourceRelMap, remapAbsToCloudKeys } from '../util/syncPath.js';
|
|
24
24
|
import { assertSafeIoPath } from '../util/security.js';
|
|
25
25
|
/** Parallel hashing / path checks — enough to saturate disk, low enough to stay polite. */
|
|
@@ -64,8 +64,8 @@ export async function runPushWith(config, state, opts = {}) {
|
|
|
64
64
|
? await indexGamePngs(kind, process.cwd(), config.outDir, {
|
|
65
65
|
onProgress: spinner ? (p) => { spinner.text = countingSpritesText(p); } : undefined,
|
|
66
66
|
})
|
|
67
|
-
: { files: []
|
|
68
|
-
const matched = matchConnectGlobs(index, config.connect ?? []);
|
|
67
|
+
: { files: [] });
|
|
68
|
+
const matched = matchConnectGlobs(index, config.connect ?? [], config.exclude);
|
|
69
69
|
const sourceByKey = collectSourceRelMap(matched.entries, state.synced);
|
|
70
70
|
const disk = await walkOutDirPngs(config.outDir);
|
|
71
71
|
const absByKey = new Map(disk.map((a) => [a.key, a.abs]));
|
|
@@ -151,12 +151,6 @@ export async function runPushWith(config, state, opts = {}) {
|
|
|
151
151
|
const sendable = actions.filter((a) => a.kind === 'update' || a.kind === 'adopt');
|
|
152
152
|
spinner?.succeed(`Local sprites: ${candidates.length} · to push ${sendable.length} · unchanged ${skipped}` +
|
|
153
153
|
(matched.entries.length ? ` · connected ${matched.entries.length}` : ''));
|
|
154
|
-
if (index.capped && !quiet) {
|
|
155
|
-
console.log(kleur.yellow(`! ${GAME_INDEX_CAP_HINT}`));
|
|
156
|
-
}
|
|
157
|
-
if (matched.capped && !quiet) {
|
|
158
|
-
console.log(kleur.yellow(`! ${connectCapMessage(matched.total, matched.entries.length)}`));
|
|
159
|
-
}
|
|
160
154
|
if (skippedUnsafe > 0 && !quiet) {
|
|
161
155
|
console.log(kleur.yellow(`! skipped ${skippedUnsafe} path${skippedUnsafe === 1 ? '' : 's'} that were not regular files inside the project.`));
|
|
162
156
|
}
|
|
@@ -215,6 +209,7 @@ export async function runPushWith(config, state, opts = {}) {
|
|
|
215
209
|
assetId: a.assetId,
|
|
216
210
|
layerIdx: a.layerIdx,
|
|
217
211
|
baseSha256: a.baseSha256,
|
|
212
|
+
...(a.baseUpdatedAt ? { baseUpdatedAt: a.baseUpdatedAt } : {}),
|
|
218
213
|
flatten: opts.flatten === true,
|
|
219
214
|
...(opts.replace ? { replace: true } : {}),
|
|
220
215
|
});
|
package/dist/commands/resync.js
CHANGED
|
@@ -77,8 +77,7 @@ export async function runResync(segments, opts = {}) {
|
|
|
77
77
|
if (plan.scanned === 0) {
|
|
78
78
|
throw new Error(`No local sprites found in "${label}". Nothing was changed. Check the folder name with \`magicpixel search\`.`);
|
|
79
79
|
}
|
|
80
|
-
const
|
|
81
|
-
const preview = pruneAllowed && plan.keptAssetIds.length > 0
|
|
80
|
+
const preview = plan.keptAssetIds.length > 0
|
|
82
81
|
? await pruneResyncFolder(segments, plan.keptAssetIds, true)
|
|
83
82
|
: null;
|
|
84
83
|
log(kleur.bold(`Resync ${label} from your game`));
|
|
@@ -90,8 +89,6 @@ export async function runResync(segments, opts = {}) {
|
|
|
90
89
|
if (preview.wouldTrash.length > 10)
|
|
91
90
|
log(kleur.dim(` …and ${preview.wouldTrash.length - 10} more`));
|
|
92
91
|
}
|
|
93
|
-
if (!pruneAllowed)
|
|
94
|
-
log(kleur.yellow(' ! game scan hit its file cap — nothing will be trashed.'));
|
|
95
92
|
log(kleur.dim(' Local files win: MagicPixel copies are overwritten and resized to match.'));
|
|
96
93
|
if (opts.dryRun) {
|
|
97
94
|
log(kleur.dim('--dry-run: nothing sent.'));
|
|
@@ -105,9 +102,7 @@ export async function runResync(segments, opts = {}) {
|
|
|
105
102
|
});
|
|
106
103
|
let pruneSkipped = null;
|
|
107
104
|
let trashed = 0;
|
|
108
|
-
if (
|
|
109
|
-
pruneSkipped = 'game scan capped';
|
|
110
|
-
else if (push.error > 0 || push.conflict > 0)
|
|
105
|
+
if (push.error > 0 || push.conflict > 0)
|
|
111
106
|
pruneSkipped = 'some sprites failed to upload';
|
|
112
107
|
else if (push.keptAssetIds.length === 0)
|
|
113
108
|
pruneSkipped = 'no files confirmed';
|
package/dist/commands/search.js
CHANGED
|
@@ -2,7 +2,7 @@ import kleur from 'kleur';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { loadConfig } from '../config.js';
|
|
4
4
|
import { detectProjectKind, isEngineKind } from '../util/framework.js';
|
|
5
|
-
import { indexGamePngs, searchGameIndex,
|
|
5
|
+
import { indexGamePngs, searchGameIndex, countingSpritesText } from '../util/gameScan.js';
|
|
6
6
|
import { cmd } from '../util/invoke.js';
|
|
7
7
|
const PRINT_CAP = 50;
|
|
8
8
|
/**
|
|
@@ -31,9 +31,6 @@ export async function searchCommand(query) {
|
|
|
31
31
|
onProgress: (p) => { spinner.text = countingSpritesText(p); },
|
|
32
32
|
});
|
|
33
33
|
spinner.stop();
|
|
34
|
-
if (index.capped) {
|
|
35
|
-
console.log(kleur.yellow(`! ${GAME_INDEX_CAP_HINT}`));
|
|
36
|
-
}
|
|
37
34
|
const hits = searchGameIndex(index, q);
|
|
38
35
|
if (hits.length === 0) {
|
|
39
36
|
console.log(kleur.dim(` No indexed PNGs matching "${q}" (${index.files.length} scanned).`));
|
package/dist/commands/sync.js
CHANGED
|
@@ -12,7 +12,7 @@ import { createLimit } from '../util/limit.js';
|
|
|
12
12
|
import { emitTypedIndex, ensureAgentsDoc, scanDiskAssets } from '../util/emitIndex.js';
|
|
13
13
|
import { assertPathInsideRoot, assertSafeIoPath } from '../util/security.js';
|
|
14
14
|
import { detectProjectKind, isEngineKind } from '../util/framework.js';
|
|
15
|
-
import { indexGamePngs, matchConnectGlobs,
|
|
15
|
+
import { indexGamePngs, matchConnectGlobs, countingSpritesText } from '../util/gameScan.js';
|
|
16
16
|
import { aliasCollisionKeys, collectSourceRelMap, isPathInside, syncDiskPathFromKey } from '../util/syncPath.js';
|
|
17
17
|
import { DEFAULT_UNITY_PPU, writeMissingUnityMetas } from '../util/unityMeta.js';
|
|
18
18
|
import { filterUnityManifest, isWorkingSetEntry, partitionWithheldEntries, shouldPruneDeselectedEntry, workingSetPullKeys } from '../util/unityFilter.js';
|
|
@@ -510,7 +510,7 @@ async function loadWatchHeader(onProgress) {
|
|
|
510
510
|
const kind = await detectProjectKind();
|
|
511
511
|
if (isEngineKind(kind) && (config.connect?.length ?? 0) > 0) {
|
|
512
512
|
gameIndex = await indexGamePngs(kind, process.cwd(), config.outDir, { onProgress });
|
|
513
|
-
workingSet = matchConnectGlobs(gameIndex, config.connect).
|
|
513
|
+
workingSet = matchConnectGlobs(gameIndex, config.connect, config.exclude).entries.length;
|
|
514
514
|
}
|
|
515
515
|
else if (state.synced) {
|
|
516
516
|
workingSet = Object.keys(state.synced).length;
|
|
@@ -649,14 +649,14 @@ async function runOnce(opts, runOpts = {}) {
|
|
|
649
649
|
: `Looking through your game sprites… ${n}`);
|
|
650
650
|
},
|
|
651
651
|
})
|
|
652
|
-
: { files: []
|
|
652
|
+
: { files: [] });
|
|
653
653
|
if (!runOpts.gameIndex)
|
|
654
654
|
runOpts.onGameIndex?.(gameIndex);
|
|
655
655
|
const fullGameIndex = gameIndex;
|
|
656
656
|
const scopedGameIndex = scope
|
|
657
657
|
? { ...fullGameIndex, files: fullGameIndex.files.filter((f) => pathInScope(scope, f.sourceRel)) }
|
|
658
658
|
: fullGameIndex;
|
|
659
|
-
const connected = matchConnectGlobs(scopedGameIndex, config.connect ?? []);
|
|
659
|
+
const connected = matchConnectGlobs(scopedGameIndex, config.connect ?? [], config.exclude);
|
|
660
660
|
const sourceByKey = collectSourceRelMap(connected.entries, state.synced);
|
|
661
661
|
if (scope) {
|
|
662
662
|
// Out-of-scope rows become hold-only: never pulled, and their folders are
|
|
@@ -686,15 +686,8 @@ async function runOnce(opts, runOpts = {}) {
|
|
|
686
686
|
}
|
|
687
687
|
}
|
|
688
688
|
aliasCollisionKeys(sourceByKey, manifest.map((e) => e.key));
|
|
689
|
-
if (verbose && gameIndex.capped) {
|
|
690
|
-
console.log(kleur.yellow(`! ${GAME_INDEX_CAP_HINT}`));
|
|
691
|
-
}
|
|
692
|
-
if (verbose && connected.capped) {
|
|
693
|
-
console.log(kleur.yellow(`! ${connectCapMessage(connected.total, connected.entries.length)}`));
|
|
694
|
-
}
|
|
695
689
|
if (verbose && isEngineKind(projectKind)) {
|
|
696
|
-
console.log(kleur.dim(` game index → ${gameIndex.files.length} PNG${gameIndex.files.length === 1 ? '' : 's'} · working set ${connected.entries.length}`
|
|
697
|
-
(connected.capped ? ` of ${connected.total} matched` : '')));
|
|
690
|
+
console.log(kleur.dim(` game index → ${gameIndex.files.length} PNG${gameIndex.files.length === 1 ? '' : 's'} · working set ${connected.entries.length}`));
|
|
698
691
|
}
|
|
699
692
|
const pathForKey = (key) => syncDiskPathFromKey(config.outDir, key, sourceByKey);
|
|
700
693
|
// Every folder on disk that belongs to a withheld document. Anything already
|
|
@@ -1377,12 +1370,14 @@ async function runOnce(opts, runOpts = {}) {
|
|
|
1377
1370
|
// After a write, the actual downloaded bytes are the disk/cloud baseline.
|
|
1378
1371
|
// The manifest hash may be null or represent a cached composite while the
|
|
1379
1372
|
// download endpoint serves storage bytes for a single-artboard document.
|
|
1373
|
+
const prev = nextSynced[entry.key];
|
|
1374
|
+
// Never let the local game file stand in for MagicPixel's composite when
|
|
1375
|
+
// the manifest has no cached hash: keep the last real cloud baseline.
|
|
1380
1376
|
const sha = writtenKeys.has(entry.key)
|
|
1381
1377
|
? await fileSha256(pathFor(entry))
|
|
1382
|
-
: (entry.sha256 ?? (await fileSha256(pathFor(entry))));
|
|
1378
|
+
: (entry.sha256 ?? prev?.sha256 ?? (await fileSha256(pathFor(entry))));
|
|
1383
1379
|
if (!sha)
|
|
1384
1380
|
continue;
|
|
1385
|
-
const prev = nextSynced[entry.key];
|
|
1386
1381
|
const diskSha256 = writtenKeys.has(entry.key)
|
|
1387
1382
|
? sha
|
|
1388
1383
|
: (shaByEntryId.get(entry.id) ?? prev?.diskSha256 ?? sha);
|
package/dist/util/gameScan.js
CHANGED
|
@@ -47,15 +47,6 @@ export const GAME_SCAN_SKIP_HIDDEN = new Set([
|
|
|
47
47
|
]);
|
|
48
48
|
/** Parallel directory reads while walking the game tree. */
|
|
49
49
|
const SCAN_WALK_CONCURRENCY = 8;
|
|
50
|
-
/** Cap on the search index (whole tree). Connect uses the same ceiling. */
|
|
51
|
-
export const MAX_GAME_INDEX = 10_000;
|
|
52
|
-
/** Cap on PNGs ingested for one working-set connect / push. */
|
|
53
|
-
export const MAX_GAME_CONNECT = MAX_GAME_INDEX;
|
|
54
|
-
export const GAME_INDEX_CAP_HINT = `Game index hit ${MAX_GAME_INDEX} PNGs — later files aren't searchable or connectable.`;
|
|
55
|
-
export function connectCapMessage(total, ingested) {
|
|
56
|
-
return (`${total} sprites match — syncing the first ${ingested} (cap ${MAX_GAME_CONNECT}). ` +
|
|
57
|
-
`Connect a smaller folder if you don't need all of them.`);
|
|
58
|
-
}
|
|
59
50
|
/** True for a full Unity game project (not an embedded UPM package). */
|
|
60
51
|
function isUnityGameProject(cwd, assetRoot) {
|
|
61
52
|
return Boolean(assetRoot && existsSync(resolve(cwd, 'ProjectSettings')));
|
|
@@ -175,7 +166,7 @@ function samePath(a, b) {
|
|
|
175
166
|
}
|
|
176
167
|
/** Walk the engine tree and return index entries. Does not copy files. */
|
|
177
168
|
export async function indexGamePngs(kind, cwd = process.cwd(), outDir = '', opts = {}) {
|
|
178
|
-
const empty = { files: []
|
|
169
|
+
const empty = { files: [] };
|
|
179
170
|
if (!isEngineKind(kind))
|
|
180
171
|
return empty;
|
|
181
172
|
const scanRoot = resolve(cwd);
|
|
@@ -192,30 +183,27 @@ export async function indexGamePngs(kind, cwd = process.cwd(), outDir = '', opts
|
|
|
192
183
|
// Library/Packages/cloud placeholders and looks hung at "1 folder".
|
|
193
184
|
const startAt = isUnityGameProject(cwd, assetRoot) ? assetRoot : scanRoot;
|
|
194
185
|
const preferFirst = startAt === scanRoot ? assetRoot : null;
|
|
195
|
-
await walkPngs(startAt, scanRoot, destNorm, found,
|
|
186
|
+
await walkPngs(startAt, scanRoot, destNorm, found, Infinity, io, report, stats, preferFirst);
|
|
196
187
|
report.flush?.();
|
|
197
188
|
opts.onProgress?.({ pngs: found.length, folders: stats.folders });
|
|
198
189
|
found.sort((a, b) => a.localeCompare(b));
|
|
199
|
-
const capped = found.length > MAX_GAME_INDEX;
|
|
200
|
-
if (capped)
|
|
201
|
-
found.length = MAX_GAME_INDEX;
|
|
202
190
|
const files = [];
|
|
203
191
|
for (const abs of found) {
|
|
204
192
|
const entry = toEntry(abs, scanRoot, assetRoot);
|
|
205
193
|
if (entry)
|
|
206
194
|
files.push(entry);
|
|
207
195
|
}
|
|
208
|
-
return { files
|
|
196
|
+
return { files };
|
|
209
197
|
}
|
|
210
|
-
/** Filter the index by connect globs (cwd
|
|
211
|
-
export function matchConnectGlobs(index, globs,
|
|
198
|
+
/** Filter the index by connect globs minus exclude globs (cwd-, asset-root-relative or manifest key). */
|
|
199
|
+
export function matchConnectGlobs(index, globs, exclude = []) {
|
|
212
200
|
if (globs.length === 0)
|
|
213
|
-
return { entries: []
|
|
214
|
-
const
|
|
201
|
+
return { entries: [] };
|
|
202
|
+
const hit = (e, g) => matchGlob(e.sourceRel, g) || matchGlob(e.adoptRel, g) || matchGlob(e.key, g);
|
|
203
|
+
// `exclude` wins over `connect`: excluded game files are never uploaded.
|
|
204
|
+
const matched = index.files.filter((e) => globs.some((g) => hit(e, g)) && !exclude.some((g) => hit(e, g)));
|
|
215
205
|
matched.sort((a, b) => a.sourceRel.localeCompare(b.sourceRel));
|
|
216
|
-
|
|
217
|
-
const capped = total > max;
|
|
218
|
-
return { entries: capped ? matched.slice(0, max) : matched, total, capped };
|
|
206
|
+
return { entries: matched };
|
|
219
207
|
}
|
|
220
208
|
export function searchGameIndex(index, query) {
|
|
221
209
|
const q = query.trim().toLowerCase();
|
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. */
|