
Security News
The Changelog Podcast: Practical Steps to Stay Safe on npm
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.
@condu/core
Advanced tools
condu is a configuration management tool for JavaScript/TypeScript projects that solves the "config hell" problem by providing a unified approach to manage all project configuration in code.
condu is a configuration management tool for JavaScript/TypeScript projects that solves the "config hell" problem by providing a unified approach to manage all project configuration in code.
Modern JavaScript/TypeScript projects require numerous configuration files:
These configurations:
condu solves these problems by:
# Using npm
npm install condu --save-dev
# Using yarn
yarn add condu --dev
# Using pnpm
pnpm add condu -D
.config/condu.ts file in your project root:import { configure } from "condu";
import { typescript } from "@condu-feature/typescript";
import { eslint } from "@condu-feature/eslint";
import { prettier } from "@condu-feature/prettier";
export default configure({
  features: [typescript(), eslint(), prettier()],
});
npx condu apply
This will generate all the necessary configuration files based on your .config/condu.ts file.
Features are the building blocks of condu. Each feature manages configuration for a specific tool or aspect of your project.
condu comes with many built-in features:
Each feature can be configured with options:
typescript({
  preset: "esm-first",
  tsconfig: {
    compilerOptions: {
      strict: true,
      skipLibCheck: true,
    },
  },
});
condu excels at managing monorepo configurations. Define your workspace structure:
export default configure({
  projects: [
    {
      parentPath: "packages/features",
      nameConvention: "@myorg/feature-*",
    },
    {
      parentPath: "packages/core",
      nameConvention: "@myorg/*",
    },
  ],
  features: [
    // ...features
  ],
});
You can create custom features to encapsulate your own configuration logic.
A feature's primary purpose is to define a recipe - a list of changes that should be made whenever condu apply is run. Think of the calls to condu recipe APIs similar to React component hooks.
For one-off or simple modifications, you can define features inline directly in your config file:
import { configure } from "condu";
import { typescript } from "@condu-feature/typescript";
export default configure({
  features: [
    typescript(),
    // Anonymous arrow function feature
    (condu) => {
      condu.in({ kind: "package" }).modifyPublishedPackageJson((pkg) => ({
        ...pkg,
        // Add sideEffects: false to all packages for better tree-shaking
        sideEffects: false,
      }));
    },
    // Named functions will use their name as the feature name
    function addLicense(condu) {
      condu.root.generateFile("LICENSE", {
        content: `MIT License\n\nCopyright (c) ${new Date().getFullYear()} My Organization\n\n...`,
      });
    },
  ],
});
Inline features:
defineFeatureFor creating proper reusable features, use the defineFeature function:
import { defineFeature } from "condu";
export const myFeature = (options = {}) =>
  defineFeature("myFeature", {
    // The main recipe that runs during configuration application
    defineRecipe(condu) {
      // Generate a configuration file
      condu.root.generateFile("my-config.json", {
        content: {
          enabled: options.enabled ?? true,
          settings: options.settings ?? {},
        },
        stringify: JSON.stringify,
      });
      // Add required dependencies
      condu.root.ensureDependency("my-library");
      // Target specific packages in a monorepo
      condu.in({ kind: "package" }).modifyPackageJson((pkg) => ({
        ...pkg,
        scripts: {
          ...pkg.scripts,
          "my-script": "my-command",
        },
      }));
    },
  });
When you want features to influence each other, use the PeerContext system. For example, the TypeScript feature could automatically enable TypeScript-specific ESLint rules as in the example below:
// ESLint feature definition
declare module "condu" {
  interface PeerContext {
    eslint: {
      rules: Record<string, unknown>;
      plugins: string[];
      extends: string[];
    };
  }
}
export const eslint = (options = {}) =>
  defineFeature("eslint", {
    initialPeerContext: {
      rules: {
        "no-unused-vars": "error",
      },
      plugins: [],
      extends: ["eslint:recommended"],
    },
    defineRecipe(condu, peerContext) {
      // Generate eslint config using the final peer context
      // which may have been modified by other features
      condu.root.generateFile(".eslintrc.js", {
        content: `module.exports = {
          extends: ${JSON.stringify(peerContext.extends)},
          plugins: ${JSON.stringify(peerContext.plugins)},
          rules: ${JSON.stringify(peerContext.rules, null, 2)}
        }`,
      });
      // Ensure ESLint dependency
      condu.root.ensureDependency("eslint");
      // Ensure any plugins are installed
      for (const plugin of peerContext.plugins) {
        condu.root.ensureDependency(`eslint-plugin-${plugin}`);
      }
    },
  });
// TypeScript feature that influences ESLint
export const typescript = (options = {}) =>
  defineFeature("typescript", {
    initialPeerContext: {
      // TypeScript-specific context
      config: {
        strict: true,
        // ...other TypeScript options
      },
    },
    // Here TypeScript feature modifies ESLint's context
    modifyPeerContexts: (project, initialContext) => ({
      eslint: (current) => ({
        ...current,
        // Add TypeScript ESLint plugin
        plugins: [...current.plugins, "typescript"],
        // Add TypeScript ESLint config
        extends: [...current.extends, "plugin:@typescript-eslint/recommended"],
        // Add/modify TypeScript-specific rules
        rules: {
          ...current.rules,
          "@typescript-eslint/no-explicit-any": "error",
          "@typescript-eslint/explicit-function-return-type": "warn",
          // Disable the base ESLint rule in favor of TypeScript-specific one
          "no-unused-vars": "off",
          "@typescript-eslint/no-unused-vars": "error",
        },
      }),
    }),
    defineRecipe(condu, peerContext) {
      // Generate tsconfig.json
      condu.root.generateFile("tsconfig.json", {
        content: {
          compilerOptions: peerContext.config,
        },
        stringify: (obj) => JSON.stringify(obj, null, 2),
      });
      // Ensure TypeScript dependencies
      condu.root.ensureDependency("typescript");
      // Also add TypeScript ESLint dependencies if ESLint is used
      if (condu.project.hasFeature("eslint")) {
        condu.root.ensureDependency("@typescript-eslint/parser");
        condu.root.ensureDependency("@typescript-eslint/eslint-plugin");
      }
    },
  });
With this setup:
defineGarnish for Post-ProcessingFor final adjustments after all features have run their main recipes, use defineGarnish:
import { defineFeature } from "condu";
export const packageScripts = () =>
  defineFeature("packageScripts", {
    // Standard recipe for basic setup
    defineRecipe(condu) {
      // Basic script setup
      condu.root.modifyPackageJson((pkg) => ({
        ...pkg,
        scripts: {
          ...pkg.scripts,
          start: "node index.js",
        },
      }));
    },
    // Garnish runs after all other features have applied their recipes
    defineGarnish(condu) {
      // Access the complete state after all features have run
      const allTasks = condu.globalRegistry.tasks;
      // Generate scripts based on tasks defined by other features
      condu.root.modifyPackageJson((pkg) => {
        const scripts = { ...pkg.scripts };
        // Create aggregate scripts based on task types
        const buildTasks = allTasks.filter(
          (task) => task.taskDefinition.type === "build",
        );
        if (buildTasks.length > 0) {
          scripts["build:all"] = buildTasks
            .map((t) => `npm run build:${t.taskDefinition.name}`)
            .join(" && ");
          // Add individual build scripts for each task
          for (const task of buildTasks) {
            scripts[`build:${task.taskDefinition.name}`] =
              task.taskDefinition.command;
          }
        }
        // Create test scripts for all test tasks
        const testTasks = allTasks.filter(
          (task) => task.taskDefinition.type === "test",
        );
        if (testTasks.length > 0) {
          scripts["test:all"] = testTasks
            .map((t) => `npm run test:${t.taskDefinition.name}`)
            .join(" && ");
        }
        return { ...pkg, scripts };
      });
    },
  });
The defineGarnish function:
globalRegistry with information about all tasks, dependencies, and filescondu objectThe main condu object available in feature recipes contains the following:
condu.project: Information about the projectcondu.root: Recipe API for the root packagecondu.in(criteria): Recipe API for the packages matching the criteriaAdditionally, when used in defineGarnish:
condu.globalRegistry: Contains the summary of all the recipes, including:
Methods for declaring configuration changes:
Creates files that are fully managed by condu.
generateFile<PathT extends string>(path: PathT, options: GenerateFileOptionsForPath<PathT>): ScopedRecipeApi
Examples:
// Generate a standard JSON configuration file
condu.root.generateFile("tsconfig.json", {
  content: {
    compilerOptions: {
      strict: true,
      target: "ES2020",
    },
    include: ["**/*.ts"],
  },
  // Automatically stringify JSON with formatting
  stringify: (obj) => JSON.stringify(obj, null, 2),
});
// Generate a YAML file
condu.root.generateFile("pnpm-workspace.yaml", {
  content: {
    packages: ["packages/*"],
  },
  // Use a custom YAML stringifier
  stringify: getYamlStringify(),
  // Set file attributes for special handling
  attributes: {
    gitignore: false, // Don't add to .gitignore
  },
});
// Generate a text file with raw content
condu.root.generateFile(".gitignore", {
  content: ["node_modules", "build", ".DS_Store", "*.log"].join("\n"),
  // No stringification needed for plain text
});
Modifies a file that was previously generated by condu.
modifyGeneratedFile<PathT extends string>(path: PathT, options: ModifyGeneratedFileOptions<ResolvedSerializedType<PathT>>): ScopedRecipeApi
Examples:
// Modify an existing tsconfig.json
condu.root.modifyGeneratedFile("tsconfig.json", {
  content: ({ content = {} }) => ({
    ...content,
    compilerOptions: {
      ...content.compilerOptions,
      // Add or update specific compiler options
      declaration: true,
      sourceMap: true,
    },
  }),
});
Modifies files that should remain editable by users.
modifyUserEditableFile<PathT extends string, DeserializedT = ...>(path: PathT, options: ModifyUserEditableFileOptions<DeserializedT>): ScopedRecipeApi
ifNotExists: "create"Examples:
// Modify a JSON file with type safety
condu.root.modifyUserEditableFile(".vscode/settings.json", {
  // Get default JSON parsers and stringifiers
  ...getJsonParseAndStringify<MySettingsType>(),
  // Create the file if it doesn't exist (that's the default)
  ifNotExists: "create", // other options: "ignore" | "error"
  // Modify or provide content
  content: ({ content = {} }) => ({
    ...content,
    // Add or update specific settings while preserving others
    "typescript.tsdk": "node_modules/typescript/lib",
    "editor.formatOnSave": true,
  }),
});
// Modify a custom format file
condu.root.modifyUserEditableFile(".npmrc", {
  // Custom parser for the specific file format
  parse: (rawContent) => customParse(rawContent),
  // Custom stringifier for the specific file format
  stringify: (data) => customStringify(data),
  // Merge content
  content: ({ content = {} }) => ({
    ...content,
    "my-setting": "value",
  }),
  // Set file attributes (e.g., for .gitignore)
  attributes: { gitignore: false },
});
Ensures a dependency is installed in the package.
ensureDependency(name: string, dependency?: DependencyDefinitionInput): ScopedRecipeApi
onlyBuiltDependenciesExamples:
// Add a simple dev dependency with default settings
condu.root.ensureDependency("typescript");
// Add a dependency with specific options
condu.root.ensureDependency("react", {
  // Specify which dependency list to use
  list: "dependencies",
  // Specify exact version
  version: "18.2.0",
  // Use a custom name for the dependency
  installAsAlias: "react-aliased",
  // Specify how versioning is managed
  managed: "version", // or "presence" to preserve existing versions
});
// Add peer dependencies
condu.root.ensureDependency("react-dom", {
  list: "peerDependencies",
  // Use semver range prefix
  rangePrefix: ">=",
  // Mark as built for pnpm
  built: true,
});
Sets dependency resolutions to override specific package versions.
setDependencyResolutions(resolutions: Record<string, string>): ScopedRecipeApi
Examples:
// Force specific versions of packages
condu.root.setDependencyResolutions({
  lodash: "4.17.21",
  "webpack/tapable": "2.2.1",
  "@types/react": "18.0.0",
});
Modifies the package.json file with a custom transformer function.
modifyPackageJson(modifier: PackageJsonModifier): ScopedRecipeApi
Examples:
// Add custom scripts based on project structure
condu.root.modifyPackageJson((pkg) => ({
  ...pkg,
  scripts: {
    ...pkg.scripts,
    build: "tsc -p tsconfig.json",
    test: "vitest run",
    lint: "eslint .",
  },
  // Add custom metadata
  keywords: [...(pkg.keywords || []), "condu-managed"],
}));
// Add or modify specific fields
condu.in({ kind: "package" }).modifyPackageJson((pkg) => ({
  ...pkg,
  // Add TypeScript configuration
  types: "./build/index.d.ts",
  // Ensure sideEffects flag is set for tree-shaking
  sideEffects: false,
}));
Modifies the package.json that will be used during publishing.
modifyPublishedPackageJson(modifier: PackageJsonModifier): ScopedRecipeApi
Examples:
// Configure exports map for published packages
condu.in({ kind: "package" }).modifyPublishedPackageJson((pkg) => ({
  ...pkg,
  // Add standard entry points
  main: "./build/index.js",
  module: "./build/index.js",
  types: "./build/index.d.ts",
  // Configure exports map
  exports: {
    ".": {
      import: "./build/index.js",
      require: "./build/index.cjs",
      types: "./build/index.d.ts",
    },
    "./package.json": "./package.json",
  },
  // Remove development-only fields
  devDependencies: undefined,
}));
Defines a task that can be run using a task runner.
defineTask(name: string, taskDefinition: Omit<Task, "name">): ScopedRecipeApi
Examples:
// Define a build task
condu.root.defineTask("build", {
  type: "build",
  command: "tsc -p tsconfig.json",
  inputs: ["**/*.ts", "tsconfig.json"],
  outputs: ["build/**"],
});
// Define a test task that depends on the build task
condu.root.defineTask("test", {
  type: "test",
  command: "vitest run",
  deps: ["build"],
});
Marks a file to be ignored by certain tools.
ignoreFile(path: string, options?: Omit<PartialGlobalFileAttributes, "inAllPackages">): ScopedRecipeApi
Examples:
// Add a file to .gitignore
condu.root.ignoreFile("build/");
// Configure file attributes
condu.root.ignoreFile("temp/debug.log", {
  gitignore: true,
  vscode: false, // will still be visible in VSCode
});
The PeerContext system enables features to share information and coordinate with each other:
declare module "condu" {
  interface PeerContext {
    myFeature: {
      config: MyConfigType;
    };
  }
}
initialPeerContext: {
  config: {
    /* initial data */
  }
}
modifyPeerContexts: (project, initialContext) => ({
  otherFeature: (current) => ({
    ...current,
    someOption: true,
  }),
});
defineRecipe(condu, peerContext) {
  // Use peerContext.config
}
This system enables powerful coordination between features without tight coupling.
To resolve any type-system issues when building a feature that might influence others, be sure to include the peer features as an optional peerDependency, with a broad version requirement (such as * or >=1.0.0).
Condu provides a comprehensive CLI for managing your projects.
condu init [project-name]Initializes a new condu project in the current directory or creates a new directory with the specified name.
# Initialize in current directory
condu init
# Create a new project directory
condu init my-new-project
Options: None
The init command will:
.config directory with a default condu.ts filecondu applycondu applyApplies configuration from your .config/condu.ts file, generating or updating all configuration files.
condu apply
Options: None
This is the primary command you'll use to apply changes after modifying your condu configuration.
condu create <partial-path> [options]Creates a new package in a monorepo according to your project conventions.
# Create a basic package
condu create features/my-feature
# Create a package with a custom name
condu create features/my-feature --as @myorg/custom-name
Options:
--as <name>: Specify a custom package name--description <text>: Add a description to the package.json--private: Mark the package as privatecondu tsc [options]Builds TypeScript code and additionally creates CommonJS (.cjs) or ES Module (.mjs) versions of your code.
# Build with CommonJS output
condu tsc --preset ts-to-cts
# Build with ES Module output
condu tsc --preset ts-to-mts
Options:
--preset ts-to-cts|ts-to-mts: Generate CommonJS or ES Module versionscondu release [packages...] [options]Prepares packages for release by generating distributable files and optionally publishing to npm.
# Release all packages
condu release
# Release specific packages
condu release @myorg/package1 @myorg/package2
# Do a dry run without publishing
condu release --dry-run
Options:
--ci: Mark non-released packages as private (useful in CI environments)--npm-tag <tag>: Specify the npm tag to use (default: latest)--dry-run: Prepare packages without actually publishingcondu exec <command> [args...]Executes a command in the context of a selected package.
# Run in current directory
condu exec npm run test
# Run in a specific package
condu exec --pkg @myorg/my-package npm run test
Options:
--cwd <path>: Specify the working directory--pkg <package>: Specify the target packagecondu help: Shows help informationcondu version: Shows the current condu versioncreate-react-app obsoletePresets combine multiple features with sensible defaults:
// monorepo.ts
export const monorepo =
  (options = {}) =>
  (pkg) => ({
    projects: [
      {
        parentPath: "packages",
        nameConvention: `@${pkg.name}/*`,
      },
    ],
    features: [
      typescript(options.typescript),
      eslint(options.eslint),
      prettier(options.prettier),
      pnpm(options.pnpm),
      // Add more features
    ],
  });
Use a preset in your project:
import { configure } from "condu";
import { monorepo } from "@condu-preset/monorepo";
export default configure(
  monorepo({
    // Override specific feature options
    typescript: {
      preset: "commonjs-first",
    },
  }),
);
condu streamlines configuration management by:
Say goodbye to config hell and focus on building your application!
FAQs
condu is a configuration management tool for JavaScript/TypeScript projects that solves the "config hell" problem by providing a unified approach to manage all project configuration in code.
The npm package @condu/core receives a total of 3 weekly downloads. As such, @condu/core popularity was classified as not popular.
We found that @condu/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.

Security News
Experts push back on new claims about AI-driven ransomware, warning that hype and sponsored research are distorting how the threat is understood.

Security News
Ruby's creator Matz assumes control of RubyGems and Bundler repositories while former maintainers agree to step back and transfer all rights to end the dispute.