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 * loses everything on the first reset. Batching at ~10 MB keeps each push to a
* few minutes and makes a retry cheap. * 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. */ /** How many times to retry one push before giving up. */
const PUSH_ATTEMPTS = 5 const PUSH_ATTEMPTS = 5
/** The order acts are published in, smallest first so the remote is usable early. */ /** The order pack directories are published in, smallest first so the remote is usable early. */
const ACT_ORDER = ['act4', 'act3', 'act5', 'act2', 'act1'] as const 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 { interface Batch {
readonly act: string 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 paths: readonly string[]
readonly bytes: number 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. * @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 let bytes = 0
for (const entry of readdirSync(dir, { withFileTypes: true })) { for (const entry of readdirSync(target, { withFileTypes: true })) {
if (entry.name === '.git') continue if (entry.name === '.git') continue
const path = join(dir, entry.name) bytes += entrySize(join(target, entry.name))
if (entry.isDirectory()) bytes += dirSize(path)
else bytes += statSync(path).size
} }
return bytes 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 * Direct child entries (map directories, overlay subdirectories, or individual asset files)
* its own. The order within an act is the directory listing order, sorted, so * are never split, so a single oversized entry becomes a batch of its own. The order within
* the plan is stable across runs — which is what makes resuming safe. * 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[] { function planBatches(): Batch[] {
const batches: Batch[] = [] const batches: Batch[] = []
for (const act of ACT_ORDER) { for (const category of PACK_CATEGORIES) {
const actDir = join(PACKS_DIR, act) const categoryDir = join(PACKS_DIR, category)
if (!existsSync(actDir)) continue if (!existsSync(categoryDir)) continue
const maps = readdirSync(actDir, { withFileTypes: true }) const entries = readdirSync(categoryDir, { withFileTypes: true })
.filter(entry => entry.isDirectory()) .filter(entry => entry.name !== '.git')
.map(entry => entry.name) .map(entry => entry.name)
.sort() .sort()
@ -219,21 +230,16 @@ function planBatches(): Batch[] {
let currentBytes = 0 let currentBytes = 0
const flush = (): void => { const flush = (): void => {
if (current.length === 0) return if (current.length === 0) return
batches.push({ act, paths: current, bytes: currentBytes }) batches.push({ act: category, paths: current, bytes: currentBytes })
current = [] current = []
currentBytes = 0 currentBytes = 0
} }
for (const map of maps) { for (const name of entries) {
const bytes = dirSize(join(actDir, map)) const bytes = entrySize(join(categoryDir, name))
if (currentBytes > 0 && currentBytes + bytes > MAX_BATCH_BYTES) flush() if (currentBytes > 0 && currentBytes + bytes > MAX_BATCH_BYTES) flush()
current.push(`${act}/${map}`) current.push(`${category}/${name}`)
currentBytes += bytes 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() flush()
} }
return batches return batches
@ -350,8 +356,11 @@ try {
// Step 0: metadata and index. // Step 0: metadata and index.
if (!committed('index.json', work)) { if (!committed('index.json', work)) {
writeFileSync(join(work, 'README.md'), readme(stats, stamp)) 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')) 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(['add', '-A'], work)
git([ git([
'-c', `user.name=${process.env['GIT_AUTHOR_NAME'] ?? 'Dao Tao'}`, '-c', `user.name=${process.env['GIT_AUTHOR_NAME'] ?? 'Dao Tao'}`,