Skip to content

Instantly share code, notes, and snippets.

@rsms
Created June 21, 2018 19:17
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save rsms/3ed1e3feb6abc5200ae269602a814f31 to your computer and use it in GitHub Desktop.
Save rsms/3ed1e3feb6abc5200ae269602a814f31 to your computer and use it in GitHub Desktop.
Script that generates statistics for a Figma project, like number of files, frames, versions etc
//
// Figma project stats
// Pulls statistics like number of files, frames, versions etc for a project.
//
// Usage:
// export FIGMA_API_ACCESS_TOKEN='your-token'
// node figma-project-stats.js <project-id>
//
// You can generate tokens in your account settings or at
// https://www.figma.com/developers/explorer#personal-access-token
//
// <project-id> is the last number in the URL path of a project.
// For instance, in this URL:
// https://www.figma.com/files/5027479082769520/project/712/Project-Title
// The project id is "712"
//
const http = require('https')
let FIGMA_API_ACCESS_TOKEN = '' // populated from env
let FIGMA_API_HOST = 'api.figma.com' // may be populated from env
function findfkey(url) {
return url.match(/\.com\/file\/([^\/]+)/)[1]
}
function apiget(path) {
return new Promise((resolve, reject) => {
let shortCircuited = false
let req = http.request(
{
protocol: 'https:',
host: FIGMA_API_HOST,
method: 'GET',
path: '/v1/' + path,
headers: {
'X-FIGMA-TOKEN': FIGMA_API_ACCESS_TOKEN,
},
},
res => {
if (res.statusCode == 504 || res.statusCode == '504') {
console.log(`${path} failed with status ${res.statusCode} -- retrying...`)
shortCircuited = true
return setTimeout(() => {
apiget(path).then(resolve).catch(reject)
}, 1000)
}
// console.log(`STATUS: ${res.statusCode}`)
// console.log(`HEADERS:`, JSON.parse(JSON.stringify(res.headers)))
let buf = ''
res.setEncoding('utf8')
res.on('data', chunk => { buf += chunk })
res.on('end', () => {
let r = null
try {
r = JSON.parse(buf)
} catch (err) {
console.error(`failed to parse response body: ${err}`)
console.error(`original body:\n------${buf}\n------`)
return reject(err)
}
resolve(r, { status: res.statusCode })
})
}
)
req.on('error', err => {
if (!shortCircuited) {
reject(err)
}
})
req.end()
}) // promise
}
const frameLikeTypes = new Set('FRAME GROUP COMPONENT INSTANCE'.split(' '))
function countFrameLikeNodes(parent) {
let n = 0
for (let node of parent.children) {
if (frameLikeTypes.has(node.type)) {
n++
}
}
return n
}
function usage() {
console.error(
`usage: ${require('path').basename(__filename)} <project-id>\n` +
`Environment variables:\n` +
` FIGMA_API_ACCESS_TOKEN API access token (required)\n` +
` FIGMA_API_HOST API server hostname (e.g. "local-api.figma.com")\n`
)
process.exit(1)
}
function main(args) {
FIGMA_API_ACCESS_TOKEN = process.env['FIGMA_API_ACCESS_TOKEN'] || ''
FIGMA_API_HOST = process.env['FIGMA_API_HOST'] || FIGMA_API_HOST
if (args.length < 1) {
console.error('missing project id')
usage()
}
if (args.indexOf('-h') != -1 || args.indexOf('--help') != -1) {
usage()
}
if (FIGMA_API_ACCESS_TOKEN.length == 0) {
console.error(
`Missing env variable FIGMA_API_ACCESS_TOKEN\n` +
`Visit the following URL to generate a token:\n` +
` https://www.figma.com/developers/explorer#personal-access-token\n`
)
usage()
}
let projectId = args[0].replace(/[^\d]+/g, '')
console.log(`collecting stats for project ${projectId} on ${FIGMA_API_HOST}`)
apiget(`projects/${projectId}/files`).then(r => {
console.log(`inspecting ${r.files.length} project files...`)
let nextFileIndex = 0
let nTotalPages = 0
let nTotalVersions = 0
let nTotalFrames = 0
let doNextFile = (key) => {
let file = r.files[nextFileIndex++]
if (!file) {
// done
console.log('project summary:')
console.log('files: ', r.files.length)
console.log('pages: ', nTotalPages)
console.log('versions: ', nTotalVersions)
console.log('top-level frames:', nTotalFrames)
return
}
console.log(`file ${file.key} "${file.name}":`)
let nversions = 0
let npages = 0
let nTopLevelFrames = 0
Promise.all([
apiget(`files/${file.key}/versions`).then(r => {
nversions = r.versions.length
}),
apiget(`files/${file.key}`).then(r => {
let pages = r.document.children
npages = pages.length
for (let page of pages) {
nTopLevelFrames += countFrameLikeNodes(page)
}
}),
]).then(() => {
console.log(' versions: ', nversions)
console.log(' pages: ', npages)
console.log(' top-level frames:', nTopLevelFrames)
nTotalPages += npages
nTotalVersions += nversions
nTotalFrames += nTopLevelFrames
doNextFile()
}).catch(err => {
console.error(err.stack || String(err))
})
}
doNextFile()
})
}
main(process.argv.slice(2))
@rsms
Copy link
Author

rsms commented Jun 21, 2018

Wondering how to only process some files? One way of doing that is to filter on files which name/title matches a certain Regular Expression.

You can add a little bit of code to doNextFile to accomplish this:

    let doNextFile = (key) => {
      let file = r.files[nextFileIndex++]
      if (!file) {
        // done
        console.log('project summary:')
        console.log('files:           ', r.files.length)
        console.log('pages:           ', nTotalPages)
        console.log('versions:        ', nTotalVersions)
        console.log('top-level frames:', nTotalFrames)
        return
      }

      // start of added code
      const nameFilterRegExp = /^some prefix /
      if (!nameFilterRegExp.test(file.name)) {
        // skip file -- doesn't match our filter
        return doNextFile()
      }
      // end of added code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment