-
-
Save arielsalminen/725c53fc4b353cb51ef2e7b3f96bd84f to your computer and use it in GitHub Desktop.
Script that generates statistics for a Figma project, like number of files, frames, versions etc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment