Skip to main content

Guide

std-env vs ci-info vs is-ci (2026)

Compare std-env, ci-info, and is-ci for detecting runtime environments in Node.js. CI/CD detection, provider identification, platform detection, and which.

·PkgPulse Team·
0

TL;DR

std-env is the UnJS runtime environment detection library — detects CI, provider, platform, Node.js version, Bun/Deno, production mode, and more in a single zero-dependency package. ci-info detects CI/CD environments — identifies which CI provider (GitHub Actions, GitLab CI, Jenkins, etc.) and whether it's a PR build. is-ci is the simplest check — a single boolean: are we running in CI or not? In 2026: std-env for comprehensive environment detection, ci-info for detailed CI provider info, is-ci for a quick boolean check.

Key Takeaways

  • std-env: ~15M weekly downloads — UnJS, detects CI + provider + platform + runtime, zero deps
  • ci-info: ~20M weekly downloads — CI provider detection, PR detection, supports 50+ CI services
  • is-ci: ~15M weekly downloads — single boolean isCI, uses ci-info internally
  • Tools use CI detection to: disable interactive prompts, skip colors, adjust verbosity, enable caching
  • std-env also detects Bun, Deno, edge runtimes, production mode — more than just CI
  • is-ci is just require("ci-info").isCI exported as a module

is-ci

is-ci — the simplest check:

Usage

import isCI from "is-ci"

if (isCI) {
  console.log("Running in CI — disabling interactive prompts")
} else {
  console.log("Running locally — full interactive mode")
}

// Common patterns:
const spinner = isCI ? null : createSpinner("Building...")
const colors = isCI ? false : true
const logLevel = isCI ? "info" : "debug"

What it checks

is-ci checks these environment variables:
  CI=true                → Generic CI indicator
  CONTINUOUS_INTEGRATION → Travis CI
  BUILD_NUMBER          → Jenkins
  GITHUB_ACTIONS        → GitHub Actions
  GITLAB_CI             → GitLab CI
  CIRCLECI              → CircleCI
  ... and 50+ more CI providers

ci-info

ci-info — detailed CI detection:

Basic usage

import ci from "ci-info"

ci.isCI          // true if running in any CI
ci.isPR          // true if this is a pull request build
ci.name          // "GitHub Actions" | "GitLab CI" | "Jenkins" | null
ci.id            // "GITHUB" | "GITLAB" | "JENKINS" | null

Provider-specific behavior

import ci from "ci-info"

if (ci.isCI) {
  console.log(`Running on ${ci.name}`)

  if (ci.isPR) {
    // PR-specific behavior:
    console.log("This is a pull request build")
    // Skip deployment, run extra checks, post comments
  }

  switch (ci.id) {
    case "GITHUB":
      // GitHub Actions — use GITHUB_TOKEN, set output
      console.log(`Workflow: ${process.env.GITHUB_WORKFLOW}`)
      console.log(`Run ID: ${process.env.GITHUB_RUN_ID}`)
      break

    case "GITLAB":
      // GitLab CI — use CI_JOB_TOKEN
      console.log(`Pipeline: ${process.env.CI_PIPELINE_ID}`)
      break

    case "JENKINS":
      // Jenkins — use BUILD_URL
      console.log(`Build: ${process.env.BUILD_NUMBER}`)
      break
  }
}

Supported CI providers

ci-info supports 50+ CI providers:
  GitHub Actions, GitLab CI, Jenkins, CircleCI,
  Travis CI, Azure Pipelines, Bitbucket Pipelines,
  AWS CodeBuild, Google Cloud Build, Buildkite,
  Drone, Semaphore, TeamCity, Vercel, Netlify,
  Heroku, Railway, Render, Fly.io, and many more

std-env

std-env — comprehensive environment detection:

CI detection

import { isCI, provider, providerInfo } from "std-env"

// CI detection (like is-ci):
isCI        // true/false

// Provider name:
provider    // "github_actions" | "gitlab_ci" | "jenkins" | ...

// Full provider info:
providerInfo
// → { name: "github_actions", isCI: true, isPR: true, ... }

Runtime detection

import {
  isNode, isBun, isDeno,
  isWindows, isLinux, isMacOS,
  isEdgeLight, isNetlify, isVercel,
  nodeVersion, nodeMajorVersion,
} from "std-env"

// Runtime:
isNode        // true if Node.js
isBun         // true if Bun
isDeno        // true if Deno

// Platform:
isWindows     // true if Windows
isLinux       // true if Linux
isMacOS       // true if macOS

// Edge runtimes:
isEdgeLight   // true if running in edge runtime
isNetlify     // true if Netlify Functions/Edge
isVercel      // true if Vercel Functions/Edge

// Node.js version:
nodeVersion     // "22.0.0"
nodeMajorVersion // 22

Production/development detection

import { isProduction, isDevelopment, isTest, isDebug } from "std-env"

// Environment mode:
isProduction   // NODE_ENV === "production"
isDevelopment  // NODE_ENV === "development"
isTest         // NODE_ENV === "test"
isDebug        // DEBUG env var is set

// Conditional behavior:
const logLevel = isProduction ? "warn" : isTest ? "silent" : "debug"
const sourceMaps = isProduction ? false : true
const minify = isProduction ? true : false

Process flags

import { hasTTY, isColorSupported, isMinimal } from "std-env"

// Terminal capabilities:
hasTTY            // true if stdout is a TTY
isColorSupported  // true if terminal supports colors

// Minimal mode:
isMinimal         // true if running in a minimal environment

// Useful for CLI tools:
if (!hasTTY) {
  // Non-interactive — no spinners, no prompts
}

if (isColorSupported) {
  // Use colored output
}

Practical example: CLI tool setup

import {
  isCI, provider, hasTTY, isColorSupported,
  isProduction, nodeVersion, isBun
} from "std-env"

function createCLIConfig() {
  return {
    // Disable interactive features in CI:
    interactive: !isCI && hasTTY,

    // Colors based on terminal support:
    colors: isColorSupported && !isCI,

    // Verbose in dev, quiet in CI:
    logLevel: isCI ? "info" : "debug",

    // Show provider info in CI:
    ciProvider: isCI ? provider : null,

    // Runtime info:
    runtime: isBun ? `Bun` : `Node.js ${nodeVersion}`,

    // Production optimizations:
    cache: isProduction,
    sourceMaps: !isProduction,
  }
}

Feature Comparison

Featurestd-envci-infois-ci
CI detection
CI provider name
PR detection
Runtime detection✅ (Node/Bun/Deno)
Platform detection✅ (Win/Mac/Linux)
Edge runtime
Production mode
TTY detection
Color support
Node.js version
Dependencies00ci-info
Weekly downloads~15M~20M~15M

When to Use Each

Use std-env if:

  • Need comprehensive environment detection (CI + runtime + platform)
  • Building cross-runtime tools (Node.js, Bun, Deno)
  • Want one import for all environment checks
  • In the UnJS ecosystem

Use ci-info if:

  • Need detailed CI provider identification
  • Want PR detection for CI-specific logic
  • Building CI/CD tools or GitHub Actions
  • Only need CI-related detection

Use is-ci if:

  • Just need a boolean: "are we in CI?"
  • Simplest possible check
  • Don't need provider info or other detection

Understanding where each package is used in production helps clarify which one fits your needs.

std-env is used across the UnJS ecosystem. Nitro (the server engine powering Nuxt) uses std-env to detect whether it's running in a Vercel Edge Function, a Cloudflare Worker, or a Node.js server — each environment requires different startup behavior. Vite uses std-env for platform detection during server-side rendering. The pattern is consistent: std-env for tools that need to adapt behavior across multiple runtime environments.

ci-info powers much of the CI-aware behavior in the Node.js ecosystem. It's a transitive dependency in Vitest (for disabling color output in CI), create-react-app, Playwright, and most major test frameworks. Its 20M weekly downloads mostly come from being pulled in transitively — if you use a test runner, you almost certainly have ci-info in your dependency tree already.

is-ci is the simplest entry point into CI detection. It's in countless CLI tools as a quick check: "if CI, disable interactive prompts." Yeoman generators, scaffolding tools, and package managers use it to decide whether to show interactive menus or run in silent mode.


Building CI/CD-Aware Scripts

The real value of these packages shows up when building tools or scripts that need to behave differently across environments:

import { isCI, provider, hasTTY, isColorSupported, isProduction } from "std-env"
import ci from "ci-info"

// A release script that adapts to its environment:
async function runRelease() {
  // Step 1: determine verbosity
  const verbose = !isCI && !isProduction
  const showProgress = hasTTY && !isCI

  // Step 2: CI-specific git configuration
  if (isCI) {
    // Set git user for automated commits
    await exec("git config user.email 'ci@pkgpulse.com'")
    await exec("git config user.name 'Release Bot'")
  }

  // Step 3: provider-specific behavior
  if (ci.isCI) {
    switch (ci.id) {
      case "GITHUB":
        // Write outputs for GitHub Actions step summary
        const summary = process.env.GITHUB_STEP_SUMMARY
        if (summary) fs.appendFileSync(summary, "## Release Notes\n...")
        break

      case "GITLAB":
        // Create GitLab release via API
        await createGitLabRelease(process.env.CI_PROJECT_ID!)
        break
    }
  }

  // Step 4: skip deployment on PR builds
  if (ci.isPR) {
    console.log("PR build — skipping deployment, running validation only")
    await runValidation()
    return
  }

  // Step 5: run the actual release
  await runBuild({ verbose })
  await runDeploy({ showProgress })
}

This pattern — combining std-env for environment type detection and ci-info for CI-specific branching — is the recommended approach for complex build tools.


The Multi-Runtime Shift: Why std-env Matters in 2026

In 2020, CI detection was the main use case for these packages. By 2026, the landscape changed significantly: Node.js is no longer the only JavaScript runtime targeting server environments. Bun runs 3-4x faster for scripts and is used in CI. Deno is used in edge deployments. Cloudflare Workers run V8 but not Node.js.

std-env was built for this reality. When a Nitro server starts up, it might be in a Node.js Lambda function, a Cloudflare Worker, a Vercel Edge Function, or a local development server. Each needs different behavior:

import { isNode, isBun, isDeno, isEdgeLight, isVercel, isNetlify } from "std-env"

function configureDatabase() {
  if (isEdgeLight || isVercel || isNetlify) {
    // Edge runtime — no long-lived connections, use HTTP-based clients
    return createHttpDatabaseClient()
  }

  if (isBun) {
    // Bun — use Bun's native SQLite or its fast fetch
    return createBunDatabaseClient()
  }

  if (isNode) {
    // Standard Node.js — traditional connection pooling
    return createPooledDatabaseClient()
  }

  throw new Error("Unsupported runtime")
}

ci-info and is-ci don't have equivalent runtime detection — they're purely for CI environment detection. If your use case is only "are we in GitHub Actions?", ci-info remains the right tool. If your use case is "what kind of environment are we running in?", std-env is the answer.


Bundle Size and Performance

These packages are small, but bundle size matters when they are used in client-side code or edge runtimes:

PackageMin+gzipInstall sizeDependencies
std-env~1.2 KB~12 KB0
ci-info~2.8 KB~18 KB0
is-ci~0.1 KB~2 KBci-info

is-ci is trivially small because it is literally module.exports = require("ci-info").isCI. The install includes ci-info as a full dependency though, so the actual disk footprint is the same as ci-info.

std-env is larger than is-ci's minimal wrapper but smaller than ci-info because it only checks a curated set of environment variables per platform, rather than enumerating all 50+ CI providers in detail. The tradeoff: std-env does not identify every possible CI provider by name, but it identifies the major ones (GitHub Actions, GitLab CI, Vercel, Netlify) that account for the vast majority of actual usage.

For edge runtime bundles (Cloudflare Workers, Vercel Edge Functions), all three packages work — they only check process.env which is available in most edge runtimes. std-env's edge runtime detection is particularly useful here: isEdgeLight and isVercel let your code know it is running in an edge context where Node.js APIs like fs are unavailable.

Migration Guide

From is-ci to std-env:

// Before:
import isCI from "is-ci"
if (isCI) { /* ... */ }

// After:
import { isCI } from "std-env"
if (isCI) { /* ... */ }
// Same boolean, zero behavior change

From ci-info to std-env:

// Before:
import ci from "ci-info"
ci.isCI   // boolean
ci.name   // "GitHub Actions" | null
ci.isPR   // boolean

// After:
import { isCI, provider, providerInfo } from "std-env"
isCI           // boolean — same
provider       // "github_actions" | "gitlab_ci" | null — snake_case instead of sentence case
providerInfo   // { name: string, isCI: boolean } — similar structure

// Note: std-env's provider names use snake_case ("github_actions")
// ci-info uses human-readable names ("GitHub Actions")
// If you display the provider name to users, you may need to map it

From std-env to ci-info (if you need more CI providers):

// std-env covers major providers but not all 50+ that ci-info supports
// Migrate when you need very specific CI detection:

// Before:
import { isCI, provider } from "std-env"
if (provider === "github_actions") { /* ... */ }

// After:
import ci from "ci-info"
if (ci.id === "GITHUB") { /* ... */ }
// ci.id uses uppercase identifiers

Decision Guide: Which to Use in 2026

Use CaseRecommended Package
Just need if (isCI) checkis-ci or std-env
Need CI provider name (GitHub Actions, etc.)ci-info
Need PR detectionci-info
Need runtime detection (Node/Bun/Deno)std-env
Need platform detection (Win/Mac/Linux)std-env
Need edge runtime detection (Vercel, Cloudflare)std-env
Need production/development modestd-env
Need TTY and color support detectionstd-env
Building a cross-runtime librarystd-env
Building CI/CD-specific toolingci-info
In the UnJS ecosystemstd-env
Want one package for all environment checksstd-env

The practical rule for 2026: if you are building a tool that runs in multiple environments (Node.js, Bun, edge runtimes, CI), use std-env. If your tool only runs in CI and you need to identify which provider it is running on with high precision, ci-info has better coverage of obscure CI systems. If your codebase currently uses is-ci and you do not need anything else, there is no strong reason to migrate — it works and has zero bugs by virtue of its simplicity.

Performance Impact of Environment Detection

Environment detection packages are commonly required at the top of build scripts and CLI entry points, where startup latency matters. std-env, ci-info, and is-ci are all synchronous and read process.env at require/import time — they add essentially zero async overhead. The meaningful performance distinction is their install size and require time, which affects tools that are invoked thousands of times in CI (per-file linting, per-test setup). is-ci's tiny footprint (it is literally a re-export of a single property) makes it the fastest to require. std-env's comprehensive detection adds negligible overhead in absolute terms — reading a dozen environment variables is microseconds, not milliseconds. For build tools that launch many worker processes (webpack's thread-loader, vitest's parallel workers), environment detection that runs in each worker process is multiplied by the number of workers. Even in this scenario, the overhead of any of these three packages is negligible compared to module resolution and compilation costs. The practical advice is to choose based on features, not performance — none of these packages are performance bottlenecks in real-world tooling.

Testing Environment Detection Code

Code that branches on environment detection is notoriously difficult to test because the environment variables it reads are set by the OS or CI system, not by your test file. The recommended approach is to isolate environment detection into a thin module that reads process.env and export the values, then override process.env in tests using vi.stubEnv() (Vitest) or jest.replaceProperty(process, 'env', {...}) (Jest). std-env reads environment variables at import time by default, which means you must stub the environment before importing the module. Using dynamic imports in tests — await import('std-env') — after setting up stubs avoids this issue. ci-info caches its results after the first read, so test isolation requires clearing the require cache or using a fresh import context between tests. For unit testing build scripts that branch on isCI or provider, mocking at the module level is cleaner than trying to mutate process.env in the middle of a test.

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on std-env v3.x, ci-info v4.x, and is-ci v3.x.

Compare environment detection package health on PkgPulse. Also see cac vs meow vs arg 2026 and cosmiconfig vs lilconfig vs c12.

Related: archiver vs adm-zip vs JSZip (2026).

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.