CircleCI: cd Into Directory

Here’s how to cd into a directory in CircleCI.

If No working_directory

If there’s no working_directory value in the step, you’ll start in the directory /home/circleci/project:

      - checkout
      - run:
          name: Running e2e tests
          command: |
            pwd
            # prints /home/circleci/project
            ls
            # prints the files in your repo: babel.config.json  LICENSE  php etc…Code language: YAML (yaml)

That directory will also have your repo, assuming your checkout command didn’t have a path value.

If the checkout command had a path value, it’ll be checked out there:

      - checkout:
          path: /tmp/e2e
      - run:
          name: Running e2e tests
          command: |
            pwd
            # prints /home/circleci/project
            cd /tmp/e2e
            ls
            # prints babel.config.json  LICENSE  php etc…Code language: YAML (yaml)

If working_directory

If there’s a working_directory value in the step, you’ll start in that directory, relative to /home/circleci/project

      - run:
          name: Running e2e tests
          working_directory: e2e
          command: |
            pwd
            # prints /home/circleci/project/e2e
Code language: YAML (yaml)

Because that doesn’t start with /, it is relative to ~/project/

So it’ll be ~/project/e2e

If it started with / or ~/ it’d be an absolute path:

      - run:
          name: Running e2e tests
          working_directory: /tmp/e2e
          command: |
            pwd
            # prints /tmp/e2e
Code language: YAML (yaml)

Don’t Do This

      - run: cd foo
      - run: npm testCode language: YAML (yaml)

Each run command goes back to /home/circleci/project or its working_directory:

  - run: cd foo && pwd # prints /home/circleci/project/foo
  - run: pwd # prints /home/circleci/projectCode language: YAML (yaml)

Checking Out Repo To Another Directory

version: 2.1

references:
  REPO_PATH: &REPO_PATH
    /tmp/e2e

jobs:
  js-build:
    docker:
      - image: cimg/node:14.18
    steps:
      - checkout:
          path: *REPO_PATH
      - run:
          working_directory: *REPO_PATH
          command: |
            pwd
            # prints /tmp/e2e
      - run:
          working_directory: *REPO_PATH
          command: npm test
Code language: YAML (yaml)

But you’ll only need REPO_PATH if you have multiple steps.

Usually, you can simply pass a string literal to working_directory.

And this should only be needed if you’re checking out multiple repos.

Like a repo for e2e tests, and a repo for your project.

Instant Feedback

Here’s how to cd into directory and see where you are, right away.

In VS Code, install the extension Local CI.

1. Click its icon on the left

2. Click ‘Select Repo’

3. Select the repo

4. You’ll see the jobs:

CircleCI cd into directory

5. Click the job you want bash access to

6. Click ‘debugging’

7. Run bash commands, like cd into directory:

$ whoami
circleci
$ pwd
/home/circleci
$ ls
project
$ cd project
$ ls
babel.config.json  package-lock.json
composer.json      php
composer.lock      README.md
LICENSE            src
node_modules       tests
package.jsonCode language: Bash (bash)

Most of the time, you shouldn’t need to cd in CircleCI commands.

working_directory should get you into the right directory.

But if you need to change directories, do it in the same command.

Local CI can help get instant feedback on where you are.

But if you’d prefer to use the CircleCI CLI on your own…

Here’s how to do that.

CircleCI cd Into Directory

Did this help?

If this was terrible, leave a comment below.

Or email me at ryan @ this domain.

CI/CD can be hard…

But instant feedback makes it easier.

Be the first to get CI/CD tips like this

You'll get these tips before they're on the blog page. See most up-to-date ways to use CircleCI®. No spam.

TypeScript CircleCI Configs: Only 3 Lines

You can now create TypeScript CircleCI configs with 3 lines of code:

import { createConfig, JobNames } from "@getlocalci/create-config";

createConfig(JobNames.JsTest, JobNames.JsLint, JobNames.Vsix);
Code language: TypeScript (typescript)

No more copy-pasting .yml between repos.

Most of the time, you’ll only need to edit one repo.

For example, let’s say you want to bump the resource class to "large" on all of your repos:

CircleCI customize your resource class

You could make the edit in 1 repo, and it’ll apply to all of them.

Same if you want to update a deprecated image.

How?

With the new TypeScript Config SDK, thanks to Kyle Tryon and Jaryt Bustard.

You can keep your common config in a single repo, like @getlocalci/create-config.

Like how Kyle Tryon shows in his great post.

And you can import that config into other repos with only:

import { createConfig, JobNames } from "@getlocalci/create-config";

createConfig(JobNames.JsTest, JobNames.JsLint, JobNames.Vsix);
Code language: TypeScript (typescript)

You don’t even have to publish the config repo as an npm package, or compile the TypeScript.

You can import it with:

$ npm install https://github.com/getlocalci/create-config/ -DCode language: Bash (bash)

…and npm will install it from the default branch on GithHub.

There are 2 parts:

  1. Your common config repo
  2. Consuming repos that import that repo and call createConfig(). You might have dozens.

Here’s a real example of both:

1. Common Config Repo

This will have the jobs you usually use.

And it’ll export createConfig() so consuming repos can run these jobs.

This is just an example.

Your repo might have completely different jobs.

Create a new repo, and add an index.ts in the root.

We’ll start with some imports, and an object JobNames:

index.ts

import CircleCI from "@circleci/circleci-config-sdk";
import fs from "fs";
import glob from "glob";
import path from "path";

/**
 * Job names you can pass to createConfig().
 * These already have jobs created for them.
 */
export const JobNames = {
  E2eTest: "e2e-test",
  JsLint: "js-lint",
  JsTest: "js-test",
  PhpLint: "php-lint",
  PhpTest: "php-test",
  Vsix: "vsix",
  Zip: "zip",
};
Code language: TypeScript (typescript)

Consuming repos will import JobNames, so they know which jobs they can add.

On line 25, we’ll add preCreatedJobs that any config can use:

import CircleCI from "@circleci/circleci-config-sdk";
import fs from "fs";
import glob from "glob";
import path from "path";

/**
 * Job names you can pass to createConfig().
 * These already have jobs created for them.
 */
export const JobNames = {
  E2eTest: "e2e-test",
  JsLint: "js-lint",
  JsTest: "js-test",
  PhpLint: "php-lint",
  PhpTest: "php-test",
  Vsix: "vsix",
  Zip: "zip",
};

const nodeExecutor = new CircleCI.executors.DockerExecutor(
  "cimg/node:lts",
  "large"
);

const preCreatedJobs = [
  new CircleCI.Job(JobNames.JsLint, nodeExecutor, [
    new CircleCI.commands.Checkout(),
    new CircleCI.commands.Run({
      command: "npm ci && npm run lint",
    }),
  ]),
  new CircleCI.Job(JobNames.JsTest, nodeExecutor, [
    new CircleCI.commands.Checkout(),
    new CircleCI.commands.Run({ command: "npm ci && npm test" }),
  ]),
Code language: TypeScript (typescript)

Each value in preCreatedJobs will be a CircleCI.Job.

The constructor of CircleCI.Job accepts:

  1. Job name
  2. Executor
  3. Steps

You’ll get type-hinting for each of these:

TypeScript CircleCI Config SDK command type hinting

On line 103, we’ll add functions to create the config:

import CircleCI from "@circleci/circleci-config-sdk";
import fs from "fs";
import glob from "glob";
import path from "path";

/**
 * Job names you can pass to createConfig().
 * These already have jobs created for them.
 */
export const JobNames = {
  E2eTest: "e2e-test",
  JsLint: "js-lint",
  JsTest: "js-test",
  PhpLint: "php-lint",
  PhpTest: "php-test",
  Vsix: "vsix",
  Zip: "zip",
};

const nodeExecutor = new CircleCI.executors.DockerExecutor(
  "cimg/node:lts",
  "large"
);

const preCreatedJobs = [
  new CircleCI.Job(JobNames.JsLint, nodeExecutor, [
    new CircleCI.commands.Checkout(),
    new CircleCI.commands.Run({
      command: "npm ci && npm run lint",
    }),
  ]),
  new CircleCI.Job(JobNames.JsTest, nodeExecutor, [
    new CircleCI.commands.Checkout(),
    new CircleCI.commands.Run({ command: "npm ci && npm test" }),
  ]),
  new CircleCI.Job(
    JobNames.PhpLint,
    new CircleCI.executors.DockerExecutor("cimg/php:8.0", "large"),
    [
      new CircleCI.commands.Checkout(),
      new CircleCI.commands.Run({
        command: "composer i && composer lint",
      }),
    ]
  ),
  new CircleCI.Job(
    JobNames.PhpTest,
    new CircleCI.executors.DockerExecutor("cimg/php:8.1", "large"),
    [
      new CircleCI.commands.Checkout(),
      new CircleCI.commands.Run({ command: "composer i && composer test" }),
    ]
  ),
  new CircleCI.Job(
    JobNames.E2eTest,
    new CircleCI.executors.MachineExecutor("large", "ubuntu-2004:202111-02"),
    [
      new CircleCI.commands.Checkout(),
      new CircleCI.commands.Run({ command: "npm ci" }),
      new CircleCI.commands.Run({
        name: "Running e2e tests",
        command: "npm run wp-env start && npm run test:e2e",
      }),
      new CircleCI.commands.StoreArtifacts({ path: "artifacts" }),
    ]
  ),
  new CircleCI.Job(
    JobNames.Vsix,
    new CircleCI.executors.DockerExecutor("cimg/node:16.8.0-browsers", "large"),
    [
      new CircleCI.commands.Checkout(),
      new CircleCI.commands.Run({ command: "npm ci && npm run vsix" }),
      new CircleCI.commands.Run({
        command: `mkdir /tmp/artifacts
          mv ${
            JSON.parse(
              fs.existsSync("../../package.json")
                ? fs.readFileSync("../../package.json")?.toString()
                : "{}"
            ).name
          }*.vsix /tmp/artifacts`,
      }),
      new CircleCI.commands.StoreArtifacts({ path: "/tmp/artifacts" }),
    ]
  ),
  new CircleCI.Job(
    JobNames.Zip,
    new CircleCI.executors.DockerExecutor("cimg/php:8.0"),
    [
      new CircleCI.commands.Checkout(),
      new CircleCI.commands.Run({ command: "composer zip" }),
      new CircleCI.commands.StoreArtifacts({
        path: `${path.basename(
          glob.sync("../../*.php")?.[0] ?? "",
          ".php"
        )}.zip`,
      }),
    ]
  ),
];

/** Not needed for the public API, simply use createConfig(). */
export function getJobs(...jobs: (string | CircleCI.Job)[]): CircleCI.Job[] {
  return jobs.map((job) => {
    return typeof job === "string"
      ? preCreatedJobs.find((preCreatedJob) => {
          return job === preCreatedJob.name;
        })
      : job;
  });
}

/** Creates and writes the config, given the passed jobs. */
export function createConfig(...jobs: (string | CircleCI.Job)[]) {
  const config = new CircleCI.Config();
  const workflow = new CircleCI.Workflow("test-lint");
  config.addWorkflow(workflow);

  getJobs(...jobs).forEach((job) => {
    if (job) {
      config.addJob(job);
      workflow.addJob(job);
    }
  });

  fs.writeFile("./dynamicConfig.yml", config.stringify(), () => {});
}
Code language: TypeScript (typescript)

createConfig() on line 114 is the main function other repos will call.

That even writes the config file on line 126.

Next, create a package.json.

Most of the lines don’t matter, but you’ll need:

  "main": "index.ts",
  "type": "module",Code language: JSON / JSON with Comments (json)

…and:

  "dependencies": {
    "@circleci/circleci-config-sdk": "^0.11.0",
  }Code language: JSON / JSON with Comments (json)

You’ll also need:

tsconfig.json

{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "Node",
    "noEmit": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
  }
}
Code language: JSON / JSON with Comments (json)

"allowSyntheticDefaultImports" is important, it’ll let you import CircleCI with:

import CircleCI from "@circleci/circleci-config-sdk";Code language: TypeScript (typescript)

2. Consuming Repo

This will import the common config above, and create a config from it.

You might have dozens of consuming repos.

.circleci/dynamic/index.ts

import { createConfig, JobNames } from "@getlocalci/create-config";

createConfig(JobNames.PhpLint, JobNames.PhpTest, JobNames.Zip);
Code language: TypeScript (typescript)

This will run the index.ts file via npm start:

.circleci/config.yml

version: 2.1
orbs:
  continuation: circleci/continuation@0.3
  node: circleci/node@5.0
setup: true
jobs:
  generate-config:
    executor: node/default
    steps:
      - checkout
      - node/install-packages:
          app-dir: .circleci/dynamic
      - run:
          name: Generate config
          command: npm start
          working_directory: .circleci/dynamic
      - continuation/continue:
          configuration_path: .circleci/dynamic/dynamicConfig.yml
workflows:
  dynamic-workflow:
    jobs:
      - generate-config
Code language: YAML (yaml)

.circleci/dynamic/package.json

{
  "name": "adapter-gravity-add-on-dynamic-config",
  "version": "0.1.0",
  "description": "Dynamic config for AGA",
  "author": "Ryan Kienstra",
  "main": "index.ts",
  "type": "module",
  "scripts": {
    "lint": "prettier --check index.ts",
    "lint:fix": "prettier --write index.ts",
    "start": "ts-node --esm --skipIgnore index.ts"
  },
  "license": "GPL-2.0-or-later",
  "devDependencies": {
    "@getlocalci/create-config": "github:getlocalci/create-config",
    "@types/node": "^18.7.20",
    "prettier": "^2.7.1",
    "ts-node": "^10.9.1",
    "tslib": "^2.4.0",
    "typescript": "^4.8.3"
  }
}
Code language: JSON / JSON with Comments (json)

Lines 7 and 11 above are important.

The --skipIgnore flag on line 11 interprets @getlocalci/create-config as TypeScript, so we don’t have to compile it earlier.

.circleci/dynamic/tysconfig.json

{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "noEmit": true
  }
}
Code language: JSON / JSON with Comments (json)

Finally, to enable the Config SDK, go to your repo’s CircleCI Advanced Settings:

https://app.circleci.com/settings/project/github/<GitHub org>/<GitHub repo>/advanced

For this example, the URL is:

https://app.circleci.com/settings/project/github/kienstra/adapter-gravity-add-on/advanced

Then, select ‘Enable dynamic config using setup workflows’:

TypeScript CircleCI Config enable setup workflows

Here’s the result:

TypeScript CircleCI configs jobs

Live TypeScript CircleCI Configs

These are all importing the common config:

Updating Consuming Repos

Let’s say in the common repo, you bump a container from "cimg/node:16.8.0" to "cimg/node:18.10.0".

And you want consuming repos to get that change.

In the common repo, merge the change into the default branch.

You don’t even have to compile the TypeScript, or do npm publish.

The consuming repo will interpret it as TypeScript.

In each consuming repo’s .circleci/dynamic, do:

$ npm update @getlocalci/create-configCode language: Bash (bash)

…but substitute the name of your common repo.

This Changes CircleCI

The TypeScript Config SDK is a huge feature.

Though of course, you can keep using the traditional config.yml if you prefer.

You can now keep your config in one repo, and mainly edit that repo.

This is like orbs, which can have commands and jobs.

But orbs can’t create the entire config for you.

The Config SDK can.

Here, you can reuse a config.

But you don’t get the typical headaches that come with reuse.

Like tagging and publishing an npm package.

If you like TypeScript…

This is now the way to write CircleCI configs.

Be the first to get CI/CD tips like this

You'll get these tips before they're on the blog page. See most up-to-date ways to use CircleCI®. No spam.

CircleCI Config SDK Tutorial

You can now write your CircleCI® config files with TypeScript.

  • Less code
  • Reusable configs
  • Type hinting:
CircleCI Config SDK type hinting

You’ll see a real example in this CircleCI config SDK tutorial.

Config File

We’ll start with a .circleci/config.yml file.

This will run the TypeScript file we create.

.circleci/config.yml

version: 2.1
orbs:
  continuation: circleci/continuation@0.3
  node: circleci/node@5.0
setup: true
jobs:
  generate-config:
    executor: node/default
    steps:
      - checkout
      - node/install-packages:
          app-dir: .circleci/dynamic
      - run:
          name: Generate config
          command: npm start
          working_directory: .circleci/dynamic
      - continuation/continue:
          configuration_path: .circleci/dynamic/dynamicConfig.yml
workflows:
  dynamic-workflow:
    jobs:
      - generate-config
Code language: YAML (yaml)

This is mainly copied from Kyle Tryon’s great post on the Config SDK.

It’ll run the index.ts file, and read the .yml file that creates.

Feel free to copy this file, you shouldn’t need any custom code.

Next, create a directory .circleci/dynamic/

This is where your code for the Config SDK will go.

We’ll start with a package.json file for the dependencies:

.circleci/dynamic/package.json

{
  "name": "circleci-tutorial-config-sdk-dynamic-config",
  "version": "0.1.0",
  "description": "Config SDK example",
  "author": "Ryan Kienstra",
  "main": "index.ts",
  "type": "module",
  "scripts": {
    "lint": "prettier --check index.ts",
    "lint:fix": "prettier --write index.ts",
    "start": "ts-node --esm index.ts"
  },
  "license": "GPL-2.0-or-later",
  "devDependencies": {
    "@circleci/circleci-config-sdk": "^0.10.1",
    "@types/node": "^18.7.20",
    "prettier": "^2.7.1",
    "ts-node": "^10.9.1",
    "tslib": "^2.4.0",
    "typescript": "^4.8.3"
  }
}
Code language: JSON / JSON with Comments (json)

The ts-node dependency lets you write your index file in TypeScript, but you can also run it with node to keep package.json simpler.

We’ll also add a tsconfig.json file:

.circleci/dynamic/tsconfig.json

{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "noEmit": true
  }
}
Code language: JSON / JSON with Comments (json)

You won’t need that if you prefer JavaScript.

Main SDK File

.circleci/dynamic/index.ts

import * as fs from "fs";
import CircleCI from "@circleci/circleci-config-sdk";

const config = new CircleCI.Config();
const workflow = new CircleCI.Workflow("test-lint");
config.addWorkflow(workflow);
Code language: TypeScript (typescript)

This imports CircleCI from the SDK package.

For each job we create, we’ll add it to the config and workflow.

First Job

This shows the power of the TypeScript SDK:

import * as fs from "fs";
import CircleCI from "@circleci/circleci-config-sdk";

const config = new CircleCI.Config();
const workflow = new CircleCI.Workflow("test-lint");
config.addWorkflow(workflow);

function createPhpTestJobs(...phpVersions: string[]) {
  return phpVersions.map((phpVersion) => {
    return new CircleCI.Job(
      `php-test-${phpVersion.replace(".", "-")}`,
      new CircleCI.executors.DockerExecutor(`cimg/php:${phpVersion}`),
      [
        new CircleCI.commands.Checkout(),
        new CircleCI.commands.Run({ command: "composer i && composer test" }),
      ]
    );
  });
}
Code language: TypeScript (typescript)

On line 8, we define the function createPhpTestJobs(), which will create 4 jobs.

Before, this used a matrix in .yml, and passed parameters.

But it’s easier to see what this does.

It simply returns a new CircleCI.Job for each php version passed.

No need to define parameters for the job, or read the docs about how to create a matrix.

Job Array

On line 21, this creates an array for the jobs.

At the end of this array, later in this post, we’ll call .forEach() to add each job to the config and workflow.

import * as fs from "fs";
import CircleCI from "@circleci/circleci-config-sdk";

const config = new CircleCI.Config();
const workflow = new CircleCI.Workflow("test-lint");
config.addWorkflow(workflow);

function createPhpTestJobs(...phpVersions: string[]) {
  return phpVersions.map((phpVersion) => {
    return new CircleCI.Job(
      `php-test-${phpVersion.replace(".", "-")}`,
      new CircleCI.executors.DockerExecutor(`cimg/php:${phpVersion}`),
      [
        new CircleCI.commands.Checkout(),
        new CircleCI.commands.Run({ command: "composer i && composer test" }),
      ]
    );
  });
}

[
  ...createPhpTestJobs("7.3", "7.4", "8.0", "8.1"),
  new CircleCI.Job(
    "php-lint",
    new CircleCI.executors.DockerExecutor("cimg/php:8.1"),
    [
      new CircleCI.commands.Checkout(),
      new CircleCI.commands.Run({ command: "composer i && composer lint" }),
    ]
  ),
Code language: TypeScript (typescript)

On line 22, we spread the php-test jobs from the function above into the jobs array.

Then, on line 23, we add a new job.

Like before, the CircleCI.Job constructor accepts the job name, the executor, and the steps.

The first command in each is Checkout().

On line 28, the constructor for CircleCI.commands.Run doesn’t need a name value.

Last 2 Jobs

On line 31, this adds more jobs:

import * as fs from "fs";
import CircleCI from "@circleci/circleci-config-sdk";

const config = new CircleCI.Config();
const workflow = new CircleCI.Workflow("test-lint");
config.addWorkflow(workflow);

function createPhpTestJobs(...phpVersions: string[]) {
  return phpVersions.map((phpVersion) => {
    return new CircleCI.Job(
      `php-test-${phpVersion.replace(".", "-")}`,
      new CircleCI.executors.DockerExecutor(`cimg/php:${phpVersion}`),
      [
        new CircleCI.commands.Checkout(),
        new CircleCI.commands.Run({ command: "composer i && composer test" }),
      ]
    );
  });
}

[
  ...createPhpTestJobs("7.3", "7.4", "8.0", "8.1"),
  new CircleCI.Job(
    "php-lint",
    new CircleCI.executors.DockerExecutor("cimg/php:8.1"),
    [
      new CircleCI.commands.Checkout(),
      new CircleCI.commands.Run({ command: "composer i && composer lint" }),
    ]
  ),
  new CircleCI.Job(
    "js-build",
    new CircleCI.executors.DockerExecutor("cimg/node:14.18"),
    [
      new CircleCI.commands.Checkout(),
      new CircleCI.commands.Run({ command: "npm ci" }),
      new CircleCI.commands.Run({
        name: "Running JS linting and unit test",
        command: "npm run lint:js && npm run test:js",
      }),
    ]
  ),
  new CircleCI.Job(
    "e2e-test",
    new CircleCI.executors.MachineExecutor("large", "ubuntu-2004:202111-02"),
    [
      new CircleCI.commands.Checkout(),
      new CircleCI.commands.Run({ command: "npm ci" }),
      new CircleCI.commands.Run({
        name: "Running e2e tests",
        command: "npm run wp-env start && npm run test:e2e",
      }),
      new CircleCI.commands.StoreArtifacts({ path: "artifacts" }),
    ]
  ),
].forEach((job) => {
  config.addJob(job);
  workflow.addJob(job);
});

fs.writeFile("./dynamicConfig.yml", config.stringify(), () => {});
Code language: TypeScript (typescript)

And on line 53, this stores test errors in artifacts, so we can debug.

This calls forEach() on line 56, adding each job to the config and workflow.

Generated .yml File

On line 61, this writes the config to .circleci/dynamic/dynamicConfig.yml.

You don’t have to commit this, the index.ts file generates it on every pipeline run:

.circleci/dynamic/dynamicConfig.yml

# This configuration has been automatically generated by the CircleCI Config SDK.
# For more information, see https://github.com/CircleCI-Public/circleci-config-sdk-ts
# SDK Version: 0.0.0-development

version: 2.1
setup: false
jobs:
  php-test-7-3:
    docker:
      - image: cimg/php:7.3
    resource_class: medium
    steps:
      - checkout
      - run:
          command: composer i && composer test
  php-test-7-4:
    docker:
      - image: cimg/php:7.4
    resource_class: medium
    steps:
      - checkout
      - run:
          command: composer i && composer test
  php-test-8-0:
    docker:
      - image: cimg/php:8.0
    resource_class: medium
    steps:
      - checkout
      - run:
          command: composer i && composer test
  php-test-8-1:
    docker:
      - image: cimg/php:8.1
    resource_class: medium
    steps:
      - checkout
      - run:
          command: composer i && composer test
  php-lint:
    docker:
      - image: cimg/php:8.1
    resource_class: medium
    steps:
      - checkout
      - run:
          command: composer i && composer lint
  js-build:
    docker:
      - image: cimg/node:14.18
    resource_class: medium
    steps:
      - checkout
      - run:
          command: npm ci
      - run:
          name: Running JS linting and unit test
          command: npm run lint:js && npm run test:js
  e2e-test:
    machine:
      image: ubuntu-2004:202111-02
    resource_class: large
    steps:
      - checkout
      - run:
          command: npm ci
      - run:
          name: Running e2e tests
          command: npm run wp-env start && npm run test:e2e
      - store_artifacts:
          path: artifacts
workflows:
  test-lint:
    jobs:
      - php-test-7-3
      - php-test-7-4
      - php-test-8-0
      - php-test-8-1
      - php-lint
      - js-build
      - e2e-test
Code language: YAML (yaml)

This .yml file is 81 lines, where index.ts is 61.

Settings

Finally, go to your repo’s CircleCI Advanced Settings:

https://app.circleci.com/settings/project/github/<GitHub org>/<GitHub repo>/advanced

For this tutorial, the URL is:

https://app.circleci.com/settings/project/github/getlocalci/circleci-tutorial-config-sdk/advanced

Then, select ‘Enable dynamic config using setup workflows’:

This enables the index.ts file above to generate the config.

Here are the jobs it generates:

CircleCI workflow from generated jobs

Type Hinting

This is much better than .yml.

What are the possible commands?

Easy:

CircleCI command type hinting

You don’t have to leave your IDE to look at docs.

Reusable Configs

The normal way of reusing CircleCI configs is writing a custom orb.

But that can be overkill if you have a simple setup you want to share between projects.

So you could write a common config with the TypeScript SDK, and publish it as a package.

And you could import that into any project.

CircleCI Config SDK Tutorial

Here’s the GitHub repo with this tutorial’s code.

This TypeScript config is easier to write and reusable.

And you won’t have to keep checking the documentation.

You can create configs entirely in your IDE.

Be the first to get CI/CD tips like this

You'll get these tips before they're on the blog page. See most up-to-date ways to use CircleCI®. No spam.

Caching Dependencies in CircleCI

It’s usually really easy to cache dependencies in CircleCI.

Most of the time…

You don’t even need to set cache keys.

Or know how its caching works.

Below are examples of caching Node, Composer, and Gradle dependencies.

CircleCI takes care of it under the hood when you use orbs for caching.

Orbs For Caching Dependencies

There’s usually a certified CircleCI orb that will handle caching for you.

An orb is packaged YAML for a CircleCI config.

Like an npm package, but for CircleCI.

So caching dependencies in CircleCI usually means finding the right orb for the environment:

Node Example

Here’s how to do caching with the Node orb:

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 74a030f..a6e0f84 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,27 +1,31 @@
 version: 2.1
 
+orbs:
+  node: circleci/node@5.0
+
 jobs:
   lint-js:
     docker:
       - image: cimg/node:14.19
     steps:
       - checkout
-      - run: npm ci
+      - node/install-packages
       - run: npm run lint:js
   test-js:
     docker:
       - image: cimg/node:14.19
     steps:
       - checkout
-      - run: npm ci
+      - node/install-packages
       - run: npm run test:js
Code language: Diff (diff)

We reference that orb on lines 18 and 26 by starting with node/, then adding the command name of install-packages.

The resulting node/install-packages command will cache npm ci.

So every time it runs, it’ll first check if there’s a cache for the same lockfile.

If there is, it restores that cache, instead of installing it all from scratch.

Here’s a small part of what that node/install-packages command does:

- restore_cache:
    keys:
      - >-
        node-deps-{{ arch
        }}-<<parameters.cache-version>>-<<#parameters.include-branch-in-cache-key>>{{
        .Branch
        }}-<</parameters.include-branch-in-cache-key>><<^parameters.cache-only-lockfile>>{{
        checksum "/tmp/node-project-package.json"
        }}-<</parameters.cache-only-lockfile>>{{ checksum
        "/tmp/node-project-lockfile" }}
      - >-
        node-deps-{{ arch
        }}-<<parameters.cache-version>>-<<#parameters.include-branch-in-cache-key>>{{
        .Branch }}-<</parameters.include-branch-in-cache-key>>{{
        checksum "/tmp/node-project-package.json" }}
      - >-
        node-deps-{{ arch
        }}-<<parameters.cache-version>>-<<#parameters.include-branch-in-cache-key>>{{
        .Branch }}-<</parameters.include-branch-in-cache-key>>Code language: YAML (yaml)

But you don’t have to know about that.

It takes care of caching dependencies in CircleCI for you.

PHP Example

Again, we’ll use an install-packages command to do caching.

But we’ll prefix it with php.

The full command is php/install-packages, on line 18:

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 20bc520..07bc337 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,6 +2,7 @@ version: 2.1
 
 orbs:
   node: circleci/node@5.0
+  php: circleci/php@1.1
 
 jobs:
   lint-js:
@@ -26,7 +27,7 @@ jobs:
     steps:
       - checkout
       - node/install-packages
-      - run: composer install
+      - php/install-packages
       - run: npm run build
       - run: composer test
Code language: Diff (diff)

Here’s a small part of the source of that command:

- restore_cache:
    keys:
      - >-
        composer-deps-<<parameters.cache-version>>-{{ checksum
        "<<parameters.app-dir>>/<<parameters.cache-key>>" }}Code language: YAML (yaml)

You might be worried about whether you’ll have to flush this cache when CircleCI runs.

Like if tests fail.

I haven’t had to do that, its caching has been reliable.

Gradle Example

This is different than the install-packages commands earlier.

This wraps steps in a caching command: gradle/with_cache

-- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,6 +2,7 @@ version: 2.1
 
 orbs:
   android: circleci/android@2.1
+  gradle: circleci/gradle@3.0
 
 executors:
   android:
@@ -13,9 +14,11 @@ jobs:
     executor: android
     steps:
       - checkout
-      - run:
-          name: Building the APK
-          command: ./gradlew -s assembleDebug
+      - gradle/with_cache:
+          steps:
+            - run:
+                name: Building the APK
+                command: ./gradlew -s assembleDebug
       - store_artifacts:
           path: app/build/outputs/apk/debug/app-debug.apk
Code language: Diff (diff)

Now, this caches that command in CircleCI:

Caching Dependencies in CircleCI in a Gradle job

Caching Dependencies in CircleCI

The install-packages or with_cache commands will be enough in many environments.

CircleCI makes it easy for you to cache.

Without creating your own cache keys, or learning the internals of how it caches.

Another great part about CircleCI is how they make it easy to debug.

Be the first to get CI/CD tips like this

You'll get these tips before they're on the blog page. See most up-to-date ways to use CircleCI®. No spam.

CircleCI CLI

The CircleCI CLI lets you run a job locally.

So if your build ever fails, you don’t have to:

  • Guess what’s wrong
  • Push a commit
  • Wait for it to fail
  • Guess what’s wrong again
  • Push another commit…

For me, this makes CircleCI® the clear leader.

Here’s how to get bash access to the job locally, using the CLI.

So you’ll be able to see what’s wrong.

Real Example

We’re going to debug a failed CI/CD build:

/bin/bash: svn: command not foundCode language: Bash (bash)
CircleCI CLI svn command not found

The solution isn’t clear, at least to me.

We don’t know:

  • What package management system does the container use?
  • What package should we install to run the svn command?

So you’re going to get bash access to debug this.

Start by cloning the example repo, where the failed build was:

$ git clone -b add/wp-org-svn https://github.com/kienstra/adapter-responsive-video
$ cd adapter-responsive-videoCode language: Bash (bash)

Of course, you can use your own repo instead, if there was a failed build there.

Adding Time To Debug

Add this to .circleci/config.yml, right before the step that failed:

diff --git a/.circleci/config.yml b/.circleci/config.yml
index e27995a..1c93a1d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -9,6 +9,7 @@ jobs:
       - image: cimg/base:current
     steps:
       - checkout
+      - run: sleep 1000
       - run: svn co https://plugins.svn.wordpress.org/adapter-responsive-video --depth=empty .
 
 workflows:Code language: Diff (diff)

This will keep the job running, so you have time to debug it with bash access.

Installing the CircleCI CLI (if you haven’t yet)

Mac:

$ brew install circleciCode language: Bash (bash)

Mac and Linux:

$ curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | bashCode language: JavaScript (javascript)

This CircleCI CLI will run the job in your local.

Please also ensure Docker is running on your machine.

Running the CLI

$ circleci local execute --job deployCode language: Bash (bash)

This will run the job on your machine:

CircleCI CLI running in terminal

You might see that pulling some images to run the job.

Getting the Job Image Name

Open a new terminal tab, and run this:

$ docker psCode language: Bash (bash)

That should show the image that’s running the job:

CONTAINER ID   IMAGE               COMMAND                  CREATED          STATUS          PORTS     NAMES
b6e09b7009ab   cimg/base:current   "/bin/sh"                13 seconds ago   Up 10 seconds             naughty_jones
b992e2d2c0a8   circleci/picard     "/opt/circleci/linux…"   16 seconds ago   Up 16 seconds             naughty_colden
5c233ff5c3b9   cimg/base:current   "/bin/sh"                6 minutes ago    Up 5 minutes              elastic_brattain
Code language: Bash (bash)

Look for an image right above circleci/picard.

If you don’t see circleci/picard, you’ll probably have to wait 10-20 seconds while the circleci local command above pulls that image.

In this case, it’s the cimg/base:current image.

Copy the container ID of that image to use it below.

In this example, it’s b6e09b7009ab.

That’s the image where your job is running, and you’re going to get bash access to it.

Bash

Then, run this in your terminal to debug the job.

$ docker exec -it b6e09b7009ab /bin/shCode language: Bash (bash)

The argument b6e09b7009ab is the container ID that you got above, from running docker ps.

You should then have bash access to the container:

CircleCI CLI bash access to the container

$ whoami
circleci
$ pwd
/home/circleci/projectCode language: Bash (bash)

Then, you can debug the container, and find out how to install the svn command:

CircleCI CLI

This faster debugging makes CircleCI the leader, in my opinion.

No more guessing and waiting.

You can fix your CI locally.

Now that you’ve debugged your config…

Here’s a way to make it faster and simpler.

Be the first to get CI/CD tips like this

You'll get these tips before they're on the blog page. See most up-to-date ways to use CircleCI®. No spam.

CircleCI Android Tutorial

In this CircleCI Android tutorial, you’ll see how to:

  • Build an .apk file
  • Lint
  • Run unit tests
  • Run UI tests

We’ll start by forking an existing Android repo that doesn’t have a CircleCI config:

CircleCI Android Tutorial repo
Android app written in Kotlin

Then, we’ll add a .circleci/config.yml file, starting with:

The orbs on line 3 make this much easier.

They handle caching, and even run and entire job for you, in the case of the android orb.

Orbs are prepackaged CircleCI jobs or commands that can abstract away details like caching.

Also, the executors stanza on line 7 defines a Docker image that you can reference in jobs.

Building an .apk

Now, let’s add the first job:

Line 14 references the android executor that we just defined.

The steps array at line 15 begins with checkout, which checks out the repo into the CircleCI environment.

Next, we use the gradle/with_cache command from the gradle orb we defined above.

This will cache the gradle packages between jobs:

CirceCI job restoring a cache of 679 MiB

It caches a huge amount of dependencies for you.

678 mebibytes, about 710 megabytes.

And it will run the command ./gradlew -s assembleDebug to build the .apk file of the app.

The store_artifacts value on line 22 makes the build available in CircleCI:

The path might be different for your repo.

Next, we’ll add a workflows stanza and add this build job to it:

Linting

The next jobs are much simpler, thanks to CircleCI orbs.

We’ll simply use the gradle orb for the gradle/run job:

We just need to pass the executor (image), and the command of klint --stacktrace.

This will run linting for us.

Unit Tests

This will also use the gradle orb:

The executor of android adds the Android SDK to the environment.

Without that, the test command will fail.

Also, we store the test results with the tests_results_path value.

This lets us see them in CircleCI under the ‘Artifacts’ tab:

UI Tests

In these very complex UI tests, the CircleCI Android orb starts an emulator and does caching.

We avoid a lot of configuration by using that orb’s android/run-ui-tests job:

Like the previous jobs, we pass a ./gradlew command.

CircleCI Android Tutorial

Here’s the final config.yml:

And we see all 4 jobs passing:

CircleCI Android workflow passing

Be the first to get CI/CD tips like this

You'll get these tips before they're on the blog page. See most up-to-date ways to use CircleCI®. No spam.

CircleCI Orb Example

With this CircleCI orb example…

You’ll see how to make your configs faster and simpler.

With common snippets for Node.js and PHP.

What are orbs?

Orbs are like NPM packages for CircleCI®.

They’re pre-packaged YAML that can run CircleCI commands and jobs.

CircleCI has many official orbs that you can use.

So you don’t have to worry about security the same way you do with 3rd party NPM packages.

In this example, we’ll use orbs for Node.js and PHP.

For comparison, here’s the example .circleci/config.yml file without orbs:

We’ll edit it to use orbs, and it’ll go from 70 lines to 34.

It’ll also be faster, as it’ll cache the packages.

Node Orb

We’ll start with the Node orb.

You’ll see a ‘Certified’ badge, meaning it’s “Written and tested by the CircleCI team”:

CircleCI orb example certified orb

In .circleci/config.yml, we’ll add an orbs stanza and add the Node orb.

Orbs should end with a version number, which is 5.0.0 here.

Then, we’ll use node/install-packages instead of npm ci:

--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -6,7 +6,7 @@ jobs:
       - image: cimg/node:14.18
     steps:
       - checkout
-      - run: npm ci
+      - node/install-packages
       - run:
           name: Running JS linting and unit test
           command: |
@@ -20,7 +20,7 @@ jobs:
       - image: cimg/node:<< parameters.node-version >>
     steps:
       - checkout
-      - run: npm ci
+      - node/install-packages
       - run:
           name: Running JS linting and unit test
           command: |Code language: Diff (diff)

In node/install-packages, node is the name of the orb.

And install-packages is the command name.

Simply replacing npm ci seems trivial, but it will cache the NPM packages:

So it can save minutes each build if there are a lot of NPM dependencies.

Next, we’ll replace the entire js-lint job with a prepackaged job in the Node orb:

--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,18 +3,6 @@ version: 2.1
 orbs:
   node: circleci/node@5.0.0
 
jobs:
-  js-lint:
-    docker:
-      - image: cimg/node:14.18
-    steps:
-      - checkout
-      - node/install-packages
-      - run:
-          name: Running JS linting and unit test
-          command: |
-            npm run lint:js
-
   js-test:
     parameters:
       node-version:
@@ -58,7 +46,8 @@ jobs:
 workflows:
   test-lint:
     jobs:
-      - js-lint
+      - node/run:
+          npm-run: lint:js
       - js-test:
           requires:
             - js-lintCode language: Diff (diff)

This node/run job does npm ci and runs whatever NPM command you pass as npm-run.

We passed an npm-run parameter of lint:js, meaning it will do:

npm run lint:jsCode language: Bash (bash)

It’s essentially the same as our custom lint:js job, but 9 lines shorter.

It also caches NPM dependencies.

Now, we’ll replace another job with an orb job:

--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,21 +3,7 @@ version: 2.1
 orbs:
   node: circleci/node@5.0.0
 
-  js-test:
-    parameters:
-      node-version:
-        type: string
-    docker:
-      - image: cimg/node:<< parameters.node-version >>
-    steps:
-      - checkout
-      - node/install-packages
-      - run:
-          name: Running JS unit test
-          command: |
-            npm run test:js
-
jobs:
   php-lint:
     docker:
       - image: cimg/php:8.1
@@ -48,12 +34,13 @@ workflows:
     jobs:
       - node/run:
           npm-run: lint:js
-      - js-test:
+      - node/test:
           requires:
             - node/run
           matrix:
             parameters:
-              node-version: [ '14.18', '15.0', '16.14', '17.4' ]
+              version: [ '14.18', '15.0', '16.14', '17.4' ]
+          run-command: test:js
       - php-lint
       - php-test:
           requires:Code language: Diff (diff)

We passed node/test in place of the previous job name.

And we deleted the js-test list from the jobs list, we don’t need a definition of the job there.

Then, we passed a run-command parameter of test:js.

If your test command in package.json is simply test, you won’t need that parameter.

PHP Orb

We can also simplify the PHP jobs with the CircleCI PHP orb.

First, we’ll add the orb to this CircleCI orb example:

--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,6 +2,7 @@ version: 2.1
 
 orbs:
   node: circleci/node@5.0.0
+  php: circleci/php@1.1.0
 Code language: Diff (diff)

Now, we’ll use that orb in place of composer i:

--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -12,7 +13,7 @@ jobs:
       - run:
           name: Linting PHP
           command: |
-            composer i
+            php/install-packages
             composer lintCode language: Diff (diff)

That will do composer install, but it will also cache the packages.

Next, we can replace another full job with an orb:

--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -9,25 +10,8 @@ jobs:
-
-  php-test:
-    parameters:
-      php-version:
-        type: string
-    docker:
-      - image: cimg/php:<< parameters.php-version >>
-    steps:
-      - checkout
-      - run:
-          name: Testing PHP
-          command: |
-            composer i
-            composer test
 
 workflows:
   test-lint:
@@ -42,9 +26,9 @@ workflows:
               version: [ '14.18', '15.0', '16.14', '17.4' ]
           run-command: test:js
       - php-lint
-      - php-test:
+      - php/test:
           requires:
             - php-lint
           matrix:
             parameters:
-              php-version: [ '7.3', '7.4', '8.0', '8.1' ]
+              version: [ '7.3', '7.4', '8.0', '8.1' ]Code language: Diff (diff)

Like before, it will do composer test in 4 versions of PHP.

Those PHP versions are in the parameter version.

CircleCI Orb Example

With these changes, we’ve decreased the file from 70 to 34 lines:

Now that you’ve seen a way to make the config faster and more concise…

Here’s another way to make your builds faster.

Be the first to get CI/CD tips like this

You'll get these tips before they're on the blog page. See most up-to-date ways to use CircleCI®. No spam.

CircleCI Tutorial For Beginners

In this CIrcleCI tutorial for beginners, you’ll see how to set up linting and testing in 2 languages.

We’ll also set up e2e tests.

Here’s the repo we’ll use.

Jobs To Add

  • Lint and test JavaScript
  • Lint and test PHP
  • End to end test

Lint And Test JavaScript

Start by adding an empty.circleci/config.yml file to your repo.

We’ll add an orb for Node to the top of the config:

Orbs are like helper functions for CircleCI.

Next, we’ll name the job js-build, and run some scripts like lint:js and test:js:

Notice on line 14 how it uses node/install-packages.

That’s from the orb we defined above.

It does npm ci and caches Node dependencies.

There’s also an image value:

That’s a pre-built CircleCI image that will spin up quickly, and has a minimum of layers.

Lint And Test PHP

Now, we’ll add jobs to lint and test PHP.

Notice that this also has a docker image value, but for a PHP image:

All of the jobs we’ll add start with a checkout step.

That makes the code available in the container.

Next, we’ll run the PHPUnit tests on 4 versions of PHP.

This could mean a lot of duplicate YAML.

But you’ll see how to do this with very little extra code.

We’ll start by adding a php-test job:

This accepts a parameter of php-version-number.

For example, when the PHP version is 8.1, the docker image will be cimg/php:8.1

Now, let’s add the parameters.

We’ll skip to the bottom of the file and add a workflows value:

Notice on line 70 how there are parameters of the PHP versions.

End To End Test

Now, we’ll add one more job, an e2e test:

That has an ubuntu image, as that image can run Docker from inside the image.

The final step is store_artifacts.

If the Puppeteer test fails, it’ll add debugging .html and .jpg files to artifacts/

Those show the final state when the tests failed.

That store_artifacts step will make the artifacts available in the CircleCI UI:

CircleCI tutorial for beginners

Then, we’ll add the e2e-test job on line 71, and the file is complete:

You can now push this completed file to your repo.

CircleCI UI

Next, we’ll set up the CircleCI UI and you’ll be done.

If you don’t have a CircleCI account, you can sign up for the free plan.

The free plan is more than enough for this tutorial, and even several more projects.

Then, go to app.circleci.com.

Click ‘Projects’, select the repo, click ‘Fastest: Use the .circleci/config.yml in my repo’, and click ‘Set Up Project’:

Setting up the CircleCI project

CircleCI should now run for your repo.

CircleCI Tutorial For Beginners

In this tutorial, you saw a typical setup for JavaScript, PHP, and e2e tests.

Now that you’re up and running, here’s a way to speed up your builds.

Be the first to get CI/CD tips like this

You'll get these tips before they're on the blog page. See most up-to-date ways to use CircleCI®. No spam.

How do I debug my CI/CD?

Here’s how I debug my CI/CD.

This is a real example of debugging CircleCI jobs on a GitHub repo.

We’ll debug it locally…

Without pushing commits to the repo and waiting.

First, we notice that CI/CD is failing:

To debug my CI/CD job

When we click Details, the errors mention node-gyp:

npm ERR!   g++ '-DNODE_GYP_MODULE_NAME=libsass' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_GLIBCXX_USE_CXX11_ABI=1'
Debug my CI/CD with node-gyp

From experience, node-gyp has failed when changing the Node version.

Looking at package-lock.json, we’d probably expect the Node version to be 14 or lower.

That’s because the lockfileVersion is 1, which usually infers Node 14 or lower:

So let’s see if the Node version in CI is 14 or lower.

We’ll open VS Code and install the extension Local CI:

Installing Local CI in VS Code

It’ll start a free trial automatically, without entering a credit card.

Then, we’ll run the CI job on our local machine.

And enter node --version to see what the version is.

Using Local CI in VS Code

We can run the job without pushing a commit or clicking anything in the repo.

It turns out that the Node version is 16, where it should probably be 14, at least with the current dependencies.

So let’s see if changing the Node version to 14 makes this job pass.

We’ll test this without pushing any code to the repo, or triggering another failed build there.

First, we’ll add an orb for Node:

Orbs are like utility functions for CircleCI, though they’re written in .yml.

And add a node/install step to set Node to 14.18.1:

Then, we’ll commit that change in git.

And we’ll rerun this job locally, no need to push the commit yet:

Local CI job failed with an error

It failed with an error:

====>> Checkout code
Error: 
Directory (/home/circleci/project) you are trying to checkout to is not empty and not a git repository

Step failed

Maybe there was a problem with the order of the checkout step.

So let’s see if moving the checkout step before the node/install step fixes it:

We’ll rerun the job locally:

CI/CD job passes locally

It passed!

Now, let’s make sure the next job doesn’t have an obvious problem.

We’ll run the next job, py39:

Python job in CI/CD

The first few minutes of the job went well.

So let’s push to the repo, as it looks like CI should pass.

All of the the jobs passed:

All of the CI builds passed

The ideal solution might have been to upgrade the package.json dependencies so they work with Node 16 or 17.

But the idea here is to show how to debug CI/CD locally.

This debugging feedback is one of CircleCI‘s huge advantages.

No endless cycle of guessing, pushing, waiting for it to fail, guessing, pushing…

Now that you’ve seen how I debug my CI/CD, here’s a fast way to speed up your builds.

Be the first to get CI/CD tips like this

You'll get these tips before they're on the blog page. See most up-to-date ways to use CircleCI®. No spam.

CircleCI Dynamic Config

A CircleCI dynamic config skips unnecessary jobs.

If your PR only changes a JavaScript file, why should CI run PHPUnit tests?

If your PR only changes README.md, why should CI run any tests?

And if your PR only changes one directory in a monorepo, why should all the other directories run tests?

With a dynamic config, your builds should pass sooner.

Especially for PRs with a smaller diff.

You’ll see how to do this by only adding 30 lines of .yml to existing .yml.

And you can copy and paste most of it.

We’ll do this with an example repo.

Files In The Repo

Tests That CircleCI Runs

  • PHPUnit: should only run when a .php file is changed in the PR.
  • Jest: should only run when a .js file is changed.
  • End to end: should run whenever a .php, .js, .json, or .yml file is changed.

First File For Dynamic Config

We’ll start with the .circleci/config.yml file:

On line 3, the setup value of true makes this file produce a dynamic config.

The path-filtering orb on line 11 looks at the PR’s diff versus the main branch.

Then, it checks which files in the mapping value are in the diff:

For example, if a .php file is in the diff, the regex on line 14 will match: .*\.php$

Then, it’ll send the parameter pipeline.parameters.run-php to the second config, with the value of true.

That’s because the 3rd value here is true:

That will tell the second config file to run the PHPUnit tests.

Second File For Dynamic Config

Next, add .circleci/continue_config.yml

This accepts parameters like pipeline.parameters.run-php, and runs the actual jobs.

Add parameters for all of the parameters in the mapping value:

These come from the 2nd values in the mapping value.

For example, the mapping:

<meta charset="utf-8">.*\.php$ run-php true Code language: HTML, XML (xml)

…has a parameter of run-php.

Then, add a workflows value:

There were 3 parameters, so there should be 3 workflows.

Name the workflows whatever you’d like, the names don’t matter.

The when conditionals determine if the workflows should run.

They have the parameters that you added to the top of the file.

And they should always begin with << pipeline.parameters.

So if the PR has a changed PHP file, pipeline.parameters.run-php will be true.

And the php-flow workflow will run.

Next, fill in the jobs section:

There’s not much to keep in mind in these jobs, as long as each job is referenced in a workflow.

Project Settings

There’s one more step to get your CircleCI dynamic config to work.

Go to your account at app.circleci.com.

Select the project > Project Settings > Advanced.

And toggle this on:

CircleCI dynamic config toggle enable  dynamic config

Dynamic Config In Action

This repo has PHP and JavaScript.

But there’s also an end to end test job.

That will run when either PHP or JavaScript files changed.

So its mapping regex looks for several files:

<meta charset="utf-8">.*\.(php|js|json|yml)$Code language: HTML, XML (xml)

With your CircleCI dynamic config…

When a PHP file is edited, PHP and e2e jobs run.

Not Jest (JavaScript) tests:

CircleCI dynamic config jobs running without JavaScript tests

And when you only edit the README.md, only 1 job runs, no tests.

It succeeds in 7 seconds:

CircleCI CircleCI dynamic config setup workflow

And if you’re not using CircleCI yet…

Get started with their free plan that’s like a pro plan.

Be the first to get CI/CD tips like this

You'll get these tips before they're on the blog page. See most up-to-date ways to use CircleCI®. No spam.