Git operations

clone

git.clone(repository, callback)

Clones repository to temporary directory. You will get location where repository is cloned to in console output.

Supports:

  • shallow clone
  • multiple repositories
  • custom branch
callback
import { git } from '@codemod.com/workflow'

await git.clone('repository-to-clone', async ({ files, exec, commit, push }) => {
  await exec('pnpm', ['install'])
  // do something inside repository
  await files()
      .jsFam()
      .astGrep('console.log($$$A)')
      .replace('console.error($$$A)')
  await exec('pnpm', ['lint'])
    .exec('pnpm', ['test'])
  await commit('Changed console.log to console.error')
  await push()
})

Parameters:

repository
string | CloneOptions | (string | CloneOptions)[]
required
interface CloneOptions {
  repository: string
  /**
   * @default true
   */
  shallow?: boolean
  /**
   * Overrides branch to clone
   */
  branch?: string
}

Can be a repository or array of repositories which you need to clone. By default shallow cloning is performed with --depth 1 --single-branch. You can specify branch to clone specific (not default branch). If you want to disable shallow clone, you can provide it with extended configuration.

callback
(subcommands: Subcommands) => Promise<void> | void

A callback which will be executed for each repository. First argument is an object with subcommands. It can be destructured to get access to subcommands.

Returns:

branch

git.branch(branchName)

Checkout to provided branch.

If branch doesn’t exist - create new branch.

Parameters:

branchName
string | BranchOptions
required
interface BranchOptions {
  branch: string;
  /**
   * @default true
   */
  force?: boolean;
}

Switch to provided branch. By default creates a branch if it doesn’t exist.

Returns:

commit

git.commit(message)

Creates commit for cloned repository.

single commit
import { git } from '@codemod.com/workflow'

// clone
const repository = await git.clone('git@github.com:codemod-com/codemod.git')
// make changes
await repository
    .files()
    .jsFam()
    .astGrep('console.log($$$A)')
    .replace('console.error($$$A)')
// commit
await repository
    .commit('Changed console.log to console.error')

Parameters:

message
string
required

Add a git commit message.

Returns:

push

git.push(options)

Pushes commits to remote.

push after commit
import { git } from '@codemod.com/workflow'

// clone
const repository = await git.clone('git@github.com:codemod-com/codemod.git')
// make changes
await repository
    .files()
    .jsFam()
    .astGrep('console.log($$$A)')
    .replace('console.error($$$A)')
// commit and push
await repository
    .commit('Changed console.log to console.error')
    .push({ force: true })

Parameters:

options
PushOptions
interface PushOptions {
  /**
   * Enables force push
   * @default false
   */
  force?: boolean
}

File system

files

files(glob, callback)

Filters out files using glob. By default all the files will be found.

Parameters:

glob
string | readonly string[]

Accepts string and template literals. You can space or comma-separate multiple globs.

callback
(subcommands: Subcommands) => Promise<void> | void

A callback which will be executed for each file. First argument is an object with subcommands. It can be destructured to get access to subcommands.

Returns:

dirs

dirs(glob, callback)

Filters out directories using glob. By default all the directories will be found.

When iterating over directories - new current working directory context is set. It means that all the commands executed inside callback will be executed in the context of the directory.

Parameters:

glob
string | readonly string[]

Accepts string and template literals. You can space or comma-separate multiple globs.

callback
(subcommands: Subcommands) => Promise<void> | void

A callback which will be executed for each directory. First argument is an object with subcommands. It can be destructured to get access to subcommands.

Returns:

move

move(target)

Moves files or directories to the target directory.

Parameters:

target
string

Absolute path to the target directory.

Code transformation operations

codemod

codemod(name, arguments)

Executes codemod from registry in current working directory.

Parameters:

name
string
required

Codemod name as published in Codemod Registry.

arguments
string | number | boolean | (string | number | boolean)[]

Codemod arguments. Arguments are passed as key-value pair objects.

Returns:

jsFam

jsFam(callback)

Returns JavaScript/TypeScript specific subcommands and helpers.

combining engines
import { files } from '@codemod.com/workflow'

await files()
  .jsFam(await ({ addImport, astGrep, jscodeshift }) => {
    addImport("import { useRef } from 'react'")

    await astGrep('console.log($$$A)')
      .replace('console.error($$$A)')

    await jscodeshift(async ({ source }, { j }) => {
      const root = j(source);
      root
        .find(j.Identifier)
        .replaceWith(j.literal('Hello'))
      return root.toSource()
    })
  })

Parameters:

callback
CallbackFunction
required
import type { SgNode } from '@ast-grep/napi'

interface CallbackFunctionHelpers {
  addImport: (imports: string) => void,
  removeImport: (imports: string) => void,
  astGrep: // see pattern matching,
  jscodeshift: // see jscodeshift,
}

type CallbackFunction =
  (helpers: CallbackFunctionHelpers) => Promise<void> | void

Allows to use JavaScript/TypeScript specific subcommands and helpers. Inside callback you can use addImport and removeImport to add or remove imports, astGrep to search for code patterns and jscodeshift to transform code.

jsFam is always should be used together with files command. It automatically adds glob to find all js/ts files, so calling files().jsFam() is equivalent to files('**/*.{js,jsx,ts,tsx,cjs,mjs}').jsFam().

Callback helpers:

  • addImport(imports) - Accepts code with imports and automatically merges file imports with provided imports.
  • removeImport(imports) - Accepts code with imports and automatically removes provided imports from file imports.

Returns:

astGrep

astGrep(pattern, callback)

Search part of the code (single AST node) using ast-grep and iterate over found nodes in the callback or returned commands. Creates separate context for each found node, which could be reused later.

callback
import { git } from '@codemod.com/workflow'

await git
  .clone('repository-to-clone')
  .files('**/*.{jsx,tsx}')
  .jsFam()
  /**
   * For string patterns, by default
   * strictness `relaxed` is used,
   * so comments and non-significant
   * syntax constructs will be ignored.
   * Possible matches:
   * - import React from 'react'
   * - import React from "react"
   */
  .astGrep('import React from "react"', async ({ replace, map }) => {
    // remove import statement
    await replace('')
    const foundCode = await map(({ getNode }) => getNode().text())
    console.log(`Found code:\n${foundCode.join('\n')}`)
})

Parameters:

pattern
string | readonly string[] | NapiConfig | AstGrepAPI
required
interface NapiConfig {
  rule: object
  constraints?: object
  language?: FrontEndLanguage
  transform?: object
  utils?: object
}
interface AstGrepAPI {
  id: string
  language: FrontEndLanguage
  rule: object
  utils?: any
  fix?: any
}

There are multiple ways to define a pattern:

  • String pattern, e.g. console.log($$$A); by default relaxed strictness algorithm is used, meaning that comments and non-significant syntax constructs (like single and double quotes for JavaScript) will be ignored.
  • Object pattern, using NapiConfig, which will be passed as is to ast-grep engine.
  • Template literal, using YAML format inside. It is a syntax sugar for NapiConfig object, so you can copy rules from ast-grep playground and paste them here.
  • Object pattern, using AstGrepAPI. In this case request is sent to ast-grep CLI and you can use all its features, like fix. But there is no programmatic access to the results, it is one-way operation. It has similar syntax to NapiConfig, but with additional id field.
callback
(subcommands: Subcommands) => Promise<void> | void

A callback which will be executed for each repository. First argument is an object with subcommands. It can be destructured to get access to subcommands.

Returns:

jscodeshift

jscodeshift(callback)

Runs jscodeshift transformation on the file.

Parameters:

callback
CallbackFunction
required
import type { API, FileInfo, Options } from "jscodeshift"

type CallbackFunction =
  (file: FileInfo, api: API, options: Options) => Promise<void> | void

Callback replicates jscodeshift Api, so any codemod created with jscodeshift can be used here.

addImport

addImport(importString)

Adds import to js file. Accepts code with imports and automatically merges file imports with provided imports.

Parameters:

importString
string
required

Full import line, e.g. import { useEffect } from "react".

removeImport

removeImport(importString)

Removes import from js file. Accepts code with imports and automatically removes provided imports from file imports.

Parameters:

importString
string
required

Full import line, e.g. import { useEffect } from "react".

Command line operations

exec

exec(name, arguments)

Executes cli command in current working directory.

Parameters:

name
string
required

Command name.

arguments
string[]

Arguments passed to command.

Returns: