Skip to content

Instantly share code, notes, and snippets.

@rsms
Created June 21, 2018 19:17
Show Gist options
  • Select an option

  • Save rsms/3ed1e3feb6abc5200ae269602a814f31 to your computer and use it in GitHub Desktop.

Select an option

Save rsms/3ed1e3feb6abc5200ae269602a814f31 to your computer and use it in GitHub Desktop.

Revisions

  1. rsms created this gist Jun 21, 2018.
    186 changes: 186 additions & 0 deletions figma-project-stats.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,186 @@
    //
    // 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))