now-sync
Version:
A tool to help developers sync their JavaScript resources with ServiceNow.
113 lines (93 loc) • 3.43 kB
JavaScript
const _ = require('lodash');
const chokidar = require('chokidar');
const fs = require('fs');
const path = require('path');
const Promise = require('bluebird');
const { getFieldValuesFromFileName } = require('./file-naming');
const { parseConfigFile, saveConfigFile } = require('./config');
const { updateRecord } = require('./service-now');
const readFileAsync = Promise.promisify(fs.readFile);
/**
* Watches the configured directory for any file changes and updates the related ServiceNow record.
*
* When a file changes, the sync’d ServiceNow record’s field is updated with the file contents.
* When a file is deleted, the file is removed from the .now-sync.yml configuration file.
*
* @returns {chokidar} A chokidar watcher instance
*/
function watch(cb) {
const config = parseConfigFile();
const dir = path.resolve(process.cwd(), config.filePath);
const watcher = chokidar.watch(dir, {
awaitWriteFinish: true,
ignored: /(^|[/\\])\../, // dotfiles
});
watcher.on('ready', () => {
cb(null, 'ready');
});
// Used for storing the file’s mtime (last modified time) before changes are made.
// This is so we can compare whether the ServiceNow record was updated before the
// user made their latest change.
const fileStats = {};
watcher.on('add', (watchPath, stats) => {
fileStats[watchPath] = stats;
cb(null, 'add', stats);
});
watcher.on('change', async (watchPath, stats) => {
const latestConfig = parseConfigFile();
const relativePath = watchPath.substr(dir.length + 1).split(path.sep);
const table = relativePath[0];
const file = relativePath[1];
const fileConfig = _.find(
latestConfig.records[table],
(record) => record.fileName === file
);
if (!fileConfig) {
cb(
new Error(
`Could not find a file configuration matching table record on ${table}: ${file}. Make sure that configuration exists in your .now-sync.yml file. If it does not exist, run \`now add\` to add the file configuration.`
)
);
return;
}
const { contentField } = fileConfig;
const { fileName: fileTemplate } = _.find(
latestConfig.config[table].formats,
(format) => format.contentField === contentField
);
const fieldValues = getFieldValuesFromFileName(file, fileTemplate);
const fileContents = await readFileAsync(watchPath, 'utf8');
const uploadBody = {};
uploadBody[contentField] = fileContents;
const updateRecordResponse = await updateRecord(
table,
fieldValues.sys_id,
uploadBody
);
fileStats[watchPath] = stats;
cb(null, 'change', stats, {
table,
sysId: fieldValues.sys_id,
response: updateRecordResponse,
});
});
watcher.on('unlink', (watchPath) => {
delete fileStats[watchPath];
const latestConfig = parseConfigFile();
const relativePath = watchPath.substr(dir.length + 1).split(path.sep);
const table = relativePath[0];
const file = relativePath[1];
const configRecords = latestConfig.records[table];
const configRecordIndex = _.findIndex(
configRecords,
(record) => record.fileName === file
);
if (configRecordIndex > -1) {
configRecords.splice(configRecordIndex, 1);
saveConfigFile(latestConfig);
cb(null, 'unlink', null, { path: watchPath, table, file });
}
});
return watcher;
}
exports.watch = watch;