UNPKG

@frontify/frontify-api

Version:

Simplifies asset management and UI pattern creation within Frontify.

455 lines (392 loc) 12.6 kB
var path = require('path'); var globby = require('globby'); var rp = require('request-promise'); var extend = require('extend'); var _ = require('lodash'); var fs = require('mz/fs'); var mime = require('mime-types'); var del = require('del'); var moment = require('moment'); var slash = require('slash'); var events = require('./events'); var auth = require('./auth'); var cache = require('./cache'); var sha = require('./sha'); /* * Diff status objects and produce a list of transformations */ function diff(src, dest, options) { var changes = []; _.forIn(src.assets, function (asset, id) { var dasset = dest.assets[id]; var change = null; if(dasset) { if(asset.action === 'created') { // file exists in destination if (asset.sha !== dasset.sha) { change = extend(true, {}, dasset, asset, { action: 'update', remote: dest.remote}); } } else if(asset.action === 'deleted' && dasset.action !== 'deleted') { change = extend(true, {}, dasset, asset, { action: 'delete', remote: dest.remote}); } else if(asset.action === 'updated' && asset.sha !== dasset.sha) { change = extend(true, {}, dasset, asset, { action: 'update', remote: dest.remote}); } if(change) { if(options.strategy === 'newest') { if(asset.modified > dasset.modified) { changes.push(change); } } else { changes.push(change); } } } else if(asset.action === 'created') { changes.push(extend(true, {}, asset, { action: 'create', remote: dest.remote})); } }); // cleaning up the destination if(options.clean) { _.forIn(dest.assets, function (asset, id) { var sasset = src.assets[id]; var change; if (!sasset) { // file does only exists in destination change = extend(true, {}, asset, { action: 'delete', remote: dest.remote}); changes.push(change); } }); } return changes; } function syncAssets(meta, assets) { // reset library cache cache.reset(); meta = sanitizeMeta(meta); // get local and remote status var promises = []; promises.push(getLocalStatus({path: meta.cwd, target: meta.target, globs: assets})); promises.push(getRemoteStatus(meta)); return Promise.all(promises).then(function (statusList) { var changes = diff(statusList[0], statusList[1], { clean: meta.clean }); return patch(meta, changes); }); } function patch(meta, changes) { meta = sanitizeMeta(meta); return Promise.all(changes.map(function (change) { switch (change.action) { case 'create': if(change.remote) { return createRemoteAsset(meta, change); } else { return createLocalAsset(meta, change); } break; case 'update': if(change.remote) { return updateRemoteAsset(meta, change); } else { return updateLocalAsset(meta, change); } break; case 'delete': if(change.remote) { return deleteRemoteAsset(meta, change); } else { return deleteLocalAsset(meta, change); } break; } })); } /* * Get status of remote assets in Frontify */ function getRemoteStatus(meta, status) { meta = sanitizeMeta(meta); var options = auth(meta); // extend with filters var filters = { path : meta.target, cursor : meta.cursor || 0 }; options.qs = extend(true, {}, options.qs, filters); options.uri = '/v1/assets/status/' + meta.project; options.method = 'GET'; return rp(options).then(function(data) { // preprocess assets to match the structure of the local status var assets = {}; // only detect action if cursor (history option) is set if (status) { _.forIn(data.assets, function (asset, id) { if (asset === null) { assets[id] = extend(true, {}, asset, {action: 'deleted'}); } else if (asset.created > filters.cursor) { assets[id] = extend(true, {}, asset, {action: 'created'}); } else if (asset.modified > filters.cursor) { assets[id] = extend(true, {}, asset, {action: 'updated'}); } }); // add non-modified assets _.forIn(status.assets, function (asset, id) { if (!assets[id]) { // file does not exist in new status tree assets[id] = extend(true, {}, asset, { action: 'none' }); } }); } else { // mark all as 'created' _.forIn(data.assets, function (asset, id) { assets[id] = extend(true, {}, asset, { action: 'created' }); }); } return { assets: assets, remote: true, time: data.time }; }).catch(function (err) { throw new Error('Unable to get asset library data. ' + err.message); }); } /* * Get status of assets in local filesystem */ function getLocalStatus(filters, status) { filters = extend(true, {}, {path: '', globs: ['**/*.*']}, filters); var opts = { cwd: filters.path }; return globby(filters.globs, opts).then(function (files) { if (files.length === 0) { throw new Error('No files found'); } return Promise.all(files.map(function (file) { // create sha1 and modified filelist var filename = path.resolve(opts.cwd, file); return fs.stat(filename).then(function(stats) { return sha(filename).then(function (sha) { return { filename: path.basename(file), path: path.join(filters.target, path.dirname(file), '/'), sha: sha, modified: stats.mtime.toISOString().replace(/T/, ' ').replace(/\..+/, '') }; }); }); })); }).then(function (assetList) { var assets = {}; if(status) { var tmpAssets = {}; // build tmp assets object assetList.forEach(function (asset) { tmpAssets[slash(path.join(asset.path, asset.filename))] = asset; }); // diff asset list with given status to detect updated and created assets _.forIn(tmpAssets, function (asset, id) { var sasset = status.assets[id]; // check if file exists in old status tree if (sasset) { if (asset.sha !== sasset.sha) { // mark as 'updated' assets[id] = extend(true, {}, sasset, asset, {action: 'updated'}); } else { assets[id] = extend(true, {}, sasset, asset, {action: 'none'}); } } else { // mark as 'created' assets[id] = extend(true, {}, asset, { action: 'created'}); } }); // diff given status with asset list to detect deleted assets _.forIn(status.assets, function (asset, id) { if (!tmpAssets[id]) { // file does not exist in new status tree. make sure that deletions are only considered once if(asset.action !== 'deleted') { assets[id] = extend(true, {}, asset, {action: 'deleted', modified: moment.utc().format("YYYY-MM-DD HH:mm:ss")}); } } }); } else { // mark all as 'created' assetList.forEach(function (asset) { assets[slash(path.join(asset.path, asset.filename))] = extend(true, {}, asset, { action: 'created'}); }); } return {assets: assets, remote: false}; }).catch(function (err) { events.emit('log', 'error', err); throw err; }); } /* * Create asset in local filesystem */ function createLocalAsset(meta, asset) { meta = sanitizeMeta(meta); var folder = path.resolve('', path.join(meta.cwd, path.relative(meta.target, asset.path))); var filename = path.join(folder, asset.filename); return fs.stat(folder).catch(function (err) { // create directory events.emit('log', 'info', 'Create folder: ' + folder); return fs.mkdir(folder); }).then(function () { // get asset from remote var options = auth(meta); options.uri = '/v1/screen/download/' + asset.id; options.method = 'GET'; options.encoding = null; if (meta.dryRun) { return Promise.resolve({ success: true }); } return rp(options).then(function(content) { return fs.writeFile(filename, content).then(function () { if(meta.update) { events.emit('log', 'info', 'Asset ' + path.join(asset.path, asset.filename) + ' has been updated locally'); } else { events.emit('log', 'info', 'Asset ' + path.join(asset.path, asset.filename) + ' has been created locally'); } return {success: true}; }); }).catch(function (err) { throw new Error('Unable to create asset. ' + err.message); }); }).catch(function (err) { events.emit('log', 'error', err); }); } /* * Create remote asset in frontify */ function createRemoteAsset(meta, asset) { meta = sanitizeMeta(meta); var folder = path.resolve('', path.join(meta.cwd, path.relative(meta.target, asset.path))); var filename = path.join(folder, asset.filename); return fs.readFile(filename, 'base64').then(function (content) { // create asset remote var options = auth(meta); options.uri = '/v1/assets/'; if (asset.id) { options.uri += asset.id; // update } options.method = 'POST'; options.body = { project_id: meta.project, encoding: 'base64', content: content, mimetype: mime.lookup(asset.filename), filename: path.basename(asset.filename), path: slash(asset.path) }; if (meta.dryRun) { return Promise.resolve(options.body); } return rp(options).then(function (data) { if(asset.id) { events.emit('log', 'info', 'Asset ' + path.join(asset.path, asset.filename) + ' has been updated'); } else { events.emit('log', 'info', 'Asset ' + path.join(asset.path, asset.filename) + ' has been created'); } return data; }).catch(function (err) { throw new Error('Unable to create/update asset. ' + err.message); }); }); } /* * Update asset in local filesystem */ function updateLocalAsset(meta, asset) { meta = sanitizeMeta(meta); meta.update = true; return createLocalAsset(meta, asset); } /* * Update remote asset in frontify */ function updateRemoteAsset(meta, asset) { meta = sanitizeMeta(meta); if (!asset.id) { return Promise.resolve().then(function () { events.emit('log', 'error', 'Unable to update asset ' + asset.filename + '. Please provide an asset id'); }); } else { return createRemoteAsset(meta, asset); } } /* * Delete asset in local filesystem */ function deleteLocalAsset(meta, asset) { meta = sanitizeMeta(meta); var folder = path.resolve('', path.join(meta.cwd, path.relative(meta.target, asset.path))); var filename = path.join(folder, asset.filename); return del(filename).then(function () { events.emit('log', 'info', 'Asset ' + path.join(asset.path, asset.filename) + ' has been deleted locally'); return {success: true}; }).catch(function (err) { events.emit('log', 'error', err); }); } /* * Delete remote asset in frontify */ function deleteRemoteAsset(meta, asset) { if (!asset.id) { return Promise.resolve().then(function () { events.emit('log', 'error', 'Unable to delete asset ' + asset.filename + '. Please provide an asset id'); }); } else { meta = sanitizeMeta(meta); // create asset remote var options = auth(meta); options.uri = '/v1/assets/' + asset.id; options.method = 'DELETE'; if (meta.dryRun) { return Promise.resolve({success: true}); } return rp(options).then(function (data) { events.emit('log', 'info', 'Asset ' + path.join(asset.path, asset.filename) + ' has been deleted'); return data; }).catch(function (err) { throw new Error('Unable to delete asset. ' + err.message); }); } } /* * Sanitize meta options */ function sanitizeMeta(meta) { meta = extend(true, {}, { cwd: '', clean: false, target: '/'}, meta); meta.target = slash(path.join('/', meta.target)); return meta; } module.exports = { createLocalAsset: createLocalAsset, updateLocalAsset: updateLocalAsset, deleteLocalAsset: deleteLocalAsset, getLocalStatus: getLocalStatus, createRemoteAsset: createRemoteAsset, updateRemoteAsset: updateRemoteAsset, deleteRemoteAsset: deleteRemoteAsset, getRemoteStatus: getRemoteStatus, syncAssets: syncAssets, sanitizeMeta: sanitizeMeta, patch: patch, diff: diff };