mirror of
https://github.com/LukeHagar/api-specs.git
synced 2025-12-06 12:27:48 +00:00
507 lines
18 KiB
JavaScript
507 lines
18 KiB
JavaScript
// Deploy collection inncrementally
|
|
// ---------------------------------------------------------------
|
|
// Cycle through the collection objects and deploy them one by one
|
|
// ---------------------------------------------------------------
|
|
// Folders -> Requests -> Responses
|
|
// ---------------------------------------------------------------
|
|
// An updated object has a different id.
|
|
// If the id matches, then the object is unchanged.
|
|
// If the id does not match, then the object is new.
|
|
// In the end delete all objects that are not in the updated collection.
|
|
// Sort the objects and update the parent order property.
|
|
// ---------------------------------------------------------------
|
|
// Except for folders which would force the update of the entire collection.
|
|
// ---------------------------------------------------------------
|
|
|
|
const pmConvert = require('./PostmanCovertions')
|
|
const pmAPI = require('./postmanAPI')
|
|
var crypto = require('crypto');
|
|
const { GenID } = require('./Utils')
|
|
|
|
const deployIncremental = async (privateRemoteCollectionId, localCollection, publicRemoteCollectionId) => {
|
|
let remoteCollection = await refreshRemoteCollection(privateRemoteCollectionId)
|
|
|
|
console.log('Incremental deployment of collection ', localCollection.info.name)
|
|
|
|
// const collectioHeadHasChanged =
|
|
await upadteCollectionHead(remoteCollection, localCollection)
|
|
|
|
remoteCollection = await refreshRemoteCollection(privateRemoteCollectionId)
|
|
|
|
// const foldersHaveChanged =
|
|
await mergeFolders(remoteCollection, localCollection)
|
|
|
|
remoteCollection = await refreshRemoteCollection(privateRemoteCollectionId)
|
|
|
|
// const requestsHaveChanged =
|
|
await mergeRequests(remoteCollection, localCollection)
|
|
|
|
remoteCollection = await refreshRemoteCollection(privateRemoteCollectionId)
|
|
|
|
// const responsesHaveChanged =
|
|
await mergeResponses(remoteCollection, localCollection)
|
|
|
|
// should we always merge into the public collection?
|
|
// There is teh case that if an error happens in the merge phase
|
|
// the private collection is fully updated
|
|
// and in the next run the public collection will NOT be updated
|
|
// because there are no changes in the private collection
|
|
|
|
// if (!(collectioHeadHasChanged || foldersHaveChanged || requestsHaveChanged || responsesHaveChanged)) {
|
|
// console.log('Incremental deployment of collection ', localCollection.info.name, ' completed\n\n')
|
|
// return
|
|
// }
|
|
const msg = 'Merging to public collection'
|
|
console.log('\n' + msg + '...')
|
|
await new pmAPI.Collection(privateRemoteCollectionId).merge(publicRemoteCollectionId)
|
|
.then(() => { console.log(msg, '-> OK\n') })
|
|
.catch((error) => {
|
|
console.log(msg, '-> FAIL')
|
|
handlePostmanAPIError(error)
|
|
})
|
|
console.log('Incremental deployment of collection ', localCollection.info.name, ' completed\n\n')
|
|
}
|
|
|
|
async function upadteCollectionHead (remoteCollection, localCollection) {
|
|
// the colelction head shoul dbe updated if there are changes in
|
|
// Authorization
|
|
// Pre-request Script
|
|
// Tests
|
|
// Variables
|
|
|
|
const localEmptyCollection = { ...localCollection }
|
|
localEmptyCollection.item = []
|
|
|
|
// Check changes in info
|
|
const hasChangesInfo = checkInfoChanges(remoteCollection, localCollection)
|
|
|
|
// Check if there are changes in the Authorization
|
|
const hasChangesAuth = checkObjectChanges(remoteCollection.collection.auth, localEmptyCollection.auth)
|
|
|
|
// Check if there are changes in the Scripts (pre-request and tests)
|
|
const hasChangesPreRequestScript = checkScriptChanges('prerequest', remoteCollection, localEmptyCollection)
|
|
|
|
const hasChangesTestScript = checkScriptChanges('test', remoteCollection, localEmptyCollection)
|
|
|
|
// Check if there are changes in the Variables
|
|
const hasChangesVariables = checkVariableChanges(remoteCollection, localEmptyCollection)
|
|
|
|
const hasFolderSortChanges = checkFolderSortChanges(remoteCollection, localCollection)
|
|
|
|
const hasChanges = (
|
|
hasFolderSortChanges ||
|
|
hasChangesInfo ||
|
|
hasChangesAuth ||
|
|
hasChangesPreRequestScript ||
|
|
hasChangesTestScript ||
|
|
hasChangesVariables
|
|
)
|
|
|
|
if (hasChanges) {
|
|
const msg = 'Updating collection head'
|
|
console.log('\n' + msg + '...')
|
|
await new pmAPI.Collection(remoteCollection.collection.info.uid)
|
|
.update({ collection: localEmptyCollection })
|
|
.then(() => { console.log(msg, '-> OK\n') })
|
|
.catch((error) => {
|
|
console.log(msg, '-> FAIL')
|
|
handlePostmanAPIError(error)
|
|
})
|
|
}
|
|
return hasChanges
|
|
}
|
|
|
|
const checkFolderSortChanges = (remoteCollection, localCollection) => {
|
|
const remoteFolders = remoteCollection.collection.item
|
|
.map(folder => ({ id: folder.id }))
|
|
const localFolders = localCollection.item
|
|
.filter(folder => !folder.folder)
|
|
.map(folder => ({ id: folder.id }))
|
|
|
|
const remoteFoldersHash = GenID(JSON.stringify(remoteFolders))
|
|
const localFoldersHash = GenID(JSON.stringify(localFolders))
|
|
|
|
return remoteFoldersHash !== localFoldersHash
|
|
}
|
|
|
|
const checkInfoChanges = (remoteCollection, localEmptyCollection) => {
|
|
// collection info does not have a specific id
|
|
// so we need to generate a hash and compare them
|
|
// The hash is only beig generated for name, description and schema
|
|
|
|
const { name, description, schema } = remoteCollection.collection.info
|
|
const remoteInfo = { name, description, schema }
|
|
|
|
const { name: localName, description: localDescription, schema: localSchema } = localEmptyCollection.info
|
|
const localInfo = { name: localName, description: localDescription, schema: localSchema }
|
|
|
|
const remoteInfoHash = calculateHash(JSON.stringify(remoteInfo))
|
|
const localInfoHash = calculateHash(JSON.stringify(localInfo))
|
|
|
|
return remoteInfoHash !== localInfoHash
|
|
}
|
|
|
|
const checkObjectChanges = (remoteCollectionObject, localCollectionObject) => {
|
|
if (!remoteCollectionObject && !localCollectionObject) {
|
|
return false
|
|
}
|
|
if (!remoteCollectionObject || !localCollectionObject) {
|
|
return true
|
|
}
|
|
|
|
// certain object like auth do not have an id,
|
|
// so we need to generate on and compare them
|
|
const remoteCollectionAuthID = GenID(JSON.stringify(remoteCollectionObject))
|
|
const localCollectionAuthID = GenID(JSON.stringify(localCollectionObject))
|
|
return remoteCollectionAuthID !== localCollectionAuthID
|
|
}
|
|
|
|
const checkScriptChanges = (scriptType, remoteCollection, localCollection) => {
|
|
// RB 2020-10-20: The collection may be empty or have no events at all
|
|
let remoteScript = null
|
|
let localScript = null
|
|
if (remoteCollection.collection.event) {
|
|
remoteScript = remoteCollection.collection.event.find(event => event.listen === scriptType)
|
|
}
|
|
|
|
if (localCollection.event) {
|
|
localScript = localCollection.event.find(event => event.listen === scriptType)
|
|
}
|
|
|
|
// const remoteScript = remoteCollection.collection.event.find(event => event.listen === scriptType)
|
|
// const localScript = localCollection.event.find(event => event.listen === scriptType)
|
|
|
|
if (!remoteScript && !localScript) {
|
|
return false
|
|
}
|
|
if (!remoteScript || !localScript) {
|
|
return true
|
|
}
|
|
// files can be big, so we hash them
|
|
const remoteHash = calculateHash(remoteScript.script.exec[0])
|
|
const localHash = calculateHash(localScript.script.exec[0])
|
|
|
|
return remoteHash !== localHash
|
|
}
|
|
|
|
const checkVariableChanges = (remoteCollection, localCollection) => {
|
|
const remoteVariables = remoteCollection.collection.variable
|
|
const localVariables = localCollection.variable.map(variable => ({ key: variable.key, value: variable.value }))
|
|
|
|
// check if null
|
|
if (!remoteVariables && !localVariables) {
|
|
return false
|
|
}
|
|
if (!remoteVariables || !localVariables) {
|
|
return true
|
|
}
|
|
|
|
// although the local collection does have a deterministic id
|
|
// the remote variable looses that value when it is updated
|
|
// so we need to generate an id for the remote variable
|
|
|
|
const remoteVariablesHash = GenID(remoteVariables)
|
|
const localVariablesHash = GenID(localVariables)
|
|
|
|
return remoteVariablesHash !== localVariablesHash
|
|
}
|
|
|
|
async function mergeFolders (remoteCollection, localCollection) {
|
|
console.log(' Deploying Folders:')
|
|
|
|
const remoteFolders = getAllFoldersFromCollectionItem(remoteCollection.collection.item)
|
|
const localFolders = localCollection.item // all folders
|
|
|
|
const newFolders = localFolders.filter(localFolder => !remoteFolders.find(remoteFolder => remoteFolder.id === localFolder.id))
|
|
const oldFolders = remoteFolders.filter(remoteFolder => !localFolders.find(localFolder => localFolder.id === remoteFolder.id))
|
|
|
|
let hasChanges = newFolders.length > 0 || oldFolders.length > 0
|
|
|
|
if (!hasChanges) {
|
|
console.log(' -> No changes')
|
|
return hasChanges
|
|
}
|
|
|
|
// create new folders
|
|
for (const folder of newFolders) {
|
|
const msg = ` Creating new folder [${folder.name}]`
|
|
await new pmAPI.Folder(remoteCollection.collection.info.uid)
|
|
.create(folder)
|
|
.then(() => {
|
|
hasChanges = true
|
|
console.log(msg, '-> OK')
|
|
})
|
|
.catch((error) => {
|
|
console.log(msg, '-> FAIL')
|
|
handlePostmanAPIError(error)
|
|
})
|
|
}
|
|
|
|
// delete old folders
|
|
for (const folder of oldFolders) {
|
|
const msg = ` Deleting old folder [${folder.name}]`
|
|
await new pmAPI.Folder(remoteCollection.collection.info.uid)
|
|
.delete(folder.id)
|
|
.then(() => {
|
|
hasChanges = true
|
|
console.log(msg, '-> OK')
|
|
})
|
|
.catch((error) => {
|
|
console.log(msg, '-> FAIL')
|
|
handlePostmanAPIError(error)
|
|
})
|
|
}
|
|
|
|
// sort folders is not supported for now
|
|
// const order = localFolders.map(folder => folder.id)
|
|
// const msg = ' Sorting folders'
|
|
|
|
// // create a temporsary root folder
|
|
// const rootFolder = await new pmAPI.Folder(remoteCollection.collection.info.uid)
|
|
// .create({ id: GenID(), name: 'root', folders: order })
|
|
// .catch((error) => {
|
|
// console.log(msg, '-> FAIL')
|
|
// handlePostmanAPIError(error)
|
|
// })
|
|
// console.log('root folder', rootFolder)
|
|
// // move all remote folders into root folder
|
|
|
|
return hasChanges
|
|
}
|
|
|
|
async function mergeRequests (remoteCollection, localCollection) {
|
|
const remoteFolders = getAllFoldersFromCollectionItem(remoteCollection.collection.item)
|
|
const localFolders = localCollection.item // all folders
|
|
|
|
console.log('\n Deploying Requests:')
|
|
let anyRequestHasChanged = false
|
|
|
|
// loop folders
|
|
for (const localFolder of localFolders) {
|
|
const remoteRemoteFolder = remoteFolders.find(remoteFolder => ((remoteFolder.id === localFolder.id)))
|
|
|
|
// TODO: RB: get requests by folder
|
|
// handle undefined items
|
|
remoteRemoteFolder.item = remoteRemoteFolder.item || []
|
|
|
|
// filter out anything that is not a request
|
|
remoteRemoteFolder.item = remoteRemoteFolder.item.filter(request => request.request)
|
|
|
|
const remoteRequests = remoteRemoteFolder.item
|
|
const localRequests = localFolder.item
|
|
|
|
// Identify old and new requests
|
|
const newRequests = localRequests.filter(localRequest => !remoteRequests.find(remoteRequest => remoteRequest.id === localRequest.id))
|
|
const oldRequests = remoteRequests.filter(remoteRequest => !localRequests.find(localRequest => localRequest.id === remoteRequest.id))
|
|
|
|
const requestsInFolderHaveChanges = newRequests.length > 0 || oldRequests.length > 0
|
|
|
|
if (!requestsInFolderHaveChanges) {
|
|
console.log(' In Folder: ', localFolder.name, '-> No changes')
|
|
continue
|
|
}
|
|
console.log(' In Folder: ', localFolder.name)
|
|
|
|
// create new requests
|
|
for (const request of newRequests) {
|
|
// check request format and convert if necessary
|
|
let pmRequest = null
|
|
if (!request.request) { // => Postman Format
|
|
pmRequest = request
|
|
} else { // => OpenAPI Format
|
|
pmRequest = pmConvert.requestFromLocal(request)
|
|
}
|
|
const msg = ` Creating new request [${request.name}]`
|
|
|
|
await new pmAPI.Request(remoteCollection.collection.info.uid)
|
|
.create(pmRequest, localFolder.id)
|
|
.then((req) => {
|
|
console.log(msg, '-> OK')
|
|
return req
|
|
})
|
|
.catch((error) => {
|
|
console.log(msg, '-> FAIL')
|
|
handlePostmanAPIError(error)
|
|
})
|
|
// console.log('\nequest', request)
|
|
// console.log('\npmRequest', pmRequest)
|
|
// console.log('\nremoteRequest', remoteRequest)
|
|
}
|
|
|
|
// delete old requests
|
|
for (const request of oldRequests) {
|
|
const msg = ` Deleting old request [${request.name}]`
|
|
await new pmAPI.Request(remoteCollection.collection.info.uid)
|
|
.delete(request.id)
|
|
.then(() => {
|
|
console.log(msg, '-> OK')
|
|
})
|
|
.catch((error) => {
|
|
console.log(msg, '-> FAIL')
|
|
handlePostmanAPIError(error)
|
|
})
|
|
}
|
|
|
|
if (requestsInFolderHaveChanges) {
|
|
// sort requests in folder
|
|
const order = localRequests.map(request => request.id)
|
|
const msg = ` Sorting requests in folder [${localFolder.name}]`
|
|
await new pmAPI.Folder(remoteCollection.collection.info.uid)
|
|
.update(localFolder.id, { order })
|
|
.then(() => { console.log(msg, '-> OK') })
|
|
.catch((error) => {
|
|
console.log(msg, '-> FAIL')
|
|
handlePostmanAPIError(error)
|
|
})
|
|
}
|
|
anyRequestHasChanged = anyRequestHasChanged || requestsInFolderHaveChanges
|
|
}
|
|
return anyRequestHasChanged
|
|
}
|
|
|
|
async function mergeResponses (remoteCollection, localCollection) {
|
|
console.log('\n Deploying Response:')
|
|
const remoteFolders = getAllFoldersFromCollectionItem(remoteCollection.collection.item)
|
|
const localFolders = localCollection.item
|
|
|
|
let anyResponseHasChanged = false
|
|
// loop folders
|
|
for (const localFolder of localFolders) {
|
|
const remoteRemoteFolder = remoteFolders.find(remoteFolder => ((remoteFolder.id === localFolder.id)))
|
|
|
|
// handle undefined items
|
|
remoteRemoteFolder.item = remoteRemoteFolder.item || []
|
|
|
|
// filter out anything that is not a request
|
|
remoteRemoteFolder.item = remoteRemoteFolder.item.filter(request => request.request)
|
|
|
|
const remoteRequests = remoteRemoteFolder.item
|
|
const localRequests = localFolder.item
|
|
|
|
console.log(' In Folder: ', localFolder.name)
|
|
// loop requests
|
|
for (const localRequest of localRequests) {
|
|
// Postman Request format does not have a response property
|
|
const remoteResponses = remoteRequests.find(remoteRequest => remoteRequest.id === localRequest.id).response
|
|
const localResponses = localRequest.response
|
|
// the request may not have responses
|
|
if (!localResponses) {
|
|
continue
|
|
}
|
|
|
|
const newResponses = localResponses.filter(localResponse => !remoteResponses.find(remoteResponse => remoteResponse.id === localResponse.id))
|
|
const oldResponses = remoteResponses.filter(remoteResponse => !localResponses.find(localResponse => localResponse.id === remoteResponse.id))
|
|
|
|
const ResponsesInReqquestHaveChanges = newResponses.length > 0 || oldResponses.length > 0
|
|
if (!ResponsesInReqquestHaveChanges) {
|
|
console.log(' In Request: ', localRequest.name, '-> No changes')
|
|
continue
|
|
}
|
|
console.log(' In Request: ', localRequest.name)
|
|
|
|
// create new responses
|
|
for (const response of newResponses) {
|
|
const pmResponse = pmConvert.responseFromLocal(response)
|
|
const msg = ` Creating new response [${response.code} ${response.status}]`
|
|
await new pmAPI.Response(remoteCollection.collection.info.uid)
|
|
.create(pmResponse, localRequest.id)
|
|
.then(() => {
|
|
console.log(msg, '-> OK')
|
|
})
|
|
.catch((error) => {
|
|
console.log(msg, '-> FAIL')
|
|
handlePostmanAPIError(error)
|
|
})
|
|
}
|
|
|
|
// delete old responses
|
|
for (const response of oldResponses) {
|
|
const msg = ` Deleting old response [${response.code} ${response.status}]`
|
|
await new pmAPI.Response(remoteCollection.collection.info.uid)
|
|
.delete(response.id)
|
|
.then(() => {
|
|
console.log(msg, '-> OK')
|
|
})
|
|
.catch((error) => {
|
|
console.log(msg, '-> FAIL')
|
|
handlePostmanAPIError(error)
|
|
})
|
|
}
|
|
|
|
// updating the requests with the order of the responses, doesn't seem to be necessary
|
|
if (ResponsesInReqquestHaveChanges) {
|
|
// sort responses in requests
|
|
const responsesOrder = localResponses.map(response => response.id)
|
|
const msg = ` Sorting responses in request [${localRequest.name}]`
|
|
await new pmAPI.Request(remoteCollection.collection.info._postman_id)
|
|
.update(localRequest.id,
|
|
{
|
|
responses_order: responsesOrder
|
|
})
|
|
.then(() => { console.log(msg, '-> OK') })
|
|
.catch((error) => {
|
|
console.log(msg, '-> FAIL')
|
|
handlePostmanAPIError(error)
|
|
})
|
|
}
|
|
anyResponseHasChanged = anyResponseHasChanged || ResponsesInReqquestHaveChanges
|
|
}
|
|
}
|
|
return anyResponseHasChanged
|
|
}
|
|
|
|
async function refreshRemoteCollection (remoteCollectionID) {
|
|
const msg = 'Refreshing remote collection'
|
|
console.log('\n' + msg + '...\n')
|
|
const remoteCollection = await new pmAPI.Collection(remoteCollectionID).get()
|
|
.catch((error) => {
|
|
console.log(msg, '-> FAIL')
|
|
handlePostmanAPIError(error)
|
|
})
|
|
return remoteCollection
|
|
}
|
|
|
|
// return all folders in the collection
|
|
// independently of where they are
|
|
const getAllFoldersFromCollectionItem = (collectionItem) => {
|
|
const folders = []
|
|
const processItem = (item) => {
|
|
if (!item.request && !item.responses) {
|
|
folders.push({ id: item.id, name: item.name, item: item.item })
|
|
}
|
|
if (item.item) {
|
|
item.item.forEach(processItem)
|
|
}
|
|
}
|
|
collectionItem.forEach(processItem)
|
|
return folders
|
|
}
|
|
|
|
// Handle axios error
|
|
const handlePostmanAPIError = (error) => {
|
|
if (error.response) {
|
|
// The request was made and the server responded with a status code
|
|
// that falls out of the range of 2xx
|
|
console.log('API ERROR:', error.response.data)
|
|
} else {
|
|
// The request was made but no response was received
|
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
|
// http.ClientRequest in node.js
|
|
console.log('NO RESPONSE:', error.message)
|
|
if (error.cause) {
|
|
console.log('CAUSE:', error.cause)
|
|
}
|
|
}
|
|
const { method, url, data } = error.config
|
|
const smallData = data.substring(0, 1000)
|
|
console.log('REQUEST DETAILS', { method, url, smallData })
|
|
process.exit(1)
|
|
}
|
|
|
|
const calculateHash = (stringToHash) => {
|
|
return crypto.createHash('sha256').update(stringToHash).digest('hex')
|
|
}
|
|
|
|
module.exports = {
|
|
deployIncremental
|
|
} |