now-sync
Version:
A tool to help developers sync their JavaScript resources with ServiceNow.
182 lines (160 loc) • 5.28 kB
JavaScript
const _ = require('lodash');
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const Promise = require('bluebird');
const { parseConfigFile, saveConfigFile } = require('./config');
const {
compileFileName,
getFileNameFields,
trimCwd
} = require('./file-naming');
const {
convertServiceNowDatetimeToMoment,
getRecord
} = require('./service-now');
const writeFileAsync = Promise.promisify(fs.writeFile);
const utimesAsync = Promise.promisify(fs.utimes);
/**
* Creates a config entry and its local file to sync with a ServiceNow record.
*
* @param {string} table ServiceNow Table API Name
* @param {string} sysId Table Record sys_id value
*/
async function add(table, sysId) {
const config = parseConfigFile();
const tableConfig = config.config[table];
if (!tableConfig) {
throw new Error(
`Configuration for table \`${
table
}\` not found. Run \`now add:table\` to configure files for this table.`
);
}
const data = await getFieldValues(table, sysId);
const filesToWrite = generateFilesToWriteForRecord(table, data);
return writeFilesForTable(table, filesToWrite);
}
exports.add = add;
/**
* Creates an array of file data for a ServiceNow record.
*
* @param {string} table ServiceNow table’s API name
* @param {object} fieldValues A field:value hash representing a ServiceNow record
*
* @typedef {object} fileContentObj
* @property {string} contentField
* @property {string} fileName
* @property {string} fileContent
*
* @returns {fileContentObj[]} An array of file content objects
*/
function generateFilesToWriteForRecord(table, fieldValues) {
const config = parseConfigFile();
return _.map(
config.config[table].formats,
({ fileName: fileTemplate, contentField }) => ({
contentField,
fileName: compileFileName(fileTemplate, fieldValues),
fileContent: fieldValues[contentField],
fileMtime: convertServiceNowDatetimeToMoment(
fieldValues.sys_updated_on
).unix()
})
);
}
exports.generateFilesToWriteForRecord = generateFilesToWriteForRecord;
/**
* Generates an array of field names to be used for syncing a table record to local files.
*
* @param {string} table ServiceNow table’s API Name
* @returns {string[]} An array of the table’s field names
*/
function getFieldsToRetrieve(table) {
const config = parseConfigFile();
const recordFields = getFileNameFields(table);
const contentFields = _.map(
config.config[table].formats,
({ contentField }) => contentField
);
recordFields.push('sys_updated_on');
const fieldsToRetrieve = _.uniq(recordFields.concat(contentFields));
return _.map(fieldsToRetrieve, name => {
if (name === 'sys_scope') {
return 'sys_scope.scope';
}
if (name === 'web_service_definition') {
return 'web_service_definition.service_id';
}
return name;
});
}
exports.getFieldsToRetrieve = getFieldsToRetrieve;
/**
* Retrieves the field values for a specific record from the ServiceNow instance.
*
* @param {string} table ServiceNow table’s API Name
* @param {string} sysId The record sys_id
* @returns {promise} Promise resolving to an object with the ServiceNow record’s field values
*/
function getFieldValues(table, sysId) {
const fieldsToRetrieve = getFieldsToRetrieve(table);
return getRecord(table, sysId, fieldsToRetrieve);
}
exports.getFieldValues = getFieldValues;
/**
* Writes local files given a table name and file content.
*
* @typedef {object} fileContentObj
* @property {string} contentField
* @property {string} fileName
* @property {string} fileContent
*
* @param {string} table ServiceNow table’s API Name
* @param {fileContentObj[]} filesToWrite Generated by generateFilesToWriteForRecord()
* @returns {Promise.<string[]>} A promise resolving to an array of file path strings written
*/
async function writeFilesForTable(table, filesToWrite) {
const config = parseConfigFile();
const writePath = path.resolve(process.cwd(), config.filePath, table);
const writeFilePromises = [];
const filesWritten = [];
if (!fs.existsSync(writePath)) {
mkdirp.sync(writePath);
}
if (!config.records[table]) {
config.records[table] = [];
}
_.forEach(
filesToWrite,
({ contentField, fileName, fileContent, fileMtime }) => {
const filePath = path.resolve(writePath, fileName);
const formattedFileContent = fileContent.replace(
new RegExp('\r\n', 'g'), // eslint-disable-line no-control-regex
'\n'
);
writeFilePromises.push(
writeFileAsync(filePath, formattedFileContent)
.then(() => utimesAsync(filePath, fileMtime, fileMtime))
.then(() => {
filesWritten.push(trimCwd(filePath));
})
);
const recordConfigAlreadyExists = _.find(
config.records[table],
({ fileName: configRecordFileName }) =>
fileName === configRecordFileName
);
if (!recordConfigAlreadyExists) {
config.records[table].push({
contentField,
fileName
});
}
}
);
saveConfigFile(config);
await Promise.all(writeFilePromises);
return filesWritten;
}
exports.writeFilesForTable = writeFilesForTable;