Runner modes (TEKIR_RUNNER)
A cross-package convention so libraries with eager side effects can short-circuit during bundling and testing without every project re-declaring an env flag in its scripts.
Introduction
Some libraries do real work as soon as they are imported: a Redis client opens a TCP connection, a queue worker starts polling, a scheduler arms its first timer, a file watcher binds an inotify handle. That work is fine when the process exists to serve traffic, and harmful when it exists for any other reason — most visibly during tekir build, where every such connection is dead weight that slows the bundler and can hang the build outright if the remote dependency is unreachable.
Tekir publishes a contract: process.env.TEKIR_RUNNER is set to a known value for the two non-serving modes ('build' and 'test') and left unset for everything else. The framework sets it; libraries read it. No project-level glue.
Values
// Set automatically by the framework — never override unless you have a reason
TEKIR_RUNNER=test // `tekir test` (cli runs the runtime's native test command)
TEKIR_RUNNER=build // `tekir build` or `bun run index.ts build` (bundle is the goal,
// not serving traffic)
// (unset) // Every other invocation: `tekir serve`, raw `bun ./dist/index.js`,
// compiled binary, REPL, custom commands.The framework only sets TEKIR_RUNNER when it recognises the command — and uses ??= assignment so an outer caller (CI scripts, custom wrappers) can pin a different value first. tekir serve, bun ./dist/index.js, compiled binaries, and any custom tekir <command> path leave the variable unset; library code defaults to its normal eager behaviour there.
Library pattern
A three-line guard at the top of any module that opens a connection, starts a worker, or hooks into the OS:
// my-redis-lib/src/index.ts
// Eager-side-effect modules read TEKIR_RUNNER and short-circuit when the
// process is for bundling or testing. Keeping the gate inside the
// library means downstream users never have to remember to set anything
// in their package.json scripts — `tekir test` and `tekir build` are
// the two contracts the framework guarantees.
import { Redis } from 'ioredis'
const isBuildOrTest = process.env.TEKIR_RUNNER === 'build' ||
process.env.TEKIR_RUNNER === 'test'
const client = new Redis(process.env.REDIS_URL!, {
// `lazyConnect` keeps the constructor side-effect-free until the
// first command. Pair it with a guarded `connect()` call below so
// the bundler does not pay for a TCP handshake it never uses.
lazyConnect: true,
})
export function startSubscriber(channels: string[]) {
if (isBuildOrTest) return // bundle/test path: register no listeners
client.subscribe(...channels)
client.on('message', handle)
}
export { client }Both branches are needed:
buildguards against a doomed connection during bundling. The bundler imports every module to follow its static graph; a Redis client that connects on import would force the bundler to talk to your Redis just to learn the module's exports.testguards against the equivalent in test runners. Tests typically point at fakes or in-memory stores; letting the production client try to connect during a unit test wastes time and surfaces flakes.
Examples
Project scripts
// Before — every project's package.json had to set the flag manually:
{
"scripts": {
"build": "TEKIR_RUNNER=build tekir build --outdir ./dist",
"test": "TEKIR_RUNNER=test bun test"
}
}
// After — the framework sets it for you. Project scripts stay clean:
{
"scripts": {
"build": "tekir build --outdir ./dist",
"test": "tekir test"
}
}Framework-side gates that already use the flag
// Inside a tekir() entry — the same flag drives the framework's own
// build-time and test-time skip paths. There is nothing to opt into;
// these short-circuits fire as soon as `TEKIR_RUNNER` is set.
import { tekir } from '@tekir/core'
const app = await tekir({
config: { app: { port: 20000 } },
frontend: { type: 'vite' }, // dev gateway is skipped under TEKIR_RUNNER=test
})
await app.router.registerDir('./controllers')
app.start(() => console.log('ready')) // no-op under TEKIR_RUNNER=test
// (use `{ force: true }` to opt back in
// for integration tests that need a
// real socket).Guarantees
TEKIR_RUNNERis set before the user entry is imported, so module-top-level code observes the right value.- The framework uses
??=: an outer process can pin a value first and Tekir will not overwrite it. - Library authors can rely on the variable being a stable string (
'build','test', or unset). New values will only land in major releases. - Custom workflows that want library code to behave as if it were a real run can simply
delete process.env.TEKIR_RUNNERbefore importing the entry.