Run Extensions

Run analysis and automation scripts across distributed repositories.

divekit run executes local extensions against repositories from a distribution. Typical uses are static analysis, report extraction, custom checks, or orchestration around external tools.

Before running an extension, make repositories available locally:

divekit fetch -d ST2M4

Then run an extension from .divekit/scripts/run/ or by path:

divekit run analyze.lua -d ST2M4
divekit run ./scripts/analyze.py -d ST2M4

Use --fetch-first when Divekit should fetch before executing:

divekit run analyze.lua -d ST2M4 --fetch-first

Simple Lua Extensions

A simple Lua extension returns a function. Divekit calls that function once for every selected repository.

return function(dk)
  local pom = dk.fs.read("pom.xml")

  dk.results.emit({
    has_pom = pom ~= nil,
  })
end

During per-repository execution, dk.run contains the current repository context:

FieldMeaning
dk.run.distributionCurrent distribution name.
dk.run.uuidCurrent repository UUID.
dk.run.pathLocal path to the repository.
dk.run.result_filePer-repository result file path.
dk.run.results_dirResult directory for the full run.

Lua Workflows

Use a Lua workflow when an extension needs lifecycle steps around the per-repository analysis. A workflow registers callbacks at top level:

dk.run.prepare(function(ctx)
  os.execute("docker compose up -d --wait")
end)

dk.run.each(function(repo)
  local pom = dk.fs.read("pom.xml")

  dk.results.emit({
    uuid = repo.uuid,
    has_pom = pom ~= nil,
  })
end)

dk.run.cleanup(function(ctx)
  os.execute("docker compose down")
end)

Run it like any other Lua extension:

divekit run workflow.lua -d ST2M4

Workflow order:

  1. dk.run.prepare(function(ctx) ... end) runs once before repository processing.
  2. dk.run.each(function(repo) ... end) runs once per selected repository.
  3. dk.run.cleanup(function(ctx) ... end) runs once at the end, including after prepare or per-repository failures.

The workflow context passed to prepare and cleanup contains:

FieldMeaning
ctx.distributionCurrent distribution name.
ctx.results_dirResult directory for the full run.
ctx.repositoriesList of selected repositories with uuid, path, source, and label.
ctx.reposAlias for ctx.repositories.

Lua extensions executed by divekit run are local code execution. They may use standard Lua APIs such as os.execute, which makes workflows suitable for starting and stopping local services such as Docker Compose. Divekit waits for prepare to finish before running repository analysis; use blocking readiness commands such as docker compose up -d --wait when services must be ready before each starts.

External Extensions

Shell, Python, Node, Deno, Go, Java, and Kotlin extensions run once per repository by default. Divekit passes repository context through stdin JSON and environment variables:

echo "$DIVEKIT_REPO_UUID"
echo "$DIVEKIT_REPO_PATH"
echo "$DIVEKIT_RESULTS_DIR"

Use --js-runner to choose the JavaScript runner and --runner-arg to pass runner-specific flags:

divekit run ./scripts/analyze.js -d ST2M4 --js-runner bun
divekit run ./scripts/analyze.ts -d ST2M4 --runner-arg=--allow-read --runner-arg=--allow-env

External Workflows

Use run init to create a starter workflow extension:

divekit run init

The interactive form asks for the workflow name and language. You can also pass both values directly:

divekit run init has_readme --language lua

Lua is the recommended default and creates a normal named Lua workflow:

.divekit/scripts/run/has_readme.lua

Non-Lua languages create workflow directories:

.divekit/scripts/run/has_readme/
  workflow.json
  workflow.py

Use --language lua|shell|python|javascript|typescript|go|java|kotlin to choose the starter runtime. JavaScript and TypeScript workflows can also set a preferred runner:

divekit run init used_ai --language javascript --runner bun

Use -y to accept defaults (repository_check, lua) and --force when generated files should be overwritten.

The installed examples also include workflow directories for Shell, JavaScript, TypeScript, Go, Java, and Kotlin. Workflow directories can contain normal project files such as package.json, node_modules, go.mod, or helper modules next to the entry file.

The manifest describes the workflow and selects the runtime. Entry files and callback names have defaults, so most workflows only need metadata and a language:

{
  "version": 1,
  "name": "has_readme",
  "description": "Checks whether each repository contains a README.md file.",
  "author": {
    "name": "Divekit"
  },
  "runtime": {
    "language": "python"
  }
}

schema is optional. When used, point it at the Divekit CLI project, for example:

{
  "schema": "https://gitlab.git.nrw/divekit/divekit-cli/-/raw/main/assets/schemas/workflow.v1.json",
  "version": 1,
  "name": "has_readme",
  "runtime": {
    "language": "python"
  }
}

Default workflow entries:

LanguageDefault entryDefault functions
shellworkflow.shprepare, each, cleanup
pythonworkflow.pyprepare, each, cleanup
javascriptworkflow.jsprepare, each, cleanup
typescriptworkflow.tsprepare, each, cleanup
go.Prepare, Each, Cleanup
javaWorkflow.javaprepare, each, cleanup
kotlinworkflow.ktsprepare, each, cleanup

Override defaults only when the workflow uses different names:

{
  "version": 1,
  "name": "custom_check",
  "runtime": {
    "language": "javascript",
    "runner": "bun",
    "args": ["--smol"]
  },
  "workflow": {
    "entry": "src/check.js",
    "functions": {
      "each": "analyzeRepository"
    }
  }
}

Divekit calls workflow functions in order:

  1. prepare(ctx) once before repository processing, when defined.
  2. each(repo) once per selected repository.
  3. cleanup(ctx) once at the end, including after prepare or per-repository failures, when defined.

Workflow functions can return a result directly. JavaScript and TypeScript workflows may also return a Promise; Divekit waits for it. Divekit waits for prepare to finish before running each; use blocking readiness commands such as docker compose up -d --wait when services must be ready.

Example workflow.py:

import os

def prepare(ctx):
    print(f"preparing analysis for {len(ctx['repositories'])} repositories")

def each(repo):
    return {
        "uuid": repo["uuid"],
        "has_readme": os.path.exists(os.path.join(repo["path"], "README.md")),
    }

def cleanup(ctx):
    print("cleanup finished")

Run the workflow directory:

divekit run has_readme -d ST2M4

Divekit recognizes workflow directories by workflow.json. It executes the workflow with the directory as the working directory, so relative includes/imports and local dependencies stay predictable.

Results

Lua extensions can emit structured rows through dk.results.emit(...). External extensions can print JSON lines to stdout. Divekit collects emitted rows in the run results directory and can print a summary:

divekit run analyze.lua -d ST2M4 --output-format table
divekit run analyze.lua -d ST2M4 --output-format json,csv,md
divekit run analyze.lua -d ST2M4 --output-format csv --output-format md

Divekit always writes results.ndjson while the run is active and results.json after aggregation. Additional selected formats are written next to them:

FormatFileTiming
ndjsonresults.ndjsonLive during the run.
jsonresults.jsonEnd of run.
csvresults.csvEnd of run.
mdresults.mdEnd of run.
tableresults.txtEnd of run.

Use --output-dir to write result files to a custom directory:

divekit run analyze.lua -d ST2M4 --output-dir ./analysis-results --output-format csv,md

Related pages: