A deploy's last phase is a long silent wait: thousands of files packed
into an upload or copied into a staging tree, with nothing between the
line naming the command and the line after it finishes.
Draw a bar over it when stderr is a terminal, and nothing at all when it
isn't -- piped or under CI a redraw per file is noise in the log, and
the stdout lines the deploy already prints are the record either way.
The tar entries a pack hands out are counted by their entry callbacks,
which wait on the pack draining into whoever is reading it, so the bar
follows what the upload command has taken rather than what has been read
off disk. The staging and -o paths count files as they land.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DRBNA4JH27Eq3cgJpzCLV2
A served name carries a content hash, so a reader that composes a fixed path
can't find one. The way out is to publish the set twice, once under the stamped
names and once under the bare ones, and a link is the one thing the queue
couldn't say: Copy dereferences and Write has bytes.
Add a Symlink op beside them. What ctx.symlink takes is not a link target but
the destination of the thing being named, since a published tree is unpacked
somewhere else and its halves can land apart; the path between the two is
computed here and is what gets written. It takes the same duplicate-dst and
absolute-dst checks the other ops do, plus one of its own -- resolved lexically
against the directory it sits in, a target has to stay inside the tree, since
the receiving side extracts as root and a link that walks out of it is the same
write to anywhere that an absolute member name is.
run() makes a real symlink in every mode, clearing the name first so a rerun
over an existing tree behaves the way copy and write already do, and pack()
emits a symlink member with no body, which is what a stream reader on the other
end sees.
refactor digests bytes, and a link has none; what it publishes is the name it
points at, so that is what it records and what a retarget shows up as.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DeployCtx read/list/hash return promises (copy and write stay sync:
they only queue ops), the stamped copy helpers and buildfile deploy
blocks await them, ActionQueue materialization and packing use
fs/promises, and the CLI, config loaders, spritesheet writer, and
trim tool follow. Declaration-time globbing and the module-init data
reads stay sync: rules exist by the end of the import.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Interfaces become type aliases with comma members (extends becomes
intersection), and class privates use #names instead of the private
keyword.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Node builtins come in as node:-prefixed namespace imports;
multi-symbol internal imports use short namespace aliases (artifact,
cas, subst, db, api, executor). Single-symbol pulls, types, and the
buildfile DSL names stay braced, matching the sanctioned exceptions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per the house rule, const survives only where TypeScript hangs
behavior off the spelling: the <const T> type parameter in rule()
and the as const option tables.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Annotation colons are tight (ternaries keep their spaces), plain
double-quoted strings become single-quoted, and .editorconfig comes
over verbatim. Rule cmd values are untouched; the sheet rules reran
once because their tool sources are declared deps, byte-identically.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An entry with dir: true materializes its subset into a temp
directory whose path replaces %d in the cmd, instead of piping a
tar on stdin -- rsync-style transports (the PS ani sync) need real
files. The flag and %d must agree, so a forgotten one is caught at
config load.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A dying consumer destroys the tar pack and its pending entry sinks,
each of which emits the EPIPE as an unhandled streamx error and
crashed the process before the child's exit code was reported. Keep
the pack and its sinks quiet; the command's exit code is the report.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An untracked json5 config maps deploy names to a buildFile plus
(subset globs, cmd) entries: the deploy verb builds the named
buildFiles, finishes each, routes the outputs through the subset
globs, and pipes one tar per entry to its command. Every glob must
match an output and every output must be covered by some entry.
The tar no longer carries __key; the upload command names the asset
set itself (smogonctl assets upload sprites).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
pnpm build/deploy now run the umbrella CLI (build/deploy/run/inspect); the
fixed-name build/ tree, the old planner (rename detection, adopt, tamper
checks), and the vm deploy sandbox are gone. Also lands tools/deploy/api.ts
and the CLI, which earlier commits referenced but an overbroad local
gitignore pattern had silently excluded.
Migration: the store migrates the v1 db in place; build/ is dead and can be
deleted. A full rebuild repopulated .build/cas, and the old and new deploy
trees were diffed: byte-identical except the two multi-file hash pointers
(algorithm change), fb/twitter PNG re-encodes (pixel-identical; old outputs
predated current tool versions), and a stale 2023 orphan sprite the old
system never cleaned up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>