techdebt

An attempt to show technical debt to sponsors

Usage no npm install needed!

<script type="module">
  import techdebt from 'https://cdn.skypack.dev/techdebt';
</script>

README

techdebt

Utils function to compute technical debt metrics and display them on the support of your choice.

Install

npm install --save-dev techdebt

Get started

This package is nothing more than an aggregation of helpers to get, save and display metrics.

It helps you to easily write a small script that computes metrics representing your technical debt. A good idea would be to run this script during continuous integration so that it updates a technical debt dashboard or Slack channel.

Simple example: display the todo comments in Slack

const techdebt = require('techdebt')
const slackClient = require('techdebt/clients/slack')
const fsHelper = require('techdebt/helpers/fs')

slackClient.initialize({
  hookUrl: 'https://hooks.slack.com/services/xx/xx/xx',
  channel: '#techdebt',
  username: 'Techdebt bot'
})

techdebt.run([
  {
    get: () => fsHelper.getRegexMatches('src', /todo(.*)/gi),
    format: (matches) => ({
      title: 'Todos in code',
      text: matches.length > 0 : matches.join('\n') : 'No todos in code ☀️',
      color: matches.length > 0 : 'warning' : 'good'
    })
  },
])
.then(formatedMetrics => slackClient.post('Techdebt report', formatedMetrics))
.then(() => {
  return process.exit()
})
.catch(error => {
  console.error(error)
  return process.exit(1)
})

example result

More complex example: display the historic of todos count in Slack

Sometimes it's useful to get the history of a metric and display it as a graph. To save a metric you can use any API that you like. The simplest API I found is datadog that allows to save metrics and take a snapshot of a graph of the metric.

const techdebt = require('techdebt')
const slackClient = require('techdebt/clients/slack')
const fsHelper = require('techdebt/helpers/fs')

slackClient.initialize({
  hookUrl: 'https://hooks.slack.com/services/xx/xx/xx',
  channel: '#techdebt',
  username: 'Techdebt bot'
})
datadogClient.initialize({
  api_key: 'xxx',
  app_key: 'xxx'
})

techdebt.run([
  {
    get: () => {
      const count = fsHelper.countRegex('src', /todo/gi)
      return count
    },
    save: (count) => datadogClient.post('techdebt.todos', count),
    format: (count) => datadogClient.get('techdebt.todos', 3600 * 24 * 30)
      .then(snapshotUrl => ({ text: 'Todos in code', image_url: snapshotUrl }))
  }
])
.then(formatedMetrics => slackClient.post('Techdebt report', formatedMetrics))
.then(() => {
  return process.exit()
})
.catch(error => {
  console.error(error)
  return process.exit(1)
})

example result

API doc

main

techdebt.run(metrics)

Takes an array of metrics and return a promise of formated metrics.

Example:

const techdebt = require('techdebt')

techdebt.run(metrics)
.then(formatedMetrics => {
  // do whatever you want with formated metrics
})

Metric

{

  get: Function
  save: Function
  format: Function
}

get()

A function that fetch the metric value. It returns a value of any type (you'll have to handle the value).

It can return a value or the promise of a value.

Example:

get: () => fsHelper.countRegex('src', /todo/gi)

save(metricValue) (optional)

A function that save the value on a third party tool such as Datadog, Google Spreadsheet etc.

If it returns a promise, the format function will wait for this promise to be resolved before execution.

Example:

const save = (metricValue) => datadogClient.post('techdebt.my_metric', metricValue)

format(metricValue)

A function that format the value as expected.

Example:

format = (metricValue) => ({
  title: 'Todos count in code',
  text: metricValue
})

Clients

Slack

slackClient.initialize(options)

Initialize the client with options

Example:

const slackClient = require('techdebt/clients/slack')

slackClient.initialize({
  hookUrl: 'https://hooks.slack.com/services/xx/xx/xx', // required
  channel: '#techdebt', // required
  username: 'Techdebt bot' // optional
})
slackClient.post(title, attachments)

Post attachments to Slack.

Datadog

Using this client requires to install dogapi package:

npm install --save-dev dogapi
datadogClient.initialize(options)
const datadogClient = require('techdebt/clients/datadog')

datadogClient.initialize({
  api_key: 'xxx', //required
  app_key: 'xxx' // required
})

Codecov

codecovClient.initialize(options)
const codecovClient = require('techdebt/clients/codecov')

codecovClient.initialize({
  accessToken: 'xxx', //required
  repo: 'owner/repo', // required
  branch: 'xxx' // required
})
codecovClient.get()

Return a promise on the last code coverage ratio.

codecovClient.get()
.then(({ timestamp, ratio }) => {
  // ratio between 0 and 100
})
datadogClient.post(metricName, metricValue, timestamp)

Post a metric value that will be saved to datadog and wait for the metric to be fetchable (datadog has a delay in serving posted metrics).

The timestamp attribute is optional (default is now)

It returns a promise that resolves when the metric is saved.

datadogClient.get(metricName, period)

Get the snapshot of a graph of a metric.

It returns a promise of the URL of the snapshot image.

missing a client? Trello, Google sheet ? Please write an issue

Helpers

packages

This helper requires to install npm-check

npm install --save-dev npm-check

This helper allow you to know which of your dependencies are unused or need upgrades

packages.get(type)

type can be one of major, minor, unused, upToDate.

It returns a promise of the list of packages corresponding to the type.

packages.slackFormat(type, packages)

Returns a predefined Slack attachment format

Example:

const slackClient = require('techdebt/clients/slack')
const packages = require('techdebt/helpers/packages')
const techdebt = require('techdebt')

slackClient.initialize({
  hookUrl: 'https://hooks.slack.com/services/xx/xx/xx',
  channel: '#techdebt',
  username: 'Techdebt bot'
})

techdebt.run([
  {
    get: () => packages.get('major'),
    format: (items) => packages.slackFormat('major', items)
  },
  {
    get: () => packages.get('minor'),
    format: (items) => packages.slackFormat('minor', items)
  },
  {
    get: () => packages.get('unused'),
    format: (items) => packages.slackFormat('unused', items)
  },
  {
    get: () => packages.get('upToDate'),
    format: (items) => packages.slackFormat('upToDate', items)
  }
])
.then(formatedMetrics => {
  return slackClient.post('Techdebt report', formatedMetrics)
})
.then(() => {
  return process.exit()
})
.catch(error => {
  console.error(error)
  return process.exit(1)
})

example result

fsHelper

A librairy of helper functions to analyse source files.

fsHelper.readRecursively(root)

Get the recursive list of files in a directory.

Example:

fsHelper.readRecursively('src')
fsHelper.countLines(root, options)

Sum the lines of files in a directory (recursively). You can specify an extension in the options. The returned value is a promise of the line count.

Example:

fsHelper.countLines('src', { extension: 'js' }).then(lineCount => ...)
fsHelper.countRegex(root, regex, options)

Sum the number of occurence if a regex in a directory (recursively). You can specify an extension in the options. The returned value is a promise of the count.

Example:

fsHelper.countRegex('src', /todo/gi, { extension: 'js' }).then(count => ...)
const slackClient = require('techdebt/clients/slack')
const datadogClient = require('techdebt/clients/datadog')
const fsHelper = require('techdebt/helpers/fs')
const techdebt = require('techdebt')

slackClient.initialize({
  hookUrl: 'https://hooks.slack.com/services/xx/xx/xx',
  channel: '#techdebt',
  username: 'Techdebt bot'
})
datadogClient.initialize({
  api_key: 'xxx',
  app_key: 'xxx'
})

techdebt.run([
  {
    get: () => {
      const count = fsHelper.countRegex('src', /todo/gi)
      return count
    },
    save: (value) => datadogClient.post('techdebt.todos', value),
    format: (count) => datadogClient.get('techdebt.todos', 3600 * 24 * 30).then(snapshotUrl => ({
      text: 'Todos in code',
      image_url: snapshotUrl
    }))
  },
  {
    get: () => fsHelper.getRegexMatches('src', /todo(.*)/gi),
    format: (matches) => ({
      title: 'Todos in code',
      text: matches.join('\n'),
      color: 'warning'
    })
  }
])
.then(formatedMetrics => {
  return slackClient.post('Techdebt report', formatedMetrics)
})
.then(() => {
  return process.exit()
})
.catch(error => {
  console.error(error)
  return process.exit(1)
})
fsHelper.getRegexMatches(root, regex, options)

Get the matches of a regex in a directory (recursively). You can specify an extension in the options. The returned value is a promise of the matches.

Example:

fsHelper.getRegexMatches('src', /todo(.*)/gi, { extension: 'js' }).then(matches => ...)