or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cli-commands.mddangerfile-api.mdgit-utilities.mdgithub-integration.mdindex.mdplatform-integrations.md
tile.json

github-integration.mddocs/

GitHub Integration

Comprehensive GitHub integration with full API access, utilities for common tasks, and GitHub Actions support.

Capabilities

GitHub DSL

Complete access to GitHub pull request metadata, API, and utilities.

interface GitHubDSL {
  readonly issue: GitHubIssue;
  readonly pr: GitHubPRDSL;
  readonly thisPR: GitHubAPIPR;
  readonly commits: GitHubCommit[];
  readonly reviews: GitHubReview[];
  readonly requested_reviewers: GitHubReviewers;
  readonly api: GitHub; // Full Octokit REST API
  readonly utils: GitHubUtilsDSL;
  setSummaryMarkdown: (markdown: string) => void;
}

Pull Request Data

Basic PR Information

interface GitHubPRDSL {
  number: number;
  state: "closed" | "open" | "locked" | "merged";
  locked: boolean;
  title: string;
  body: string;
  created_at: string;
  updated_at: string;
  closed_at: string | null;
  merged_at: string | null;
  draft: boolean;
  merged: boolean;
  
  // User information
  user: GitHubUser;
  assignee: GitHubUser;
  assignees: GitHubUser[];
  
  // Statistics
  comments: number;
  review_comments: number;
  commits: number;
  additions: number;
  deletions: number;
  changed_files: number;
  
  // References
  head: GitHubMergeRef;
  base: GitHubMergeRef;
  html_url: string;
  
  // Author association
  author_association: "COLLABORATOR" | "CONTRIBUTOR" | "FIRST_TIMER" | 
                     "FIRST_TIME_CONTRIBUTOR" | "MEMBER" | "NONE" | "OWNER";
}

Usage Examples:

// Check PR size
if (danger.github.pr.additions > 500) {
  warn(`Large PR: ${danger.github.pr.additions} lines added. Consider breaking into smaller PRs.`);
}

// Check PR title conventions
if (!danger.github.pr.title.match(/^(feat|fix|docs|style|refactor|test|chore):/)) {
  fail("PR title must start with a type (feat:, fix:, docs:, etc.)");
}

// Check for draft PRs
if (danger.github.pr.draft) {
  message("πŸ“ This is a draft PR");
}

// Check author association
if (danger.github.pr.author_association === "FIRST_TIME_CONTRIBUTOR") {
  message("πŸŽ‰ Welcome! Thanks for your first contribution!");
}

// Validate PR description
if (danger.github.pr.body.length < 50) {
  warn("Please add a more detailed PR description");
}

User Information

interface GitHubUser {
  id: number;
  login: string;
  type: "User" | "Organization" | "Bot";
  avatar_url: string;
  href: string;
}

Repository Information

interface GitHubRepo {
  id: number;
  name: string;
  full_name: string;
  owner: GitHubUser;
  private: boolean;
  description: string;
  fork: boolean;
  html_url: string;
}

Issue and Label Data

interface GitHubIssue {
  labels: GitHubIssueLabel[];
}

interface GitHubIssueLabel {
  id: number;
  url: string;
  name: string;
  color: string;
}

Usage Examples:

// Check for required labels
const hasTypeLabel = danger.github.issue.labels.some(label => 
  label.name.startsWith("type:")
);

if (!hasTypeLabel) {
  warn("Please add a type: label to categorize this PR");
}

// Check for breaking change labels
const hasBreakingChange = danger.github.issue.labels.some(label =>
  label.name === "breaking-change"
);

if (hasBreakingChange) {
  fail("⚠️ This PR contains breaking changes. Update major version.");
}

Commit Data

interface GitHubCommit {
  commit: GitCommit;
  sha: string;
  url: string;
  author: GitHubUser;
  committer: GitHubUser;
  parents: any[];
}

interface GitCommit {
  sha: string;
  author: GitCommitAuthor;
  committer: GitCommitAuthor;
  message: string;
  tree: any;
  parents?: string[];
  url: string;
}

Usage Examples:

// Check commit message conventions
const invalidCommits = danger.github.commits.filter(commit =>
  !commit.commit.message.match(/^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+/)
);

if (invalidCommits.length > 0) {
  fail(`${invalidCommits.length} commits don't follow conventional commit format`);
}

// Check for merge commits
const mergeCommits = danger.github.commits.filter(commit =>
  commit.commit.message.startsWith("Merge")
);

if (mergeCommits.length > 0) {
  warn("Found merge commits. Consider rebasing to maintain a clean history.");
}

Review Data

interface GitHubReview {
  user: GitHubUser;
  id?: number;
  body?: string;
  commit_id?: string;
  state?: "APPROVED" | "REQUEST_CHANGES" | "COMMENT" | "PENDING";
}

interface GitHubReviewers {
  users: GitHubUser[];
  teams: any[];
}

Usage Examples:

// Check for approvals
const approvals = danger.github.reviews.filter(review => 
  review.state === "APPROVED"
);

if (approvals.length === 0) {
  message("⏰ Waiting for approval from reviewers");
}

// Check for requested changes
const changesRequested = danger.github.reviews.filter(review =>
  review.state === "REQUEST_CHANGES"
);

if (changesRequested.length > 0) {
  warn("πŸ“ Some reviewers have requested changes");
}

GitHub API Access

Full Octokit Integration

Direct access to the complete GitHub REST API via Octokit.

// GitHub API client (Octokit instance)
readonly api: GitHub;

Usage Examples:

// Create issue comments
schedule(async () => {
  await danger.github.api.rest.issues.createComment({
    owner: danger.github.thisPR.owner,
    repo: danger.github.thisPR.repo,
    issue_number: danger.github.thisPR.number,
    body: "Automated comment from Danger"
  });
});

// Add labels to PR
schedule(async () => {
  await danger.github.api.rest.issues.addLabels({
    owner: danger.github.thisPR.owner,
    repo: danger.github.thisPR.repo,
    issue_number: danger.github.thisPR.number,
    labels: ["needs-review", "enhancement"]
  });
});

// Request reviewers
schedule(async () => {
  await danger.github.api.rest.pulls.requestReviewers({
    owner: danger.github.thisPR.owner,
    repo: danger.github.thisPR.repo,
    pull_number: danger.github.thisPR.number,
    reviewers: ["senior-dev1", "senior-dev2"]
  });
});

// Create GitHub Check
schedule(async () => {
  await danger.github.api.rest.checks.create({
    owner: danger.github.thisPR.owner,
    repo: danger.github.thisPR.repo,
    name: "Custom Security Check",
    head_sha: danger.github.pr.head.sha,
    status: "completed",
    conclusion: "success",
    output: {
      title: "Security scan passed",
      summary: "No security vulnerabilities found"
    }
  });
});

PR Helper Object

interface GitHubAPIPR {
  owner: string;
  repo: string;
  number: number; // @deprecated use pull_number
  pull_number: number;
}

Usage Examples:

// Use thisPR for API calls
const { owner, repo, pull_number } = danger.github.thisPR;

schedule(async () => {
  const prFiles = await danger.github.api.rest.pulls.listFiles({
    owner,
    repo,
    pull_number
  });
  
  const largeFiles = prFiles.data.filter(file => file.changes > 100);
  if (largeFiles.length > 0) {
    warn(`Large files changed: ${largeFiles.map(f => f.filename).join(", ")}`);
  }
});

GitHub Utilities

File Operations

interface GitHubUtilsDSL {
  fileLinks: (paths: string[], useBasename?: boolean, repoSlug?: string, branch?: string) => string;
  fileContents: (path: string, repoSlug?: string, ref?: string) => Promise<string>;
}

fileLinks

Creates clickable links to files in the GitHub interface.

/**
 * Creates HTML links for file paths pointing to GitHub source.
 * @param paths - Array of file paths
 * @param useBasename - Show only filename (true) or full path (false)
 * @param repoSlug - Override repo (default: current PR repo)
 * @param branch - Override branch (default: PR head branch)
 * @returns HTML string with clickable file links
 */
fileLinks(paths: string[], useBasename?: boolean, repoSlug?: string, branch?: string): string;

Usage Examples:

// Create links for modified files
const modifiedFiles = danger.git.modified_files.slice(0, 5);
const fileLinks = danger.github.utils.fileLinks(modifiedFiles);
message(`Modified files: ${fileLinks}`);

// Show full paths instead of basenames
const fullPathLinks = danger.github.utils.fileLinks(
  danger.git.created_files,
  false // show full paths
);

// Link to files in different repo/branch
const crossRepoLinks = danger.github.utils.fileLinks(
  ["docs/README.md"],
  true,
  "company/docs-repo",  // different repo
  "main"                // different branch
);

fileContents

Downloads file contents from GitHub for analysis.

/**
 * Downloads file contents via GitHub API.
 * @param path - File path to download
 * @param repoSlug - Override repo (default: current PR repo)
 * @param ref - Override ref (default: PR head)
 * @returns Promise resolving to file contents as string
 */
fileContents(path: string, repoSlug?: string, ref?: string): Promise<string>;

Usage Examples:

// Check package.json changes
schedule(async () => {
  if (danger.git.modified_files.includes("package.json")) {
    const packageJson = await danger.github.utils.fileContents("package.json");
    const pkg = JSON.parse(packageJson);
    
    if (!pkg.engines?.node) {
      warn("Consider adding Node.js version requirement to package.json");
    }
  }
});

// Compare files across branches
schedule(async () => {
  const mainReadme = await danger.github.utils.fileContents(
    "README.md",
    undefined, // current repo
    "main"     // main branch
  );
  
  const prReadme = await danger.github.utils.fileContents("README.md");
  
  if (mainReadme.length > prReadme.length * 2) {
    warn("README.md was significantly shortened. Was this intentional?");
  }
});

Issue Management

interface GitHubUtilsDSL {
  createUpdatedIssueWithID: (
    id: string,
    content: string,
    config: {
      title: string;
      open: boolean;
      owner: string;
      repo: string;
    }
  ) => Promise<string>;
}

Usage Examples:

// Create or update tracking issue
schedule(async () => {
  const issueId = "weekly-metrics";
  const content = `
## Weekly Metrics Report

- PRs merged: ${weeklyStats.mergedPRs}
- Issues closed: ${weeklyStats.closedIssues}
- New contributors: ${weeklyStats.newContributors}
  `;

  await danger.github.utils.createUpdatedIssueWithID(
    issueId,
    content,
    {
      title: "Weekly Metrics Report",
      open: true,
      owner: danger.github.thisPR.owner,
      repo: danger.github.thisPR.repo
    }
  );
});

Label Management

interface GitHubUtilsDSL {
  createOrAddLabel: (
    labelConfig: {
      name: string;
      color: string;
      description: string;
    },
    repoConfig?: {
      owner: string;
      repo: string;
      id: number;
    }
  ) => Promise<void>;
}

Usage Examples:

// Ensure labels exist and add them
schedule(async () => {
  // Create/ensure label exists
  await danger.github.utils.createOrAddLabel({
    name: "needs-docs",
    color: "fef2c0",
    description: "Documentation needs to be added or updated"
  });

  // Add to current PR
  const hasDocChanges = danger.git.modified_files.some(f => 
    f.includes("docs/") || f.includes("README")
  );
  
  if (!hasDocChanges && danger.git.modified_files.some(f => f.includes("src/"))) {
    await danger.github.utils.createOrAddLabel(
      {
        name: "needs-docs",
        color: "fef2c0",
        description: "Documentation needs to be added or updated"
      },
      {
        owner: danger.github.thisPR.owner,
        repo: danger.github.thisPR.repo,
        id: danger.github.thisPR.number
      }
    );
  }
});

PR Creation and Updates

interface GitHubUtilsDSL {
  createOrUpdatePR: (
    config: {
      title: string;
      body: string;
      owner?: string;
      repo?: string;
      commitMessage: string;
      newBranchName: string;
      baseBranch: string;
    },
    fileMap: any
  ) => Promise<any>;
}

GitHub Actions Integration

Job Summaries

Set markdown summaries that appear in GitHub Actions job overview.

/**
 * Sets a markdown summary for GitHub Actions jobs.
 * @param markdown - Markdown content for the job summary
 */
setSummaryMarkdown: (markdown: string) => void;

Usage Examples:

// Create comprehensive job summary
const summary = `
## πŸ” Code Review Summary

### πŸ“Š PR Statistics
- **Files changed:** ${danger.github.pr.changed_files}
- **Lines added:** ${danger.github.pr.additions}
- **Lines removed:** ${danger.github.pr.deletions}
- **Commits:** ${danger.github.pr.commits}

### βœ… Checks Performed
- [x] Lint check
- [x] Test coverage
- [x] Security scan
- [x] Documentation update

### 🎯 Quality Metrics
- Test coverage: 87%
- Code quality score: A-
- Security rating: βœ… Pass
`;

danger.github.setSummaryMarkdown(summary);

Environment Variables

GitHub Actions provides these environment variables automatically:

GITHUB_TOKEN          # Automatically provided token
GITHUB_REPOSITORY     # Repository slug (owner/repo)
GITHUB_EVENT_NAME     # Event that triggered the workflow
GITHUB_SHA            # Commit SHA
GITHUB_REF            # Branch or tag ref

Usage in Workflows:

- name: Run Danger
  run: danger ci
  env:
    DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}