chore(scripts): include anim, missiles, overlays, entities, and world-graph in publish-packs

This commit is contained in:
troytt 2026-09-26 13:32:51 +00:00
parent bdb3b4145a
commit cccc6ff527
1 changed files with 41 additions and 32 deletions

View File

@ -163,55 +163,66 @@ npm run publish:packs # orphan 提交 + force push 覆盖本仓库
* loses everything on the first reset. Batching at ~10 MB keeps each push to a
* few minutes and makes a retry cheap.
*/
const MAX_BATCH_BYTES = 10 * 1024 * 1024
const MAX_BATCH_BYTES = 25 * 1024 * 1024
/** How many times to retry one push before giving up. */
const PUSH_ATTEMPTS = 5
/** The order acts are published in, smallest first so the remote is usable early. */
const ACT_ORDER = ['act4', 'act3', 'act5', 'act2', 'act1'] as const
/** The order pack directories are published in, smallest first so the remote is usable early. */
const PACK_CATEGORIES = [
'anim',
'missiles',
'overlays',
'act4',
'act3',
'act5',
'act2',
'act1',
'entities',
] as const
/** One push batch: a set of map directories inside one act. */
/** One push batch: a set of relative paths inside one top-level pack directory. */
interface Batch {
readonly act: string
/** Paths relative to {@link PACKS_DIR}, e.g. `act1/1-act-1-town-townn1`. */
/** Paths relative to {@link PACKS_DIR}, e.g. `act1/1-act-1-town-townn1` or `entities/char-so.r8`. */
readonly paths: readonly string[]
readonly bytes: number
}
/**
* Total size of a directory tree, in bytes.
* Total size of a file or directory tree, in bytes.
*
* @param dir - absolute path.
* @param target - absolute path.
* @returns bytes, `.git` excluded.
*/
function dirSize(dir: string): number {
function entrySize(target: string): number {
const st = statSync(target)
if (!st.isDirectory()) return st.size
let bytes = 0
for (const entry of readdirSync(dir, { withFileTypes: true })) {
for (const entry of readdirSync(target, { withFileTypes: true })) {
if (entry.name === '.git') continue
const path = join(dir, entry.name)
if (entry.isDirectory()) bytes += dirSize(path)
else bytes += statSync(path).size
bytes += entrySize(join(target, entry.name))
}
return bytes
}
/**
* Split every act into batches of at most {@link MAX_BATCH_BYTES}.
* Split every top-level pack directory into batches of at most {@link MAX_BATCH_BYTES}.
*
* Map directories are never split, so a single oversized map becomes a batch of
* its own. The order within an act is the directory listing order, sorted, so
* the plan is stable across runs — which is what makes resuming safe.
* Direct child entries (map directories, overlay subdirectories, or individual asset files)
* are never split, so a single oversized entry becomes a batch of its own. The order within
* a category is the sorted directory listing order, so the plan is stable across runs —
* which is what makes resuming safe.
*
* @returns the batches, act by act in {@link ACT_ORDER}.
* @returns the batches, category by category in {@link PACK_CATEGORIES}.
*/
function planBatches(): Batch[] {
const batches: Batch[] = []
for (const act of ACT_ORDER) {
const actDir = join(PACKS_DIR, act)
if (!existsSync(actDir)) continue
const maps = readdirSync(actDir, { withFileTypes: true })
.filter(entry => entry.isDirectory())
for (const category of PACK_CATEGORIES) {
const categoryDir = join(PACKS_DIR, category)
if (!existsSync(categoryDir)) continue
const entries = readdirSync(categoryDir, { withFileTypes: true })
.filter(entry => entry.name !== '.git')
.map(entry => entry.name)
.sort()
@ -219,21 +230,16 @@ function planBatches(): Batch[] {
let currentBytes = 0
const flush = (): void => {
if (current.length === 0) return
batches.push({ act, paths: current, bytes: currentBytes })
batches.push({ act: category, paths: current, bytes: currentBytes })
current = []
currentBytes = 0
}
for (const map of maps) {
const bytes = dirSize(join(actDir, map))
for (const name of entries) {
const bytes = entrySize(join(categoryDir, name))
if (currentBytes > 0 && currentBytes + bytes > MAX_BATCH_BYTES) flush()
current.push(`${act}/${map}`)
current.push(`${category}/${name}`)
currentBytes += bytes
}
// Loose files directly under the act directory ride along in the last batch.
const loose = readdirSync(actDir, { withFileTypes: true })
.filter(entry => !entry.isDirectory())
.map(entry => `${act}/${entry.name}`)
for (const file of loose) current.push(file)
flush()
}
return batches
@ -350,8 +356,11 @@ try {
// Step 0: metadata and index.
if (!committed('index.json', work)) {
writeFileSync(join(work, 'README.md'), readme(stats, stamp))
writeFileSync(join(work, '.gitattributes'), '*.png binary -diff\n*.json text eol=lf\n')
writeFileSync(join(work, '.gitattributes'), '*.png binary -diff\n*.r8 binary -diff\n*.json text eol=lf\n')
cpSync(join(PACKS_DIR, 'index.json'), join(work, 'index.json'))
if (existsSync(join(PACKS_DIR, 'world-graph.json'))) {
cpSync(join(PACKS_DIR, 'world-graph.json'), join(work, 'world-graph.json'))
}
git(['add', '-A'], work)
git([
'-c', `user.name=${process.env['GIT_AUTHOR_NAME'] ?? 'Dao Tao'}`,