mirror of
https://github.com/smogon/sprites.git
synced 2026-09-15 21:46:44 -05:00
- casInsert verifies an existing object's bytes before trusting it, fsyncs before the rename, and the store records object sizes so a crash-truncated object reads as dirty instead of clean forever. - The driver hashes sources over the producer closure, so partial builds (inspect) that pull in producers outside the demanded set work. - Unknown executor errors are reported and abort the build instead of vanishing into allSettled. - %b/%B expand to nominal names at declaration (never CAS basenames), which also puts name-dependence in the identity key; cmds are key-joined with NUL so a multi-line command cannot collide with split commands. This churns every rule key once. - Rules must declare at least one output; output names reject shell-hostile characters. - run --link copies read-only (CAS) sources instead of hardlinking 0444 modes into deploy trees; ctx.list filters dotfiles and directories like the build-side glob; inspect resolves targets against the invoking cwd. - The registry dedupes identical declarations (same cmds/inputs/deps/ outputs/display), returning the existing artifacts, so shared rule sets are plain functions and memo() is gone; ps.deploy.ts no longer declares the gen5 gif set it never ships. - rule() returns a tuple typed by its literal output list (single string output returns the Artifact directly), so destructuring needs no non-null assertions. - Manifest is a class carrying the deploy ctx: set() rejects duplicate keys, write(dst) emits the sorted JSON, and spritecopy/itemspritecopy/ stampcopy take just the manifest. The unstamped smogon sets (xyitems, fbsprites, twittersprites) are deprecated and commented out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
|
|
import crypto from 'crypto';
|
|
import fs from 'fs';
|
|
|
|
import b32encode from 'base32-encode';
|
|
|
|
import {Artifact} from '../build/artifact.ts';
|
|
import {casPath} from '../build/cas.ts';
|
|
import {type ActionQueue} from './queue.ts';
|
|
import * as pathlib from './path.ts';
|
|
|
|
// A source-tree file from ctx.list(); shaped like a deploy Path plus the
|
|
// repo-relative path.
|
|
export interface SrcFile extends pathlib.Path {
|
|
path : string;
|
|
}
|
|
|
|
export type CopySource = Artifact | SrcFile | string; // string = repo-relative path
|
|
|
|
// The finish API: nice naming over built artifacts and raw sources. Ops are
|
|
// queued in call order (the tar entry order), so __key-style entries must be
|
|
// written first.
|
|
export interface DeployCtx {
|
|
copy(src : CopySource, dst : string) : void;
|
|
write(dst : string, data : string) : void;
|
|
read(src : CopySource) : string;
|
|
list(dir : string) : SrcFile[];
|
|
// 8-char base32 content stamp. One source: the digest of its bytes,
|
|
// byte-compatible with artifact hashes. Several: a digest of the sorted
|
|
// per-source digests (order-insensitive).
|
|
hash(...srcs : CopySource[]) : string;
|
|
}
|
|
|
|
export interface DeploySpec {
|
|
finish : (ctx : DeployCtx) => void | Promise<void>;
|
|
}
|
|
|
|
// Identity helper: a deploy module declares its rules at top level and
|
|
// `export default defineDeploy({finish})`.
|
|
export function defineDeploy(spec : DeploySpec) : DeploySpec {
|
|
return spec;
|
|
}
|
|
|
|
function shortHash(digest : Buffer) : string {
|
|
return b32encode(digest, 'RFC4648').slice(0, 8);
|
|
}
|
|
|
|
export function makeCtx(casDir : string, queue : ActionQueue) : DeployCtx {
|
|
// Every path here is repo-root-relative; the CLI chdirs to the root.
|
|
const srcPath = (src : CopySource) : string => {
|
|
if (src instanceof Artifact) {
|
|
return casPath(casDir, src.hash, src.ext);
|
|
}
|
|
return typeof src === 'string' ? src : src.path;
|
|
};
|
|
const digestOf = (src : CopySource) : Buffer => {
|
|
if (src instanceof Artifact) {
|
|
return Buffer.from(src.hash, 'hex');
|
|
}
|
|
return crypto.createHash('sha256').update(fs.readFileSync(srcPath(src))).digest();
|
|
};
|
|
return {
|
|
copy(src : CopySource, dst : string) : void {
|
|
queue.copy(srcPath(src), dst);
|
|
},
|
|
write(dst : string, data : string) : void {
|
|
queue.write(data, dst);
|
|
},
|
|
read(src : CopySource) : string {
|
|
return fs.readFileSync(srcPath(src), 'utf8');
|
|
},
|
|
list(dir : string) : SrcFile[] {
|
|
const result = [];
|
|
// Files only, no dotfiles: the same filtering the build-side
|
|
// glob applies to rule inputs.
|
|
for (const ent of fs.readdirSync(dir, {withFileTypes: true}).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
if (ent.name.startsWith('.') || (!ent.isFile() && !ent.isSymbolicLink())) {
|
|
continue;
|
|
}
|
|
const p = pathlib.path(ent.name, {dir});
|
|
result.push({...p, path: pathlib.format(p)});
|
|
}
|
|
return result;
|
|
},
|
|
hash(...srcs : CopySource[]) : string {
|
|
if (srcs.length === 1) {
|
|
return shortHash(digestOf(srcs[0]!));
|
|
}
|
|
const digests = srcs.map(digestOf).sort(Buffer.compare);
|
|
const h = crypto.createHash('sha256');
|
|
for (const d of digests) {
|
|
h.update(d);
|
|
}
|
|
return shortHash(h.digest());
|
|
},
|
|
};
|
|
}
|