@shadow-library/scripts
v0.3.0
Published
Shared CLI for the Shadow Library ecosystem — centralizes the library build, local verification (lint + format + type-check + test), OpenAPI type generation, release, and migration-drift-check workflows behind a single .shadowrc.json config
Readme
@shadow-library/scripts
A shared CLI for the Shadow Library ecosystem. It centralizes the workflows that were duplicated — and
had drifted — across the shadow-library/* repositories: the library build, local verification
(format + lint + type-check + test), OpenAPI type generation, release, and the migration-drift check.
Everything is driven from a single .shadowrc.json at the repo root, and every command runs on
Bun. The binary is named shadow.
Installation
bun add -D @shadow-library/scriptsCommands
shadow init [--type <library|component|backend|spa|ssr>]
shadow prepare
shadow build
shadow verify [--fix]
shadow gen-api-types <url> [--out <path>]
shadow release <stable|alpha|beta> [--path <path>]
shadow check-migrations [--dir <path>]Run shadow --help for a summary. This document is the full reference.
Configuration — .shadowrc.json
A single file at the repo root configures every command. All fields are optional; the defaults below apply when the file (or a field) is absent.
{
"build": {
"exports": { ".": "index" }, // public subpath → source-relative base (no extension)
"bin": { "my-cli": "bin/my-cli" }, // optional; string shorthand also accepted
"outDir": "dist"
},
"verify": {
"files": "{src,tests,scripts}/**/*.ts", // what lint + format cover
"lint": {
"rules": {}, // merged over the shipped ESLint flat config
"ignores": [] // appended to the shipped ignore globs
}
// Prettier options are NOT configured here — they live in the repo's .prettierrc.json (see below).
},
"release": {
"npm": true, // publish to npm after tagging
"publishDir": "dist",
"changelog": true // generate the GitHub release body from commits
},
"genApiTypes": {
"outputPath": "src/lib/apis/api-types.gen.ts"
},
"checkMigrations": {
"dir": "generated/drizzle"
}
}build
Cleans outDir, compiles the package with tsc + tsc-alias, synthesizes dist/package.json
(main/module/types/exports/typesVersions, rewritten sideEffects and bin paths), and copies
README.md/LICENSE into the output if present.
Produces a single, flat ESM tree (no esm//cjs/ subdirectories, no CommonJS) with type
declarations and correct subpath exports. The exports map is keyed by public subpath and valued by the
source-relative base:
{ "build": { "exports": { ".": "index", "./errors": "errors/index", "./utils": "utils/index" } } }A bin entry (string shorthand or name → base map) is rewritten into dist/package.json pointing at
the compiled ./…js, gets a bun shebang injected if missing, and is chmod 755'd.
Prerequisites: a tsconfig.build.json extending your tsconfig.json (noEmit: false,
declaration: true). typescript and tsc-alias ship as dependencies of this package.
verify [--fix]
Runs, in order, stopping at the first failure:
- format — Prettier over
verify.files, reading options from the repo's own.prettierrc.json(resolved by prettier itself, see Prettier config).shadowno longer carries a copy of the ruleset, soshadow verifyand a bareprettierrun — editor format-on-save,bunx prettier --write— read the exact same file and can't drift. - lint — ESLint using the shipped flat config (
typescript-eslintstrict + stylistic, witheslint-plugin-perfectionisthandling import sorting).verify.lint.rules/verify.lint.ignoresare layered on top. Noeslint.config.jsis needed in the consuming repo. - type-check — the repo's
type-check(ortypecheck) package.json script, if present. - test — the repo's
testpackage.json script, if present.
--fix applies Prettier and ESLint fixes in place. Steps 3–4 are delegated to the repo's own scripts;
a step that maps back to shadow verify is skipped instead of recursing. This is a local pre-commit
convenience, not a CI replacement.
Prettier config
The Prettier ruleset is not kept in .shadowrc.json. The standard is a plain .prettierrc.json
at the repo root — the one place prettier itself, an editor's format-on-save, and the shadow verify
format step all read from, so nothing can drift. shadow init drops it (the canonical ecosystem
ruleset) when a repo has none:
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 180,
"arrowParens": "avoid"
}shadow verify and shadow gen-api-types resolve this file via prettier's own config resolution rather
than applying a built-in ruleset — a repo that wants to tweak an option edits .prettierrc.json
directly. shadow init never overwrites an existing prettier config (any .prettierrc* /
prettier.config.* file or a package.json "prettier" key).
prepare
The prepare-lifecycle setup, wired by shadow init as "prepare": "shadow prepare" (the slot husky's
own "prepare": "husky" used) so it runs on every bun install / npm install. It readies the configs
the ecosystem needs; for now that is husky only — activating git hooks. It tolerates a not-yet-git
inited repo so a fresh clone's first install never fails.
gen-api-types <url>
Fetches an OpenAPI document, rewrites every operationId to ${method}_${path} (unique by
construction), widens non-string GET query parameter types to also accept string, runs
openapi-typescript, appends per-schema and <Name>QueryParams/<Name>PathParams aliases, formats with
Prettier (the same base ruleset as verify), and writes it out atomically.
Output path defaults to genApiTypes.outputPath; override per-invocation with --out <path>.
release <stable|alpha|beta>
You choose only the channel; the CLI infers the rest:
- Reads the commits since the last
v*tag and derives the semver bump from Conventional Commits — any breaking change → major, anyfeat→ minor, otherwise → patch. - Computes the next version for the chosen channel (
stablefinalizes;alpha/betacut or advance a prerelease). - Runs the test gate, builds, then performs every remote git operation through Octokit: a Verified
package.jsoncommit onmain(Contents API), thev<version>tag, and the GitHub release (marked pre-release foralpha/beta). - Publishes
publishDirto npm whenrelease.npmistrue.
Prerequisites: GITHUB_TOKEN in the environment (a GitHub App installation token, so the commit is
Verified), NODE_AUTH_TOKEN for npm publish, and a checkout with full history and tags
(fetch-depth: 0). --path defaults to process.cwd().
check-migrations [--dir <path>]
Runs the repo's db:generate script, then fails if it leaves the migrations directory dirty — checking
both modified and untracked files (git status --porcelain), since a new migration is a new file
git diff alone would never flag. --dir defaults to checkMigrations.dir and must resolve inside the
repo.
Example — a library repo
.shadowrc.json:
{ "build": { "exports": { ".": "index" } } }package.json:
{
"scripts": {
"build": "shadow build",
"verify": "shadow verify",
"type-check": "tsc"
}
}Failure and exit-code behavior
Every command throws a ShadowError on failure; the CLI catches it, prints the message (no stack trace)
to stderr, and sets the exit code — error.exitCode when set, otherwise 1. verify propagates a
delegated step's own exit code. Unexpected (non-ShadowError) failures print a full stack trace so a bug
in this package isn't silently swallowed.
