Developer recipes

Publish a generated file from a standalone TypeScript script

When a release step produces a small JSON manifest, you want the same bytes on disk and on the public URL. This recipe uses a single dependency-free TypeScript file run directly by Node.js.

On this page

What this does

The TypeScript download is a standalone Node.js 22.18+ script written in TypeScript and executed with the built-in type stripping. It needs no npm packages. It generates a release manifest, writes it to disk with exclusive creation, then presigns and uploads the identical bytes, printing the public URL for a manual release handoff.

Run it in a fresh directory

sh
work=$(mktemp -d)
cd "$work"
curl --fail-with-body --silent --show-error \
  --connect-timeout 10 --max-time 60 --retry 2 \
  https://yoink.zip/examples/publish-generated-file.mts \
  -o publish-generated-file.mts
node publish-generated-file.mts

The script writes release-manifest.json using exclusive creation (wx semantics), so rerunning it in the same directory fails by design rather than silently overwriting an artifact you may have already shared. To rerun, choose a new directory or deliberately archive the original first.

Edit the manifest for real data

Open the script and adjust the manifest fields to describe your actual public release. Do not include process.env dumps, credentials, internal package paths, or anything you would not want a stranger to read, because the resulting link is public with no delete API and no retention guarantee.

Why byte count matters

When including size in the presign call, use bytes, and UTF-8 text can have fewer characters than bytes. The script builds the manifest as a UTF-8 Buffer and uses buffer.byteLength for the presign size, then PUTs that same buffer. Saving the file and uploading it from one buffer guarantees the local copy and the public copy are identical.

typescript
const bytes = Buffer.from(JSON.stringify(manifest, null, 2) + "\n");
await writeFile(filename, bytes, { flag: "wx" });

Request behavior

The script applies a 60 second timeout per request and retries transient failures at most twice. On PUT retries it reuses the same signed URL, which stays valid for 900 seconds; requesting a new presign would allocate a new path. Validation-style failures (400, 401, 403, 413) abort with a diagnostic instead of retrying.

A TypeScript interface describing the API response does not validate anything at runtime, so the script explicitly checks that the presign response contains a string uploadUrl, publicUrl, and contentType before using them.

The PUT sends the raw buffer with the returned Content-Type, no multipart, no JSON wrapper, and no authorization header.

Scope and handoff

This pattern suits small in-memory artifacts: manifests, checksum files, changelogs. It is not shaped for large build outputs. Once the script prints the URL, copy it into your release notes or hand it to whoever publishes the announcement. Keep the local release-manifest.json as your original record, rather than relying on the public copy as your only archive. A new publication gets a new URL; Yoink does not expose an editing or deletion API. See the API reference for the request contract.