diff --git a/README.md b/README.md index a3566a0..221ab90 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,34 @@ 1. `cd node-gitlab-2-github` 1. `npm i` +## Preliminaries + +Before using this script, you must mirror your GitLab repo to your new GitHub repo. This can be done with the following steps: + +```bash +# Clone the repo from GitLab using the `--mirror` option. This is like +# `--bare` but also copies all refs as-is. Useful for a full backup/move. +git clone --mirror git@your-gitlab-site.com:username/repo.git + +# Change into newly created repo directory +cd repo + +# Push to GitHub using the `--mirror` option. The `--no-verify` option skips any hooks. +git push --no-verify --mirror git@github.com:username/repo.git + +# Set push URL to the mirror location +git remote set-url --push origin git@github.com:username/repo.git + +# To periodically update the repo on GitHub with what you have in GitLab +git fetch -p origin +git push --no-verify --mirror +``` + +After doing this, the autolinking of issues/commits will work. See **Usage** for next steps. + ## Usage -Before using this script "copy" your gitlab repo with all the branches over to github. For this create a new repo with the infos (github.owner and github.repo) from the settings.js and push all the branches you need in the new repo. (Then will the autolinking of issues to commits work.) The user must be a member of the project you want to copy or else you won't see it in the first step. +The user must be a member of the project you want to copy or else you won't see it in the first step. 1. `cp sample_settings.js settings.js` 1. edit settings.js @@ -35,18 +60,21 @@ Leave it null for the first run of the script. Then the script will show you whi ### github -#### github.url +#### github.baseUrl Where is the github instance hosted? Default is the official `api.github.com` domain -#### github.pathPrefix - -Only needed when using github enterprise and not beeing hosted at the root of the domain - #### github.owner Under which organisation or user will the new project be hosted +#### github.username + +Go to your settings. Open the Access Token tab. Create a new Access Token and copy that into the `settings.js` + +#### github.token + +Go to [Settings / Developer settings / Personal access tokens](https://github.com/settings/tokens). Generate a new token and copy that into the `settings.js` #### github.repo diff --git a/index.js b/index.js index 3335d4d..3803b7f 100644 --- a/index.js +++ b/index.js @@ -1,500 +1,590 @@ -var GitHubApi = require('@octokit/rest') -var Gitlab = require('node-gitlab-api'); -var async = require('async'); +const GitHubApi = require('@octokit/rest') +const Gitlab = require('gitlab').default +const async = require('async'); -try{ +try { var settings = require('./settings.js'); -} -catch(e){ - if(e.code === 'MODULE_NOT_FOUND'){ - console.log('\n\nPlease copy the sample_settings.json to settings.json.'); - process.exit(1); - } - else{ +} catch(e) { + if (e.code === 'MODULE_NOT_FOUND') { + console.log('\n\nPlease copy the sample_settings.js to settings.js.'); + } else { console.log(e); - process.exit(1); } + + process.exit(1); } -console.log('\n\n\n'); - -if(!settings.gitlab.url || settings.gitlab.url === "http://gitlab.mycompany.com/"){ - console.log('\n\nYou have to enter your gitlab url in the settings.json file.'); +// Ensure that the GitLab URL and token has been set in settings.js +if (!settings.gitlab.url || settings.gitlab.url === "http://gitlab.mycompany.com/") { + console.log('\n\nYou have to enter your GitLab url in the settings.js file.'); process.exit(1); } -if(!settings.gitlab.token || settings.gitlab.token === "{{gitlab private token}}"){ - console.log('\n\nYou have to enter your gitlab private token in the settings.json file.'); +if (!settings.gitlab.token || settings.gitlab.token === "{{gitlab private token}}") { + console.log('\n\nYou have to enter your GitLab private token in the settings.js file.'); process.exit(1); } -var gitlab = Gitlab({ +// Create a GitLab API object +var gitlab = new Gitlab({ url: settings.gitlab.url, token: settings.gitlab.token }); +// Create a GitHub API object +var github = new GitHubApi({ + debug: false, + baseUrl: (settings.github.baseUrl?settings.github.baseUrl:"https://api.github.com"), + timeout: 5000, + headers: { + 'user-agent': 'node-gitlab-2-github', // GitHub is happy with a unique user agent + 'accept': 'application/vnd.github.v3+json', + } +}); + +// regex for converting user from GitLab to GitHub var userProjectRe = generateUserProjectRe(); +// If no project id is given in settings.js, just return +// all of the projects that this user is associated with. if (settings.gitlab.projectID === null) { - gitlab.projects.all({membership:true}) - .then(function(projects) { - projects = projects.sort(function(a, b) { - return a.id - b.id; - }); - for (var i = 0; i < projects.length; i++) { - console.log('projects:', projects[i].id, projects[i].description, projects[i].name); + listProjects(); +} else { + // user has chosen a project + migrate(); +} + +// ---------------------------------------------------------------------------- + +/** + * List all projects that the GitLab user is associated with. + */ +async function listProjects() { + try { + let projects = await gitlab.Projects.all({membership: true}); + + // print each project with info + for (let i = 0; i < projects.length; i++) { + console.log(projects[i].id.toString(), '\t', projects[i].name, '\t--\t', projects[i].description); } + + // instructions for user console.log('\n\n'); - console.log('Select which project ID should be transported to github. Edit the settings.json accordingly. (gitlab.projectID)'); + console.log('Select which project ID should be transported to github. Edit the settings.js accordingly. (gitlab.projectID)'); console.log('\n\n'); - }).catch(function(err){ - console.log('An Error occured while fetching all projects:'); - console.log(err); - }); -} else { - // user has choosen a project + } catch (err) { + console.error('An Error occured while fetching all projects:'); + console.error(err); + } +} + +// ---------------------------------------------------------------------------- + +/** + * Performs all of the migration tasks to move a GitLab repo to GitHub + */ +async function migrate() { - var github = new GitHubApi({ - debug: false, - baseUrl: (settings.github.baseUrl?settings.github.baseUrl:"https://api.github.com"), - timeout: 5000, - headers: { - "user-agent": "node-gitlab-2-github",// GitHub is happy with a unique user agent - 'accept': 'application/vnd.github.v3+json', - } - }); github.authenticate({ type: "basic", username: settings.github.username, password: settings.github.token }); - gitlab.projects.milestones.all(settings.gitlab.projectID).then(function(data) { - console.log('Amount of gitlab milestones', data.length); - data = data.sort(function(a, b) { - return a.id - b.id; - }); - getAllGHMilestones(function(milestoneDataA, milestoneDataMappedA) { - // for now use globals - milestoneData = milestoneDataA; - milestoneDataMapped = milestoneDataMappedA; - - console.log('\n\n\n\n\n\n\nMilestones>>>>'); - console.log(milestoneDataMapped); - console.log('\n\n\n\n\n\n\n'); - - async.each(data, function(item, cb) { - if (milestoneDataMapped.indexOf(item.title) < 0) { - console.log('Creating new Milestone', item.title); - createMilestone(item, function(err, createMilestoneData) { - console.log(createMilestoneData); - return cb(err); - }); - } else { - return cb(null); - } - }, function(err) { - if (err) return console.log(err); - // all milestones are created - getAllGHMilestones(function(milestoneDataA, milestoneDataMappedA) { - // create labels - gitlab.projects.labels.all(settings.gitlab.projectID).then(function(glLabels) { - getAllGHLabelNames(function(ghlabelNames) { - async.each(glLabels, function(glLabel, cb) { - if (ghlabelNames.indexOf(glLabel.name) < 0) { - console.log('Creating new Label', glLabel.name); - createLabel(glLabel, function(err, createLabelData) { - console.log(createLabelData); - return cb(err); - }); - } else { - return cb(null); - } - }, function(err) { - if (err) return console.log(err); - // all labels are created, create a hasAttachment label for manual attachment migration - var glLabel = { - name: 'hasAttachment', - color: '#fbca04' - } - createLabel(glLabel, function(err, createLabelData) { - console.log(createLabelData); - }); - - // for now use globals - milestoneData = milestoneDataA; - milestoneDataMapped = milestoneDataMappedA; - - createAllIssuesAndComments(milestoneData, function(err, data) { - console.log('\n\n\n\nFinished creating all issues and Comments\n\n\n\n'); - console.log('err and data:', err, data); - }); - }); //async - }); // getAllGHLabelNames - }).catch(function(err){ - console.log('An Error occured while loading all labels:'); - console.log(err); - }); // gitlab list labels - }); // getAllGHMilestones - }); // async - }) - }).catch(function(err){ - console.log('An Error occured while loading all milestones:'); - console.log(err); - }); // gitlab list milestones -} + // + // Sequentially transfer repo things + // + // transfer GitLab milestones to GitHub + await transferMilestones(settings.gitlab.projectId); -function createAllIssuesAndComments(milestoneData, callback) { - // select all issues and comments from this project - gitlab.projects.issues.all(settings.gitlab.projectID).then(function(issueData) { - // TODO return all issues via pagination - // look whether issue is already created - issueData = issueData.sort(function(a, b) { - return a.id - b.id; - }); - console.log('length Issue GitLab:', issueData.length); - - // loop through all issues and add placeholder issues if there are gaps - // this is to ensure issue id's are the same in gitlab and GitHub - var placeHolderItem = { - title: 'Place holder issue for issue which does not exist probably deleted in Gitlab', - description: 'This is to ensure the issue ids in Gitlab and GitHub are the same', - state: 'closed' - } - for (var iIssue = 0; iIssue < issueData.length; iIssue++) { - if (issueData[iIssue].iid != iIssue + 1) { - issueData.splice(iIssue, 0, placeHolderItem); - iIssue++; - console.log('Added placeholder item for missing issue with id:', iIssue + 1); - } - } + // transfer GitLab labels to GitHub + await transferLabels(settings.gitlab.projectId, true, settings.conversion.useLowerCaseLabels); - getAllGHIssues(function(err, ghIssues) { - if(err){ - console.log(err); - console.log('getAllGHIssues'); - console.log('FAIL!'); - process.exit(1); - } - ghIssuesMapped = ghIssues.map(function(item) { - return item.title; - }); - console.log('length Issue GitHub:', ghIssues.length); - - async.eachSeries(issueData, function(item, cb) { - if (item.milestone) { - var title = findMileStoneforTitle(milestoneData, item.milestone.title) - if (title !== null) { - console.log('title', title); - } - } - if (ghIssuesMapped.indexOf(item.title.trim()) < 0) { - console.log('Creating new Issue', item.title.trim()); - createIssueAndComments(item, function(err, createIssueData) { - console.log(createIssueData); - return cb(err); - }); - } else { - var ghIssue = ghIssues.filter(function(element, index, array) { - return element.title == item.title.trim(); - }); - return makeCorrectState(ghIssue[0], item, cb); - } - }, function(err) { - if (err) console.log('error with issueData:', err); - callback(err); - }); // each series - }); // getAllGHIssues - }).catch(function(err){ - console.log('An Error occured while fetching all issues:'); - console.log(err); - }); // gitlab project Issues + // Transfer issues with their comments + await transferIssues(settings.github.owner, settings.github.repo, settings.gitlab.projectId); + + console.log("\n\nTransfer complete!\n\n"); } -function getAllGHMilestones(callback) { - github.issues.getMilestones({ - owner: settings.github.owner, - repo: settings.github.repo, - state: 'all' - }, function(err, milestoneDataAll) { - if(err){ - console.log(err); - console.log('getAllGHMilestones1'); - console.log('FAIL!'); - process.exit(1); +// ---------------------------------------------------------------------------- + +/** + * Transfer any milestones that exist in GitLab that do not exist in GitHub. + */ +async function transferMilestones(projectId) { + inform("Transferring Milestones"); + + // Get a list of all milestones associated with this project + let milestones = await gitlab.ProjectMilestones.all(projectId); + + // sort milestones in ascending order of when they were created (by id) + milestones = milestones.sort((a, b) => a.id - b.id); + + // get a list of the current milestones in the new GitHub repo (likely to be empty) + let ghMilestones = await getAllGHMilestones(settings.github.owner, settings.github.repo); + + // if a GitLab milestone does not exist in GitHub repo, create it. + for (let milestone of milestones) { + if (!ghMilestones.find(m => m.title === milestone.title)) { + console.log("Creating: " + milestone.title); + try { + + // process asynchronous code in sequence + await createMilestone(settings.github.owner, settings.github.repo, milestone); + + } catch (err) { + console.error("Could not create milestone", milestone.title); + console.error(err); + } + } else { + console.log("Already exists: " + milestone.title); } - milestoneData = milestoneDataAll.data.map(function(item) { - return { - number: item.number, - title: item.title - }; - }); - milestoneDataMapped = milestoneData.map(function(item) { - return item.title; - }); - return callback(milestoneData, milestoneDataMapped); - - }); // Milestones + }; } -function getAllGHIssues(callback) { - var lastItem = null; - var curPage = 1; - var allGhIssues = []; - async.whilst(function() { - return hasNext(lastItem) - }, function(cb) { - github.issues.getForRepo({ - owner: settings.github.owner, - repo: settings.github.repo, - state: 'all', - per_page: 100, - page: curPage - }, function(err, ghIssues) { - if(err){ - console.log(err); - console.log('getAllGHIssues'); - console.log('FAIL!'); - process.exit(1); +// ---------------------------------------------------------------------------- + +/** + * Transfer any labels that exist in GitLab that do not exist in GitHub. + */ +async function transferLabels(projectId, attachmentLabel = true, useLowerCase = true) { + inform("Transferring Labels"); + + // Get a list of all labels associated with this project + let labels = await gitlab.Labels.all(projectId); + + // get a list of the current label names in the new GitHub repo (likely to be just the defaults) + let ghLabels = await getAllGHLabelNames(settings.github.owner, settings.github.repo); + + // create a hasAttachment label for manual attachment migration + if (attachmentLabel) { + const hasAttachmentLabel = {name: 'has attachment', color: '#fbca04'}; + labels.push(hasAttachmentLabel); + } + + // if a GitLab label does not exist in GitHub repo, create it. + for (let label of labels) { + + // GitHub prefers lowercase label names + if (useLowerCase) { + label.name = label.name.toLowerCase() } - console.log('got page', curPage, 'with', ghIssues.data.length, 'entries'); - console.log('\n\n\n'); - console.log('ghIssues.meta', ghIssues.meta); - - curPage++; - lastItem = ghIssues; - var l = ghIssues.data.length; - for (var i = 0; i < l; i++) { - allGhIssues[allGhIssues.length] = ghIssues.data[i]; + + if (!ghLabels.find(l => l === label.name)) { + console.log("Creating: " + label.name); + try { + + // process asynchronous code in sequence + await createLabel(settings.github.owner, settings.github.repo, label).catch(x=>{}); + + } catch (err) { + console.error("Could not create label", label.name); + console.error(err); + } + } else { + console.log("Already exists: " + label.name); } - cb(err); - }); // gh repo Issues - }, function(err) { - console.log('issue Count on GH:', allGhIssues.length) - callback(err, allGhIssues); - }); // async whilst + }; } -function getAllGHLabelNames(callback) { - github.issues.getLabels({ - owner: settings.github.owner, - repo: settings.github.repo, - per_page: 100 - }, function(err, labelData) { - if (err){ - console.log(err); - console.log('getAllGHLabelNames'); - console.log('FAIL!'); - process.exit(1); +// ---------------------------------------------------------------------------- + +/** + * Transfer any issues and their comments that exist in GitLab that do not exist in GitHub. + */ +async function transferIssues(owner, repo, projectId) { + inform("Transferring Issues"); + + // Because each + let milestoneData = await getAllGHMilestones(owner, repo); + + // get a list of all GitLab issues associated with this project + // TODO return all issues via pagination + let issues = await gitlab.Issues.all({projectId: projectId}); + + // sort issues in ascending order of when they were created (by id) + issues = issues.sort((a, b) => a.id - b.id); + + // get a list of the current issues in the new GitHub repo (likely to be empty) + let ghIssues = await getAllGHIssues(settings.github.owner, settings.github.repo); + + console.log("Transferring " + issues.length.toString() + " issues"); + + // + // Create Placeholder Issues + // + + // Create placeholder issues so that new GitHub issues will have the same + // issue number as in GitLab. If a placeholder is used it is because there + // was a gap in GitLab issues -- likely caused by a deleted GitLab issue. + const placeholderItem = { + title: 'placeholder issue for issue which does not exist and was probably deleted in GitLab', + description: 'This is to ensure the issue numbers in GitLab and GitHub are the same', + state: 'closed' + } + + for (let i=0; i i.title.trim() === issue.title.trim()); + if (!ghIssue) { + console.log("Creating: " + issue.iid + " - " + issue.title); + try { + + // process asynchronous code in sequence -- treats the code sort of like blocking + await createIssueAndComments(settings.github.owner, settings.github.repo, milestoneData, issue); + + } catch (err) { + console.error("Could not create issue: " + issue.iid + " - " + issue.title); + console.error(err); + } + } else { + console.log("Already exists: " + issue.iid + " - " + issue.title); + updateIssueState(ghIssue, issue); + } + }; } -function hasNext(item) { - if (item === null) { - return true; - } else if (item.meta.link == undefined || item.meta.link.indexOf('next') < 0) { - return false - } else { - return true - } +// ---------------------------------------------------------------------------- +/** + * Get a list of all GitHub milestones currently in new repo + */ +async function getAllGHMilestones(owner, repo) { + try { + // get an array of GitHub milestones for the new repo + let result = await github.issues.getMilestones({owner: owner, repo: repo, state: 'all'}); + + // extract the milestone number and title and put into a new array + let milestones = result.data.map(x => ({number: x.number, title: x.title})); + + return milestones; + } catch (err) { + console.error("Could not access all GitHub milestones"); + console.error(err); + process.exit(1); + } } +// ---------------------------------------------------------------------------- -function findMileStoneforTitle(milestoneData, title) { - for (var i = milestoneData.length - 1; i >= 0; i--) { - if (milestoneData[i].title == title) { - console.log('findMileStoneforTitle', milestoneData[i].number); - return milestoneData[i].number; - } +/** + * Get a list of all GitHub label names currently in new repo + */ +async function getAllGHLabelNames(owner, repo) { + try { + // get an array of GitHub labels for the new repo + let result = await github.issues.getLabels({owner: owner, repo: repo, per_page: 100}); + + // extract the label name and put into a new array + let labels = result.data.map(x => x.name); + + return labels; + } catch (err) { + console.error("Could not access all GitHub label names"); + console.error(err); + process.exit(1); } - return null; } -function createIssueAndComments(item, callback) { - var props = null; - convertIssuesAndComments(item.description, item, function(bodyConverted) { - props = { - owner: settings.github.owner, - repo: settings.github.repo, - title: item.title.trim(), - body: bodyConverted - }; - }); - if (item.assignee) { - if (item.assignee.username == settings.github.username) { - props.assignee = item.assignee.username; - } else if (settings.usermap && settings.usermap[item.assignee.username]) { - // get github username name from config - props.assignee = settings.usermap[item.assignee.username]; - } +// ---------------------------------------------------------------------------- + +/** + * Get a list of all the current GitHub issues. + * This uses a while loop to make sure that each page of issues is received. + */ +async function getAllGHIssues(owner, repo) { + let allIssues = [] + let page = 1; + const perPage = 100; + + while (true) { + // get a paginated list of issues + const issues = await github.issues.getForRepo({owner: owner, repo: repo, state: 'all', per_page: perPage, page: page }); + + // if this page has zero issues then we are done! + if (issues.data.length === 0) + break; + + // join this list of issues with the master list + allIssues = allIssues.concat(issues.data); + + // if there are strictly less issues on this page than the maximum number per page + // then we can be sure that this is all the issues. No use querying again. + if (issues.data.length < perPage) + break; + + // query for the next page of issues next iteration + page++; } - if (item.milestone) { - var title = findMileStoneforTitle(milestoneData, item.milestone.title) - if (title !== null) { - props.milestone = title; - } else { - // TODO also import issues where milestone got deleted - // return callback(); + return allIssues; +} + +// ---------------------------------------------------------------------------- + +/** + * + */ +async function createIssueAndComments(owner, repo, milestones, issue) { + + // create the issue in GitHub + let ghIssueData = await createIssue(owner, repo, milestones, issue); + let ghIssue = ghIssueData.data; + + // add any comments/notes associated with this issue + await createIssueComments(ghIssue, issue); + + // make sure to close the GitHub issue if it is closed in GitLab + await updateIssueState(ghIssue, issue); +} + +// ---------------------------------------------------------------------------- + +/** + * + */ +async function createIssue(owner, repo, milestones, issue) { + let bodyConverted = convertIssuesAndComments(issue.description, issue); + + let props = { + owner: owner, + repo: repo, + title: issue.title.trim(), + body: bodyConverted + }; + + // + // Issue Assignee + // + + // If the GitLab issue has an assignee, make sure to carry it over -- but only + // if the username is a valid GitHub username. + if (issue.assignee) { + props.assignees = []; + if (issue.assignee.username == settings.github.username) { + props.assignees.push(settings.github.username); + } else if (settings.usermap && settings.usermap[issue.assignee.username]) { + // get GitHub username name from settings + props.assignees.push(settings.usermap[item.assignee.username]); } } - if (item.labels) { - props.labels = item.labels; - // add hasAttachment label if body contains an attachment for manual migration - if (props.body && props.body.indexOf('/uploads/') > -1) { - props.labels.push('hasAttachment'); + // + // Issue Milestone + // + + // if the GitLab issue has an associated milestone, make sure to attach it. + if (issue.milestone) { + let milestone = milestones.find(m => m.title === issue.milestone.title); + if (milestone) { + props.milestone = milestone.number; } } - console.log('props', props); - github.issues.create(props, function(err, newIssueData) { - if (!err) { - createAllIssueComments(settings.gitlab.projectID, item.iid, newIssueData.data, function(err, issueData) { - makeCorrectState(newIssueData.data, item, callback); - }); - } else { - console.log('errData', err, newIssueData); - return callback(err); - } - }); -} + // + // Issue Labels + // -function makeCorrectState(ghIssueData, item, callback) { - if (item.state != 'closed' || ghIssueData.state == 'closed') { - // standard is open so we don't have to update - return callback(null, ghIssueData); + // make sure to add any labels that existed in GitLab + if (issue.labels) { + props.labels = issue.labels; } - // TODO get props - var props = { - owner: settings.github.owner, - repo: settings.github.repo, - number: ghIssueData.number, - state: 'closed', - }; - if (item.milestone) { - var title = findMileStoneforTitle(milestoneData, item.milestone.title); - if (title !== null) { - props.milestone = title; - } + // + // Issue Attachments + // + + // if the issue contains a url that contains "/uploads/", it is likely to + // have an attachment. Therefore, add the "has attachment" label. + if (props.body && props.body.indexOf('/uploads/') > -1) { + props.labels.push('has attachment'); } - console.log('makeCorrectState', ghIssueData.number, item.state, props.milestone); - console.log('makeCorrectState props', props); - github.issues.edit(props, callback); + // create the GitHub issue from the GitLab issue + return github.issues.create(props); } -function createAllIssueComments(projectID, issueID, newIssueData, callback) { - if (issueID == null) { - return callback(); +// ---------------------------------------------------------------------------- + +/** + * + */ +async function createIssueComments(ghIssue, issue) { + // retrieve any notes/comments associated with this issue + try { + let notes = await gitlab.IssueNotes.all(settings.gitlab.projectId, issue.iid); + + // if there are no notes, then there is nothing to do! + if (notes.length == 0) return; + + // sort notes in ascending order of when they were created (by id) + notes = notes.sort((a, b) => a.id - b.id); + + for (let note of notes) { + + if ((/Status changed to .*/.test(note.body) && !/Status changed to closed by commit.*/.test(note.body)) || + /changed milestone to .*/.test(note.body) || + /Milestone changed to .*/.test(note.body) || + /Reassigned to /.test(note.body) || + /added .* labels/.test(note.body) || + /Added ~.* label/.test(note.body) || + /mentioned in issue.*/.test(note.body)) { + // Don't transfer when the state changed (this is a note in GitLab) + } else { + + let bodyConverted = convertIssuesAndComments(note.body, note); + + // process asynchronous code in sequence -- treats kind of like blocking + await github.issues.createComment({ + owner: settings.github.owner, + repo: settings.github.repo, + number: ghIssue.number, + body: bodyConverted + }).catch(x=>{ + console.error("could not create GitHub issue comment!"); + console.error(x); + process.exit(1); + }); + + } + + }; + } catch (err) { + console.error("Could not fetch notes for GitLab issue #" + issue.number); + console.error(err); } - // get all comments add them to the comment - console.log(`fetching all notes for issue ${issueID}`); - gitlab.projects.issues.notes.all(projectID, issueID).then(function(data) { - if (data.length) { - data = data.sort(function(a, b) { - return a.id - b.id; - }); - async.eachSeries(data, function(item, cb) { - if ((/Status changed to .*/.test(item.body) && !/Status changed to closed by commit.*/.test(item.body)) || - /changed milestone to .*/.test(item.body) || - /Reassigned to /.test(item.body) || - /added .* labels/.test(item.body) || - /mentioned in issue.*/.test(item.body)) { - // don't transport when the state changed (is a note in gitlab) - return cb(); - } else { - convertIssuesAndComments(item.body, item, function(bodyConverted) { - github.issues.createComment({ - owner: settings.github.owner, - repo: settings.github.repo, - number: newIssueData.number, - body: bodyConverted - }, cb); - }); - } - }, callback) - } else { - callback(); - } - }).catch(function(err){ - console.log(`An Error occured while fetching all notes for issue ${issueID}:`); - console.log(err); - }); } +// ---------------------------------------------------------------------------- + +/** + * Update the issue state (i.e., closed or open). + */ +async function updateIssueState(ghIssue, issue) { + // default state is open so we don't have to update if the issue is closed. + if (issue.state != 'closed' || ghIssue.state == 'closed') return; -function createMilestone(data, cb) { - let item = { + let props = { owner: settings.github.owner, repo: settings.github.repo, - title: data.title, - description: data.description, - state: (data.state === 'active') ? 'open' : 'closed' + number: ghIssue.number, + state: issue.state }; - if (data.due_date){ - item.due_on = data.due_date + 'T00:00:00Z'; + + // make the state update + return github.issues.edit(props); +} + +// ---------------------------------------------------------------------------- + +/** + * Create a GitHub milestone from a GitLab milestone + */ +async function createMilestone(owner, repo, milestone) { + // convert from GitLab to GitHub + let ghMilestone = { + owner: owner, + repo: repo, + title: milestone.title, + description: milestone.description, + state: (milestone.state === 'active') ? 'open' : 'closed' + }; + + if (milestone.due_date) { + ghMilestone.due_on = milestone.due_date + 'T00:00:00Z'; } - github.issues.createMilestone(item, cb); + + // create the GitHub milestone + return await github.issues.createMilestone(ghMilestone); } -function createLabel(glLabel, cb) { - github.issues.createLabel({ +// ---------------------------------------------------------------------------- + +/** + * Create a GitHub label from a GitLab label + */ +async function createLabel(owner, repo, label) { + // convert from GitLab to GitHub + let ghLabel = { owner: settings.github.owner, repo: settings.github.repo, - name: glLabel.name, - color: glLabel.color.substr(1) // remove leading "#" because gitlab returns it but github wants the color without it - }, cb); + name: label.name, + color: label.color.substr(1) // remove leading "#" because gitlab returns it but github wants the color without it + }; + + // create the GitHub label + return await github.issues.createLabel(ghLabel); } +// ---------------------------------------------------------------------------- + /** - * Converts issue body and issue comments from gitlab to github. That means: + * Converts issue body and issue comments from GitLab to GitHub. That means: * - Add a line at the beginning indicating which original user created the - * issue or the comment and when - because the github API creates everything + * issue or the comment and when - because the GitHub API creates everything * as the API user - * - Change username from gitlab to github in "mentions" (@username) + * - Change username from GitLab to GitHub in "mentions" (@username) */ -function convertIssuesAndComments(str, item, cb){ - if ( (settings.usermap == null || Object.keys(settings.usermap).length == 0) && +function convertIssuesAndComments(str, item) { + + if ((settings.usermap == null || Object.keys(settings.usermap).length == 0) && (settings.projectmap == null || Object.keys(settings.projectmap).length == 0)) { - addMigrationLine(str, item, cb); + return addMigrationLine(str, item); } else { // - Replace userids as defined in settings.usermap. // They all start with '@' in the issues but we have them without in usermap // - Replace cross-project issue references. They are matched on org/project# so 'matched' ends with '#' // They all have a '#' right after the project name in the issues but we have them without in projectmap - addMigrationLine(str, item, function(strWithMigLine) { - cb(strWithMigLine.replace(userProjectRe, function(matched) { - if (matched.startsWith('@')) { - // this is a userid - return '@' + settings.usermap[matched.substr(1)]; - } else if (matched.endsWith('#')) { - // this is a cross-project issue reference - return settings.projectmap[matched.substring(0, matched.length-1)] + '#'; - } else { - // something went wrong, do nothing - return matched; - } - })); + let strWithMigLine = addMigrationLine(str, item); + + strWithMigLine.replace(userProjectRe, matched => { + if (matched.startsWith('@')) { + // this is a userid + return '@' + settings.usermap[matched.substr(1)]; + } else if (matched.endsWith('#')) { + // this is a cross-project issue reference + return settings.projectmap[matched.substring(0, matched.length-1)] + '#'; + } else { + // something went wrong, do nothing + return matched; + } }); + + return strWithMigLine; + } } -function addMigrationLine(str, item, cb) { +// ---------------------------------------------------------------------------- + +/** + * Adds a line of text at the beginning of a comment that indicates who, when + * and from GitLab. + */ +function addMigrationLine(str, item) { if (item == null || item.author == null || item.author.username == null || item.created_at == null) { - return cb(str); + return str; } var dateformatOptions = { @@ -508,9 +598,11 @@ function addMigrationLine(str, item, cb) { var formattedDate = new Date(item.created_at).toLocaleString('en-US', dateformatOptions); - return cb("In gitlab by @" +item.author.username+ " on " +formattedDate+ "\n\n" +str); + return "In GitLab by @" + item.author.username + " on " + formattedDate + "\n\n" + str; } +// ---------------------------------------------------------------------------- + /** * Generate regular expression which finds userid and cross-project issue references * from usermap and projectmap @@ -529,3 +621,14 @@ function generateUserProjectRe() { return new RegExp(reString,'g'); } + +// ---------------------------------------------------------------------------- + +/** + * Print out a section heading to let the user know what is happening + */ +function inform(msg) { + console.log("=================================="); + console.log(msg) + console.log("=================================="); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 35c05f0..b5235d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,14 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/runtime": { + "version": "7.0.0-beta.56", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0-beta.56.tgz", + "integrity": "sha512-vP9XV2VP013UEyZdU9eWClCsm6rQPUYHVNCfmpcv5uKviW7mKmUZq71Y5cr5dYsFKfnGDxSo8h6plUGR60lwHg==", + "requires": { + "regenerator-runtime": "0.12.1" + } + }, "@octokit/rest": { "version": "15.2.5", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.2.5.tgz", @@ -19,6 +27,31 @@ "url-template": "2.0.8" } }, + "@semantic-release/error": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-2.2.0.tgz", + "integrity": "sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg==" + }, + "@semantic-release/npm": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-4.0.2.tgz", + "integrity": "sha512-7uzMkY4IDN4tFTy3/zAsHPveW1fxXo4ORALaVqdopkCBGLhLtthf4TyWvJVaiS+LsBpgMer/vcwIpv5k58IUzA==", + "requires": { + "@semantic-release/error": "2.2.0", + "aggregate-error": "1.0.0", + "detect-indent": "5.0.0", + "detect-newline": "2.1.0", + "execa": "0.10.0", + "fs-extra": "7.0.0", + "lodash": "4.17.5", + "nerf-dart": "1.0.0", + "normalize-url": "3.2.0", + "parse-json": "4.0.0", + "rc": "1.2.8", + "read-pkg": "4.0.1", + "registry-auth-token": "3.3.2" + } + }, "agent-base": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", @@ -27,6 +60,15 @@ "es6-promisify": "5.0.0" } }, + "aggregate-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-1.0.0.tgz", + "integrity": "sha1-iINE2tAiCnLjr1CQYRf0h3GSX6w=", + "requires": { + "clean-stack": "1.3.0", + "indent-string": "3.2.0" + } + }, "ajv": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", @@ -87,24 +129,26 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "requires": { - "hoek": "4.2.1" - } - }, "btoa-lite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", "integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc=" }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "clean-stack": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-1.3.0.tgz", + "integrity": "sha1-noIVAa6XmYbEax1m0tQy2y/UrjE=" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -123,29 +167,18 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "requires": { - "boom": "5.2.0" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "requires": { - "hoek": "4.2.1" - } - } + "nice-try": "1.0.4", + "path-key": "2.0.1", + "semver": "5.5.0", + "shebang-command": "1.2.0", + "which": "1.3.1" } }, - "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -162,11 +195,40 @@ "ms": "2.0.0" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "requires": { + "foreach": "2.0.5", + "object-keys": "1.0.12" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=" + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" + }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" + }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", @@ -176,6 +238,36 @@ "jsbn": "0.1.1" } }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "0.2.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "requires": { + "es-to-primitive": "1.1.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "is-callable": "1.1.4", + "is-regex": "1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "requires": { + "is-callable": "1.1.4", + "is-date-object": "1.0.1", + "is-symbol": "1.0.1" + } + }, "es6-promise": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", @@ -189,6 +281,20 @@ "es6-promise": "4.2.4" } }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "requires": { + "cross-spawn": "6.0.5", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + }, "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", @@ -209,6 +315,19 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "1.1.4" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -224,6 +343,26 @@ "mime-types": "2.1.18" } }, + "fs-extra": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz", + "integrity": "sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==", + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -232,6 +371,73 @@ "assert-plus": "1.0.0" } }, + "gitlab": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/gitlab/-/gitlab-3.7.0.tgz", + "integrity": "sha512-MCPgNCVCUhOLzz6ZWBHyG6eyMAjiZdGEcGebWwFKPJR7N25y90rVXFJWBv6QfYKyPV8bNPFfxGLGwDJjtXUlkw==", + "requires": { + "@babel/runtime": "7.0.0-beta.56", + "@semantic-release/npm": "4.0.2", + "humps": "2.0.1", + "lodash.pick": "4.4.0", + "parse-link-header": "1.0.1", + "qs": "6.5.2", + "request": "2.87.0", + "request-promise": "4.2.2", + "request-promise-core": "1.1.1", + "url-join": "4.0.0", + "util.promisify": "1.0.0", + "xhr": "2.5.0" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "request": { + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.1", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + } + } + }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "requires": { + "min-document": "2.19.0", + "process": "0.5.2" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -246,21 +452,18 @@ "har-schema": "2.0.0" } }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.1", - "sntp": "2.1.0" + "function-bind": "1.1.1" } }, - "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" }, "http-proxy-agent": { "version": "2.1.0", @@ -290,11 +493,77 @@ "debug": "3.1.0" } }, + "humps": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", + "integrity": "sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao=" + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, + "is-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", + "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "1.0.3" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=" + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -306,6 +575,11 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "optional": true }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -321,6 +595,14 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "4.1.11" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -337,6 +619,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + }, "mime-db": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", @@ -350,26 +637,61 @@ "mime-db": "1.33.0" } }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "0.1.1" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=" + }, + "nice-try": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", + "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==" + }, "node-fetch": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.1.tgz", "integrity": "sha1-NpynC4L1DIZJYQSmx3bSdPTkotQ=" }, - "node-gitlab-api": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/node-gitlab-api/-/node-gitlab-api-2.2.6.tgz", - "integrity": "sha512-wDdM8TYYW6AVenXbu+o4VPbs4SjF/Ki8wchpME4TbvJxiBpWm3+1GJ87D87u/t4tH/r4G7OUA62Bv+DyiGtLWA==", + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "requires": { - "parse-link-header": "1.0.1", - "request": "2.85.0", - "request-promise": "4.2.2", - "tempy": "0.2.1", - "url-join": "4.0.0" + "hosted-git-info": "2.7.1", + "is-builtin-module": "1.0.0", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.4" + } + }, + "normalize-url": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.2.0.tgz", + "integrity": "sha512-WvF3Myk0NhXkG8S9bygFM4IC1KOvnVJGq0QoGeoqOYOBeinBZp5ybW3QuYbTc89lkWBMM9ZBO4QGRoc0353kKA==" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "2.0.1" } }, "oauth-sign": { @@ -377,6 +699,43 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.12.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "parse-headers": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.1.tgz", + "integrity": "sha1-aug6eqJanZtwCswoaYzR8e1+lTY=", + "requires": { + "for-each": "0.3.3", + "trim": "0.0.1" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2" + } + }, "parse-link-header": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-link-header/-/parse-link-header-1.0.1.tgz", @@ -385,48 +744,64 @@ "xtend": "4.0.1" } }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "0.6.0", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + } }, - "request": { - "version": "2.85.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", - "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", + "read-pkg": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "normalize-package-data": "2.4.0", + "parse-json": "4.0.0", + "pify": "3.0.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + }, + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "requires": { + "rc": "1.2.8", + "safe-buffer": "5.1.1" } }, "request-promise": { @@ -453,14 +828,57 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, - "sntp": { + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "requires": { + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" + } + }, + "spdx-exceptions": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "requires": { - "hoek": "4.2.1" + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" } }, + "spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" + }, "sshpk": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", @@ -481,24 +899,15 @@ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" - }, - "temp-dir": { + "strip-eof": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=" + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, - "tempy": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.2.1.tgz", - "integrity": "sha512-LB83o9bfZGrntdqPuRdanIVCPReam9SOZKW0fOy5I9X3A854GGWi0tjCqoXEk84XIEYBc/x9Hq3EFop/H5wJaw==", - "requires": { - "temp-dir": "1.0.0", - "unique-string": "1.0.0" - } + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "tough-cookie": { "version": "2.3.4", @@ -508,6 +917,11 @@ "punycode": "1.4.1" } }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -522,13 +936,10 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "optional": true }, - "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "requires": { - "crypto-random-string": "1.0.0" - } + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "url-join": { "version": "4.0.0", @@ -540,11 +951,29 @@ "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "requires": { + "define-properties": "1.1.2", + "object.getownpropertydescriptors": "2.0.3" + } + }, "uuid": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" + } + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -555,6 +984,25 @@ "extsprintf": "1.3.0" } }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "2.0.0" + } + }, + "xhr": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", + "integrity": "sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ==", + "requires": { + "global": "4.3.2", + "is-function": "1.0.1", + "parse-headers": "2.0.1", + "xtend": "4.0.1" + } + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index dfd6aa9..952816f 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,6 @@ "dependencies": { "@octokit/rest": "^15.2.5", "async": "^1.4.0", - "node-gitlab-api": "^2.2.6" + "gitlab": "^3.7.0" } } diff --git a/sample_settings.js b/sample_settings.js index 7a0c0d3..82ad3b1 100644 --- a/sample_settings.js +++ b/sample_settings.js @@ -2,7 +2,7 @@ module.exports = { "gitlab": { "url": "http://gitlab.mycompany.com/", // probably: https://gitlab.com "token": "{{gitlab private token}}", - "projectID": null + "projectId": null }, "github": { // "baseUrl":"https://gitlab.mycompany.com:123/etc", @@ -18,5 +18,8 @@ module.exports = { "projectmap": { "gitlabgroup/projectname.1": "GitHubOrg/projectname.1", "gitlabgroup/projectname.2": "GitHubOrg/projectname.2" + }, + "conversion": { + "useLowerCaseLabels": true } }