Read ../AGENTS.md if it exists and has not already been loaded. These repository instructions take precedence over parent instructions where they differ.
Also follow the Workflow Rules, Code Design, Writing and TypeScript Conventions sections of ../config-director/AGENTS.md; this repository keeps no copy of them.
@configdirector/cli, the configdirector command: TypeScript on Node 20 or later, ESM, built with tsdown and published to npm under the latest dist-tag. The product definition is product-documents/cli-definition.md in the config-director repository; read its sections 3 to 5 before changing a command.
yarn typecheck # tsc --noEmit
yarn lint # eslint .
yarn format # prettier --write .
yarn test # vitest in watch mode
yarn ci:test # vitest run
yarn build # tsdown into dist/configdirector.mjs
yarn pack:tarball # pack the publishable tarball into package.tgz
yarn ci:test:packed-tarball # install package.tgz into an empty directory and run the binaryRun a single spec with yarn vitest run test/login.spec.ts.
src/main.tsis the only file that readsprocess. It builds anInvocation(arguments, environment variables, working and home directories, platform, stdin, stdout, stderr) and passes it torun. Everything else reads the invocation; lint rejectsprocessandconsoleelsewhere insrc/.run.tsresolves the global options once into aCommandContext: the base URL with its default, and the project slug from--projectorCONFIGDIRECTOR_PROJECT. The token stays the raw--tokenvalue, becauseloginfalls back to the prompt while every other command usesconfiguredApiToken(--token,CONFIGDIRECTOR_TOKEN, then the CLI sign-in for the organization named by--organization,CONFIGDIRECTOR_ORGANIZATIONororganizationinconfigdirector.json, then the only sign-in).loginkeeps one sign-in per organization insign-ins.json(src/sign-ins/), and the first read of the sign-ins moves the token an earlier version saved inconfig.json. An empty environment variable counts as unset (nonEmptyVariable), which is what CI sets for a missing secret.- A command returns a
CommandOutputholding its JSON, its human text and its exit code, andrunprints the JSON or the text on stdout. Apart from commander's--helpand--version, nothing else writes to stdout; prompts and errors go to stderr. - Exit codes (
ExitCode): 0 success, 1validatefound problems orconfigs createfound the key taken, and 2 the command could not run (usage, auth or network). Throw aCliErrorsubclass for every expected failure;runprints its message and exits 2. An unexpected exception prints its stack and also exits 2. - Every Admin API call goes through
AdminApiClient. It sendsAuthorization: Bearer <token>,X-SourceClient: cliandUser-Agent: configdirector-cli/<version>, validates responses with valibot, and maps each failure class to its own error inadmin-api-errors.ts. Declare only the response fields the CLI reads, so the API can add fields without breaking a released CLI. - Runtime packages go in
dependencies, which tsdown leaves external sonpm auditsees them; everything else is a devDependency. - The CLI never checks for updates and sends no telemetry.
- Behavior specs call
runwith an invocation fromtest/support/sandbox.ts(temporary working and home directories, captured stdout and stderr) against a real HTTP stub of the Admin API fromtest/support/stub-admin-api.ts. Assert the exit code, stdout, stderr, the files written and the requests the stub received. - Seed state through commands rather than files: run
loginto save a sign-in instead of writingsign-ins.json. Write the file directly only for statesloginnever produces, such as invalid contents, permissions others can read, or theconfig.jsonan earlier version saved. test/support/terminal-input.tsstands in for a terminal, to test the hidden token prompt.test/packed-tarball/installspackage.tgzwith npm into an empty directory and runs the installed binary. Runyarn build && yarn pack:tarballfirst. CI runs it on Node 24 and again on Node 20.
- Every user-visible change gets an entry in
CHANGELOG.mdunder## [Unreleased], in Keep a Changelog sections (Added, Changed, Fixed), written from the user's side. - To release, set
versioninpackage.json, move the Unreleased entries under that version, and run theReleaseworkflow from GitHub Actions. It runsyarn release, publishespackage.tgzto npm underlatestthrough trusted publishing, tags the commitcli-v<version>, and points the formula inConfigDirector/homebrew-tap(../homebrew-tap) at the new tarball, with theHOMEBREW_TAP_TOKENsecret, a fine-grained token with Contents read and write on that repository only. When the formula step fails, npm already has the release; the log names theurlandsha256to commit to the tap by hand. - Versions are plain
0.x.yuntil 1.0, never a prerelease such as-beta.1: when a user's.npmrcsetsmin-release-age, npm falls back from a release too new to install to the newest older plain version, but never to an older prerelease, so a prerelease fails every install until it is old enough.