Asset publishing pipeline
Asset publishing is composed of three pnpm scripts: assets:prepare → assets:build → assets:publish. Each has a single responsibility and can be run independently.
0. Prerequisite: the cos-workspace mirror
cos-workspace/ is a purely local working directory and must be gitignored. pnpm assets:prepare expects a cos-workspace/public-root-legacy/ subdirectory containing a mirror of the current live public bucket (pull it with coscli sync once, or place it manually).
1. pnpm assets:prepare
scripts/prepare-cos-workspace.mjs:
- Rewrite
cos-workspace/public-root-legacy/intocos-workspace/public-root/, normalizing all paths to the canonicalrealm/.../shared/...form - Normalize object base names to short English kebab-case (the build step appends
hash8later) - Regenerate
cos-workspace/_meta/realm/{home,works,collections,links,audio}.jsonand the logical asset-source metadata
Output is still local files, no upload.
2. pnpm assets:build
scripts/assets-build.mjs:
- Walk every file under
cos-workspace/public-root/ realm/images/**goes throughprocessImageFile: read metadata withsharp, hash the content, and resize if needed; output is<name>.<hash8>.<ext>realm/audio/**goes throughprocessAudioFile: hash + copy- Other files go through
copySharedAsset: direct copy - Load
_meta/realm/{home,works,collections,links,audio}.jsonand the optionallegacy-asset-sources.json; replace eachsourcefield with the hashedobjectKey - Write the private catalog
dist/cos-upload/private-root/realm/catalog/versions/<version>/<section>.json - Write the private
current.json(defaultcurrent.next.json; with--publish-currentwritecurrent.jsondirectly) - Write the public site catalog
dist/cos-upload/public-root/realm/site-catalog/versions/<version>/assets.json(onlySITE_ASSET_KEYS) - Write the public
current.json/current.next.json - Write
dist/local-manifest/manifest.generated.jsonfor the publish script to read
--publish-current skips the staging step and points current.json straight at the new version (used for one-off major-version cuts).
3. pnpm assets:publish
scripts/assets-publish.mjs:
revalidate() parses the response body:
200 + failed: []→ success200 + partial: true→ warning, log the failed paths, continue5xx→ throw, the whole publish fails
--dry-run does not execute coscli or send the request, just prints the commands. --force-full swaps cos sync for cos cp -r to do a one-off full re-upload (for emergencies). --rollback <version> skips build and just cos cp writes current.json pointing at the old version.
4. Credentials and local dev
COS_SECRET_ID/COS_SECRET_KEYare injected temporarily throughnode --env-file=.env.local- Never write to a coscli config file; never print secrets
COSCLI_PATHdefaults to./cos-workspace/coscli-windows-amd64.exe; can be overriddenCOS_SESSION_TOKENis optional (STS scenarios)
5. Verification checklist
After publishing:
6. Critical invariants
- The resource version (
<version>) is UTC ISO without separators in the form20260710T120000Z - Object keys are content-addressed (hash embedded in the key), so
cos syncskipping unchanged objects by etag is safe - Writing
current.jsonis the last step, and only viacos cp, nevercos sync cos-workspace/is always gitignored locally