
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@codedrifters/configulator
Advanced tools
A library of Projen components used by CodeDrifters to manage repository configuration across various projects. Configulator extends standard Projen configurations with our preferred defaults and provides company-specific components and workflows.
This guide explains the core infrastructure tools used in our projects: Projen and Configulator. These tools help us maintain consistency across multiple projects and reduce repetitive setup work.
Projen is a project generator and configuration management tool that:
Components: Individual pieces of configuration (e.g., Jest, Vitest, Prettier, TypeScript). Each component configures specific files and settings. When you add or remove a component, all its associated files and configurations are automatically managed.
Project Types: Base-level configurations for different kinds of projects (e.g., TypeScript app, monorepo, React project).
Synthesis: The process of generating actual files from your configuration. Run npx projen to synthesize your project.
Configulator is CodeDrifters' custom extension of Projen. It:
@codedrifters/configulatorInstead of setting up each new project from scratch (90% identical setup repeated), Configulator lets us:
Configulator provides two main project types designed for monorepo workflows:
MonorepoProject extends Projen's TypeScriptAppProject and is designed specifically for monorepo root projects. It provides all the infrastructure needed to manage a monorepo with multiple sub-projects.
Use MonorepoProject for:
pnpm-workspace.yamlimport { MonorepoProject } from '@codedrifters/configulator';
const project = new MonorepoProject({
name: 'my-monorepo'
});
project.synth();
import { MonorepoProject } from '@codedrifters/configulator';
const project = new MonorepoProject({
name: 'my-monorepo',
turboOptions: {
remoteCacheOptions: {
profileName: 'profile-prod-000000000000-us-east-1',
oidcRole: 'arn:aws:iam::000000000000:role/TurborepoRemoteCachingRole',
endpointParamName: '/TURBOREPO/ENDPOINT/PARAMETER',
tokenParamName: '/TURBOREPO/TOKEN/PARAMETER',
teamName: 'prod',
},
},
pnpmOptions: {
pnpmWorkspaceOptions: {
onlyBuiltDependencies: ['@swc/core', 'esbuild', 'unrs-resolver'],
},
},
});
project.synth();
The MonorepoProject accepts all options from TypeScriptProjectOptions plus:
| Option | Type | Default | Description |
|---|---|---|---|
name | string | Required | Project name |
turbo | boolean | true | Enable Turborepo support |
turboOptions | TurboRepoOptions | undefined | Turborepo configuration including remote cache options |
pnpmOptions.version | string | VERSION.PNPM_VERSION | PNPM version to use |
pnpmOptions.pnpmWorkspaceOptions | PnpmWorkspaceOptions | See below | PNPM workspace configuration |
Default Behavior:
projenrcTs: true - Uses TypeScript for projen configurationprettier: true - Enables Prettier formattinglicensed: false - No license by defaultsampleCode: false - No sample code generatedjest: false - Jest disabled at root levelrelease: false - Root project is not releaseddepsUpgrade: false - No automatic dependency upgrades at rootdisableTsconfigDev: true - No tsconfig.dev.json at rootpackageManager: NodePackageManager.PNPM - PNPM is mandatorydefaultReleaseBranch: "main" - Standard branch namePull Request Linting:
feat, fix, docs, style, refactor, perf, test, build, ci, chore, revertOverriding Pull Request Lint Options:
To customize which commit types are allowed, override the githubOptions.pullRequestLintOptions:
const project = new MonorepoProject({
name: 'my-monorepo',
githubOptions: {
pullRequestLintOptions: {
semanticTitleOptions: {
types: ['feat', 'fix', 'docs', 'chore'], // Only allow these types
},
},
},
});
You can also configure other pull request linting options:
const project = new MonorepoProject({
name: 'my-monorepo',
githubOptions: {
pullRequestLintOptions: {
semanticTitleOptions: {
types: ['feat', 'fix', 'docs'],
requireScope: true, // Require scope in commit type (e.g., feat(ui):)
scopes: ['ui', 'api', 'core'], // Only allow these scopes
},
},
},
});
The root project automatically includes @codedrifters/configulator and constructs as dev dependencies.
TypeScriptProject extends Projen's TypeScriptProject with CodeDrifters defaults. It's designed for sub-projects within a monorepo or standalone TypeScript projects.
Use TypeScriptProject for:
MonorepoProject when used as a sub-projecttestRunner option. Jest uses @swc/jest for fast execution; Vitest uses a generated vitest.config.ts with coverage and watch tasks.import { MonorepoProject, TypeScriptProject } from '@codedrifters/configulator';
// Root project
const root = new MonorepoProject({
name: 'my-monorepo'
});
// Sub-project
const myPackage = new TypeScriptProject({
name: 'my-package',
packageName: '@myorg/my-package',
outdir: 'packages/my-package',
parent: root,
description: 'My awesome package',
deps: ['some-dependency'],
devDeps: ['aws-cdk-lib@catalog:', 'constructs@catalog:'],
});
root.synth();
import { MonorepoProject, TypeScriptProject } from '@codedrifters/configulator';
const root = new MonorepoProject({
name: 'my-monorepo'
});
const constructs = new TypeScriptProject({
name: '@codedrifters/constructs',
packageName: '@codedrifters/constructs',
outdir: 'packages/@codedrifters/constructs',
description: 'Constructs frequently used in CodeDrifter projects.',
repository: 'https://github.com/codedrifters/packages',
authorName: 'CodeDrifters',
authorOrganization: true,
licensed: false,
parent: root,
deps: [
'@aws-sdk/client-dynamodb',
'@types/aws-lambda',
'change-case@^4.0',
'type-fest@^4',
],
devDeps: ['aws-cdk-lib@catalog:', 'constructs@catalog:'],
peerDeps: ['aws-cdk-lib@catalog:', 'constructs@catalog:'],
release: true,
releaseToNpm: true,
});
root.synth();
When TypeScriptProject is created with a parent that is a MonorepoProject:
The TypeScriptProject accepts all options from Projen's TypeScriptProjectOptions with these defaults:
| Option | Type | Default | Description |
|---|---|---|---|
name | string | Required | Project name |
packageName | string | Same as name | NPM package name |
outdir | string | "." | Output directory (required for sub-projects) |
parent | MonorepoProject | undefined | Parent monorepo project (recommended) |
defaultReleaseBranch | string | "main" | Default release branch |
packageManager | NodePackageManager | PNPM | Package manager (always PNPM) |
prettier | boolean | true | Enable Prettier |
sampleCode | boolean | false | Generate sample code |
release | boolean | false | Enable NPM releases |
licensed | boolean | false | Include license (unless license option provided) |
testRunner | TestRunner | TestRunner.JEST | Test runner: TestRunner.JEST or TestRunner.VITEST |
vitestOptions | VitestOptions | undefined | Options for Vitest (only used when testRunner is TestRunner.VITEST) |
Test runner (Jest or Vitest):
jest.config.json, @swc/jest for fast compilation, test files in src/. Use when you want to keep existing Jest-based workflows.testRunner: TestRunner.VITEST to use Vitest. The project gets a generated vitest.config.ts, vitest (and optionally @vitest/coverage-v8) as devDeps, and tasks: test (runs vitest run), test:watch. Snapshots are updated automatically on each test run to match the project's previous Jest behavior (no separate snapshot-update step). Coverage directory is added to .gitignore and .npmignore. Jest is disabled (jest: false) when Vitest is selected; the two cannot be used together.NPM Ignore:
*.spec.*, *.test.*, and __fixtures__ patternsRelease Configuration:
outdirThe most common pattern is to create a monorepo with a root project and multiple sub-projects:
MonorepoProject:// projenrc/root-project.ts
import { MonorepoProject } from '@codedrifters/configulator';
export const configureRootProject = () => {
const project = new MonorepoProject({
name: 'my-monorepo',
turboOptions: {
remoteCacheOptions: {
profileName: 'my-profile',
oidcRole: 'arn:aws:iam::123456789012:role/TurborepoRole',
endpointParamName: '/TURBOREPO/ENDPOINT',
tokenParamName: '/TURBOREPO/TOKEN',
teamName: 'my-team',
},
},
});
return project;
};
TypeScriptProject:// projenrc/my-package.ts
import { MonorepoProject } from '@codedrifters/configulator';
import { TypeScriptProject } from '@codedrifters/configulator';
export const configureMyPackage = (parent: MonorepoProject) => {
const myPackage = new TypeScriptProject({
name: 'my-package',
packageName: '@myorg/my-package',
outdir: 'packages/my-package',
description: 'My package description',
parent,
deps: ['dependency-1', 'dependency-2'],
devDeps: ['aws-cdk-lib@catalog:', 'constructs@catalog:'],
});
return { myPackage };
};
// .projenrc.ts or projenrc/index.ts
import { configureRootProject } from './projenrc/root-project';
import { configureMyPackage } from './projenrc/my-package';
const root = configureRootProject();
const { myPackage } = configureMyPackage(root);
root.synth();
You can also use TypeScriptProject without a parent for standalone projects:
import { TypeScriptProject } from '@codedrifters/configulator';
const project = new TypeScriptProject({
name: 'standalone-project',
packageName: '@myorg/standalone-project',
description: 'A standalone TypeScript project',
deps: ['some-dependency'],
});
project.synth();
Note: Without a parent, the project will use the default PNPM version from Configulator's version constants.
⚠️ Important: All dependencies must be configured through Projen configuration files (
.projenrc.tsorprojenrc/*.ts), never by manually running package manager commands likenpm install,pnpm add, oryarn add. Manual installation will create conflicts with Projen-managed files and may be overwritten when you runnpx projen.
The MonorepoProject automatically sets up a default catalog with common dependencies:
// Defined in MonorepoProject defaults
defaultCatalog: {
'aws-cdk': VERSION.AWS_CDK_CLI_VERSION,
'aws-cdk-lib': VERSION.AWS_CDK_LIB_VERSION,
'projen': VERSION.PROJEN_VERSION,
'constructs': VERSION.AWS_CONSTRUCTS_VERSION,
'turbo': VERSION.TURBO_VERSION,
}
Sub-projects can reference these using the catalog: protocol:
const myPackage = new TypeScriptProject({
// ... other options
devDeps: ['aws-cdk-lib@catalog:', 'constructs@catalog:'],
peerDeps: ['aws-cdk-lib@catalog:', 'constructs@catalog:'],
});
Sub-projects can depend on other packages in the same monorepo:
const packageB = new TypeScriptProject({
// ... other options
deps: ['@myorg/package-a@workspace:*'],
});
You can also define custom catalogs in the MonorepoProject:
const root = new MonorepoProject({
name: 'my-monorepo',
pnpmOptions: {
pnpmWorkspaceOptions: {
defaultCatalog: {
'react': '^18.0.0',
'typescript': '^5.0.0',
},
namedCatalogs: {
frontend: {
'react': '^18.0.0',
'react-dom': '^18.0.0',
},
backend: {
'express': '^4.18.0',
},
},
},
},
});
Then reference them in sub-projects:
// Default catalog
deps: ['react@catalog:react']
// Named catalog
deps: ['react@catalog:frontend/react']
The catalog versions (e.g. VERSION.PROJEN_VERSION, VERSION.TURBO_VERSION) in src/versions.ts can be updated automatically so they stay in sync with npm releases while respecting the PNPM workspace minimumReleaseAge (so only versions published long enough ago are considered).
What it does
scripts/update-versions.ts at monorepo root) fetches the latest eligible version for each npm-backed constant from the npm registry using the time field.minimumReleaseAge minutes (from the PNPM workspace) are considered.workflow_dispatch), version updates are included in the same PR as dependency upgrades.How to run
From the monorepo root:
pnpm run update-versions
versions.ts and run npx projen):
pnpm run update-versions -- --apply
Adding a new npm-backed constant
src/versions.ts (e.g. MY_PACKAGE_VERSION: "1.0.0").VERSION_NPM_PACKAGES in src/version-package-map.ts:
{ key: "MY_PACKAGE_VERSION", npmPackage: "my-package" },
Constants not listed in VERSION_NPM_PACKAGES (e.g. NODE_WORKFLOWS) are skipped by the update script.When you create a MonorepoProject with turbo: true (the default), Turborepo is automatically configured. Sub-projects created with TypeScriptProject automatically get Turborepo task configuration if their parent has Turborepo enabled.
The integration includes:
dist/**, lib/**)See the Turbo Repo section for more details.
Extends TypeScriptProjectOptions with:
interface MonorepoProjectOptions {
name: string; // Required
turbo?: boolean; // Default: true
turboOptions?: TurboRepoOptions;
pnpmOptions?: {
version?: string;
pnpmWorkspaceOptions?: PnpmWorkspaceOptions;
};
// ... all TypeScriptProjectOptions
}
Extends Projen's TypeScriptProjectOptions with CodeDrifters defaults. See the TypeScriptProject section for details.
interface TurboRepoOptions {
turboVersion?: string;
remoteCacheOptions?: RemoteCacheOptions;
extends?: Array<string>;
globalDependencies?: Array<string>;
globalEnv?: Array<string>;
globalPassThroughEnv?: Array<string>;
ui?: 'tui' | 'stream';
envMode?: string;
// ... and more
}
interface RemoteCacheOptions {
profileName: string;
oidcRole: string;
endpointParamName: string;
tokenParamName: string;
teamName: string;
}
interface PnpmWorkspaceOptions {
fileName?: string; // Default: 'pnpm-workspace.yaml'
minimumReleaseAge?: number; // Minutes, default: ONE_DAY (1440)
minimumReleaseAgeExclude?: Array<string>; // Default: ['@codedrifters/*']
onlyBuiltDependencies?: Array<string>;
ignoredBuiltDependencies?: Array<string>;
subprojects?: Array<string>;
defaultCatalog?: { [key: string]: string };
namedCatalogs?: { [catalogName: string]: { [dependencyName: string]: string } };
}
Turbo Repo is a build system for monorepos that:
Hashing: All input files (source code, configs) are hashed
Cache Check: If inputs haven't changed, cached outputs are reused
Smart Rebuilds: Only affected packages rebuild when dependencies change
Remote Cache: Build outputs stored in S3 (not Vercel) for team sharing
Turborepo is automatically enabled in MonorepoProject (can be disabled with turbo: false). Configure it using the turboOptions:
const project = new MonorepoProject({
name: 'my-monorepo',
turboOptions: {
remoteCacheOptions: {
profileName: 'my-profile',
oidcRole: 'arn:aws:iam::123456789012:role/TurborepoRole',
endpointParamName: '/TURBOREPO/ENDPOINT',
tokenParamName: '/TURBOREPO/TOKEN',
teamName: 'my-team',
},
},
});
Before using Turbo Repo locally, you need AWS credentials:
# Run the login script (credentials last ~8 hours)
./scripts/aws-profile-turbo-repo.sh
Common Error: If you get a "gRPC error" when running Turbo first thing in the morning, the Lambda function is cold. Just run the command again - it will work the second time.
The MonorepoProject automatically generates and manages pnpm-workspace.yaml. This file:
packages arrayminimumReleaseAge and onlyBuiltDependenciesThe workspace file is auto-generated and lists all sub-projects:
packages:
- 'packages/package-a'
- 'packages/package-b'
- 'apps/frontend'
Sub-projects are automatically discovered from project.subprojects and any additional paths specified in pnpmWorkspaceOptions.subprojects.
Configure the workspace through pnpmOptions.pnpmWorkspaceOptions:
const project = new MonorepoProject({
name: 'my-monorepo',
pnpmOptions: {
pnpmWorkspaceOptions: {
minimumReleaseAge: MIMIMUM_RELEASE_AGE.ONE_DAY,
minimumReleaseAgeExclude: ['@codedrifters/*'],
onlyBuiltDependencies: ['@swc/core', 'esbuild'],
defaultCatalog: {
'react': '^18.0.0',
},
},
},
});
Projen manages many files automatically. Never edit these files by hand:
.prettierrc, tsconfig.json, etc.).github/workflows/*).projen/files.jsonpackage.json is special - Projen reads, modifies, and writes it back, preserving some manual changes. However, you should still manage dependencies through Projen config, not by editing package.json directly.
WRONG:
npm install some-package
CORRECT: Add dependencies in your .projenrc.ts configuration and run npx projen.
const project = new TypeScriptProject({
// ... other options
deps: ['some-package'],
});
Run Turbo build locally (from monorepo root) to cache everything:
# This caches outputs that GitHub Actions can reuse
pnpm build:all
Then commit and push. Your GitHub build will be much faster.
projenrc/ (e.g., projenrc/new-package.ts)TypeScriptProject with the parent as a parameternpx projen to regenerate configsExample:
// projenrc/new-package.ts
import { MonorepoProject, TypeScriptProject } from '@codedrifters/configulator';
export const configureNewPackage = (parent: MonorepoProject) => {
return new TypeScriptProject({
name: 'new-package',
packageName: '@myorg/new-package',
outdir: 'packages/new-package',
parent,
deps: ['dependency'],
});
};
To receive updates from Configulator in your project:
npx projenConstructor:
new MonorepoProject(options: MonorepoProjectOptions)
Key Properties:
pnpmVersion: string - The PNPM version used by the monorepoKey Methods:
TypeScriptAppProjectConstructor:
new TypeScriptProject(options: TypeScriptProjectOptions)
Key Methods:
addDeps(...deps: string[]) - Add runtime dependenciesaddDevDeps(...deps: string[]) - Add dev dependenciesaddPeerDeps(...deps: string[]) - Add peer dependenciesTypeScriptProjectStatic Method:
PnpmWorkspace.of(project: Project): PnpmWorkspace | undefined
Returns the PnpmWorkspace component from a project if it exists.
Static Method:
TurboRepo.of(project: Project): TurboRepo | undefined
Returns the TurboRepo component from a project if it exists.
"Cannot find module" errors
Run npx projen to regenerate configuration files.
Turbo Repo cache errors
Build workflow failures
Check that you haven't manually edited generated files. If you have, run npx projen to restore them.
Dependency conflicts
Don't mix npm install with Projen. Always add dependencies through configuration.
Sub-project not appearing in workspace
parent set to the MonorepoProjectoutdir is set correctlynpx projen to regenerate the workspace fileRemember: The goal is consistency and automation. Let the tools manage the boilerplate so you can focus on building features.
FAQs
Projen configs frequently used in CodeDrifter projects.
We found that @codedrifters/configulator demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers 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
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.