From a3e99c69ecaca1d328d79285a75897d642302b7b Mon Sep 17 00:00:00 2001 From: Parker Lusk Date: Tue, 7 Aug 2018 15:55:43 -0600 Subject: [PATCH 1/9] Update to new Gitlab npm package; README and style --- README.md | 40 ++- index.js | 93 ++++--- package-lock.json | 660 ++++++++++++++++++++++++++++++++++++++-------- package.json | 2 +- 4 files changed, 642 insertions(+), 153 deletions(-) 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..0798dd0 100644 --- a/index.js +++ b/index.js @@ -1,79 +1,92 @@ var GitHubApi = require('@octokit/rest') -var Gitlab = require('node-gitlab-api'); +const Gitlab = require('gitlab').default var async = require('async'); -try{ +try { var settings = require('./settings.js'); -} -catch(e){ - if(e.code === 'MODULE_NOT_FOUND'){ +} catch(e) { + if (e.code === 'MODULE_NOT_FOUND') { console.log('\n\nPlease copy the sample_settings.json to settings.json.'); - process.exit(1); - } - else{ + } 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/"){ +// Ensure that the GitLab URL and token has been set in settings.json +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.'); process.exit(1); } -if(!settings.gitlab.token || settings.gitlab.token === "{{gitlab private token}}"){ +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.'); 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.json, 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 choosen a project + migrate(); +} + +// ---------------------------------------------------------------------------- + +function listProjects() { + gitlab.Projects.all({membership: true}).then(function(projects) { + // sort projects by increasing ProjectID + projects = projects.sort(function(a, b) { return a.id - b.id; }); + + // 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('\n\n'); + }).catch(function(err){ console.log('An Error occured while fetching all projects:'); console.log(err); }); -} else { - // user has choosen a project +} +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) { + gitlab.ProjectMilestones.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; - }); + data = data.sort(function(a, b) { return a.id - b.id; }); getAllGHMilestones(function(milestoneDataA, milestoneDataMappedA) { // for now use globals milestoneData = milestoneDataA; @@ -98,7 +111,7 @@ if (settings.gitlab.projectID === null) { // all milestones are created getAllGHMilestones(function(milestoneDataA, milestoneDataMappedA) { // create labels - gitlab.projects.labels.all(settings.gitlab.projectID).then(function(glLabels) { + gitlab.Labels.all(settings.gitlab.projectID).then(function(glLabels) { getAllGHLabelNames(function(ghlabelNames) { async.each(glLabels, function(glLabel, cb) { if (ghlabelNames.indexOf(glLabel.name) < 0) { @@ -111,7 +124,7 @@ if (settings.gitlab.projectID === null) { return cb(null); } }, function(err) { - if (err) return console.log(err); + // if (err) return console.log(err); // all labels are created, create a hasAttachment label for manual attachment migration var glLabel = { name: 'hasAttachment', @@ -147,7 +160,7 @@ if (settings.gitlab.projectID === null) { function createAllIssuesAndComments(milestoneData, callback) { // select all issues and comments from this project - gitlab.projects.issues.all(settings.gitlab.projectID).then(function(issueData) { + gitlab.Issues.all({projectId: settings.gitlab.projectID}).then(function(issueData) { // TODO return all issues via pagination // look whether issue is already created issueData = issueData.sort(function(a, b) { @@ -401,7 +414,7 @@ function createAllIssueComments(projectID, issueID, newIssueData, callback) { } // 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) { + gitlab.IssueNotes.all(projectID, issueID).then(function(data) { if (data.length) { data = data.sort(function(a, b) { return a.id - b.id; 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" } } From 91b184bacf7996b5a15dec07309a04753aadd756 Mon Sep 17 00:00:00 2001 From: Parker Lusk Date: Tue, 7 Aug 2018 17:38:39 -0600 Subject: [PATCH 2/9] using async/await for milestones and labels --- index.js | 188 ++++++++++++++++++++++++++++++++------------- sample_settings.js | 5 +- 2 files changed, 138 insertions(+), 55 deletions(-) diff --git a/index.js b/index.js index 0798dd0..3daca7a 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ -var GitHubApi = require('@octokit/rest') +const GitHubApi = require('@octokit/rest') const Gitlab = require('gitlab').default -var async = require('async'); +const async = require('async'); try { var settings = require('./settings.js'); @@ -55,10 +55,12 @@ if (settings.gitlab.projectID === null) { // ---------------------------------------------------------------------------- -function listProjects() { - gitlab.Projects.all({membership: true}).then(function(projects) { - // sort projects by increasing ProjectID - projects = projects.sort(function(a, b) { return a.id - b.id; }); +/** + * 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++) { @@ -70,13 +72,95 @@ function listProjects() { console.log('Select which project ID should be transported to github. Edit the settings.json accordingly. (gitlab.projectID)'); console.log('\n\n'); - }).catch(function(err){ - console.log('An Error occured while fetching all projects:'); - console.log(err); + } 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() { + + github.authenticate({ + type: "basic", + username: settings.github.username, + password: settings.github.token }); + + // transfer GitLab milestones to GitHub + transferMilestones(settings.gitlab.projectId); + + // transfer GitLab labels to GitHub + transferLabels(settings.gitlab.projectId, settings.conversion.useLowerCaseLabels); + + // create a hasAttachment label for manual attachment migration + const hasAttachmentLabel = {name: 'hasAttachment', color: '#fbca04'}; + // createLabel(hasAttachmentLabel, function(err, createLabelData) { + // console.log(createLabelData); + // }); + } -function migrate() { +// ---------------------------------------------------------------------------- + +/** + * Transfer any milestones that exist in GitLab that do not exist in GitHub. + */ +async function transferMilestones(projectId) { + // 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(function(a, b) { return 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. + milestones.forEach(function(milestone) { + if (!ghMilestones.find(m => m.title === milestone.title)) { + console.log("Creating " + milestone.title); + } else { + console.log("Already exists: " + milestone.title); + } + }); +} + +// ---------------------------------------------------------------------------- + +/** + * Transfer any labels that exist in GitLab that do not exist in GitHub. + */ +async function transferLabels(projectId, useLowerCase = true) { + // 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); + + // if a GitLab label does not exist in GitHub repo, create it. + labels.forEach(function(label) { + + // GitHub prefers lowercase label names + if (useLowerCase) { + label.name = label.name.toLowerCase() + } + + if (!ghLabels.find(l => l === label.name)) { + console.log("Creating " + label.name); + } else { + console.log("Already exists: " + label.name); + } + }); +} + +// ---------------------------------------------------------------------------- + +function migrate1() { github.authenticate({ type: "basic", @@ -225,33 +309,50 @@ function createAllIssuesAndComments(milestoneData, callback) { }); // gitlab project Issues } -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); - } - 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); +// ---------------------------------------------------------------------------- + +/** + * 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); + } +} - }); // Milestones +// ---------------------------------------------------------------------------- +/** + * 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); + } } +// ---------------------------------------------------------------------------- + function getAllGHIssues(callback) { var lastItem = null; var curPage = 1; @@ -290,27 +391,6 @@ function getAllGHIssues(callback) { }); // 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); - } - var labelNames = labelData.data.map(function(item) { - return item.name; - }); - - return callback(labelNames); - }); - -} - function hasNext(item) { if (item === null) { return true; 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 } } From 856bab651df4a9fe9a524b8e703ce653085e6077 Mon Sep 17 00:00:00 2001 From: Parker Lusk Date: Tue, 7 Aug 2018 18:14:21 -0600 Subject: [PATCH 3/9] transfer GitLab milestones to GitHub --- index.js | 59 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index 3daca7a..9f354ca 100644 --- a/index.js +++ b/index.js @@ -115,19 +115,31 @@ async function transferMilestones(projectId) { let milestones = await gitlab.ProjectMilestones.all(projectId); // sort milestones in ascending order of when they were created (by id) - milestones = milestones.sort(function(a, b) { return a.id - b.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. - milestones.forEach(function(milestone) { - if (!ghMilestones.find(m => m.title === milestone.title)) { - console.log("Creating " + milestone.title); - } else { - console.log("Already exists: " + milestone.title); - } - }); + 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) + })(milestone); + + } catch (err) { + console.error("Could not create milestone", milestone.title); + console.error(err); + } + } else { + console.log("Already exists: " + milestone.title); + } + }; + } // ---------------------------------------------------------------------------- @@ -528,20 +540,31 @@ function createAllIssueComments(projectID, issueID, newIssueData, callback) { } -function createMilestone(data, cb) { - let item = { - owner: settings.github.owner, - repo: settings.github.repo, - title: data.title, - description: data.description, - state: (data.state === 'active') ? 'open' : 'closed' +// ---------------------------------------------------------------------------- + +/** + * 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 (data.due_date){ - item.due_on = data.due_date + 'T00:00:00Z'; + + 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({ owner: settings.github.owner, From 79c5b94ade0a8df76866d2a137542500c0ce0ae0 Mon Sep 17 00:00:00 2001 From: Parker Lusk Date: Tue, 7 Aug 2018 19:37:12 -0600 Subject: [PATCH 4/9] transfer GitLab labels to GitHub --- index.js | 87 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/index.js b/index.js index 9f354ca..7ae1d6b 100644 --- a/index.js +++ b/index.js @@ -49,7 +49,7 @@ var userProjectRe = generateUserProjectRe(); if (settings.gitlab.projectID === null) { listProjects(); } else { - // user has choosen a project + // user has chosen a project migrate(); } @@ -95,13 +95,7 @@ async function migrate() { transferMilestones(settings.gitlab.projectId); // transfer GitLab labels to GitHub - transferLabels(settings.gitlab.projectId, settings.conversion.useLowerCaseLabels); - - // create a hasAttachment label for manual attachment migration - const hasAttachmentLabel = {name: 'hasAttachment', color: '#fbca04'}; - // createLabel(hasAttachmentLabel, function(err, createLabelData) { - // console.log(createLabelData); - // }); + transferLabels(settings.gitlab.projectId, true, settings.conversion.useLowerCaseLabels); } @@ -120,15 +114,17 @@ async function transferMilestones(projectId) { // 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); + inform("Transferring Milestones"); + // 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); + console.log("Creating: " + milestone.title); try { // process asynchronous code in sequence await (() => { - createMilestone(settings.github.owner, settings.github.repo, milestone) + createMilestone(settings.github.owner, settings.github.repo, milestone); })(milestone); } catch (err) { @@ -147,27 +143,46 @@ async function transferMilestones(projectId) { /** * Transfer any labels that exist in GitLab that do not exist in GitHub. */ -async function transferLabels(projectId, useLowerCase = true) { +async function transferLabels(projectId, attachmentLabel = true, useLowerCase = true) { // 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); + inform("Transferring Labels") + + // 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. - labels.forEach(function(label) { + for (let label of labels) { - // GitHub prefers lowercase label names - if (useLowerCase) { - label.name = label.name.toLowerCase() - } + // GitHub prefers lowercase label names + if (useLowerCase) { + label.name = label.name.toLowerCase() + } - if (!ghLabels.find(l => l === label.name)) { - console.log("Creating " + label.name); - } else { - console.log("Already exists: " + label.name); - } - }); + 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=>{}); + })(label); + + } catch (err) { + console.error("Could not create label", label.name); + console.error(err); + } + } else { + console.log("Already exists: " + label.name); + } + }; } // ---------------------------------------------------------------------------- @@ -565,15 +580,24 @@ async function createMilestone(owner, repo, milestone) { // ---------------------------------------------------------------------------- -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: * - Add a line at the beginning indicating which original user created the @@ -645,3 +669,12 @@ 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 From 12efb076119fbfc4eb63aa36eef4354e18e2c7a7 Mon Sep 17 00:00:00 2001 From: Parker Lusk Date: Tue, 7 Aug 2018 22:46:34 -0600 Subject: [PATCH 5/9] issues and comments are created --- index.js | 573 ++++++++++++++++++++++++++----------------------------- 1 file changed, 267 insertions(+), 306 deletions(-) diff --git a/index.js b/index.js index 7ae1d6b..2b9a71e 100644 --- a/index.js +++ b/index.js @@ -6,7 +6,7 @@ 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.'); + console.log('\n\nPlease copy the sample_settings.js to settings.js.'); } else { console.log(e); } @@ -14,13 +14,13 @@ try { process.exit(1); } -// Ensure that the GitLab URL and token has been set in settings.json +// 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.json file.'); + 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.'); + console.log('\n\nYou have to enter your GitLab private token in the settings.js file.'); process.exit(1); } @@ -44,7 +44,7 @@ var github = new GitHubApi({ // regex for converting user from GitLab to GitHub var userProjectRe = generateUserProjectRe(); -// If no project id is given in settings.json, just return +// 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) { listProjects(); @@ -69,7 +69,7 @@ async function listProjects() { // 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 (err) { @@ -97,6 +97,8 @@ async function migrate() { // transfer GitLab labels to GitHub transferLabels(settings.gitlab.projectId, true, settings.conversion.useLowerCaseLabels); + // Transfer issues with their comments + transferIssues(settings.github.owner, settings.github.repo, settings.gitlab.projectId); } // ---------------------------------------------------------------------------- @@ -187,153 +189,78 @@ async function transferLabels(projectId, attachmentLabel = true, useLowerCase = // ---------------------------------------------------------------------------- -function migrate1() { +/** + * Transfer any issues and their comments that exist in GitLab that do not exist in GitHub. + */ +async function transferIssues(owner, repo, projectId) { - github.authenticate({ - type: "basic", - username: settings.github.username, - password: settings.github.token - }); + // Because each + let milestoneData = await getAllGHMilestones(owner, repo); - gitlab.ProjectMilestones.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.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 -} + // 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); -function createAllIssuesAndComments(milestoneData, callback) { - // select all issues and comments from this project - gitlab.Issues.all({projectId: 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' + // 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); + + inform("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 (true || !ghIssue) { + console.log("Creating: " + issue.iid + " - " + issue.title); + try { + + // process asynchronous code in sequence + await (() => { + createIssueAndComments(settings.github.owner, settings.github.repo, milestoneData, issue).catch(x=>{}); + })(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); } + }; - 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 } // ---------------------------------------------------------------------------- @@ -380,180 +307,203 @@ async function getAllGHLabelNames(owner, repo) { // ---------------------------------------------------------------------------- -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); - } - 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]; - } - cb(err); - }); // gh repo Issues - }, function(err) { - console.log('issue Count on GH:', allGhIssues.length) - callback(err, allGhIssues); - }); // async whilst -} +/** + * 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; -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 + 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++; } + return allIssues; } +// ---------------------------------------------------------------------------- -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; - } - } - return null; +/** + * + */ +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); } -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]; - } - } - 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(); +/** + * + */ +async function createIssue(owner, repo, milestones, issue) { + let props = { + owner: owner, + repo: repo, + title: issue.title.trim(), + body: issue.description + }; + + + // convertIssuesAndComments(item.description, item, function(bodyConverted) { + // props = { + // owner: settings.github.owner, + // repo: settings.github.repo, + // title: item.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 { + // process asynchronous code in sequence + await (async () => { + await github.issues.createComment({ + owner: settings.github.owner, + repo: settings.github.repo, + number: ghIssue.number, + body: note.body + }).catch(x=>{}); + })(ghIssue, note); + + // convertIssuesAndComments(issue.body, issue, function(bodyConverted) { + // github.issues.createComment({ + // owner: settings.github.owner, + // repo: settings.github.repo, + // number: ghIssue.number, + // body: bodyConverted + // }, cb); + // }); + } + + }; + } 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.IssueNotes.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; + + let props = { + owner: settings.github.owner, + repo: settings.github.repo, + number: ghIssue.number, + state: issue.state + }; + + // make the state update + return github.issues.edit(props); +} // ---------------------------------------------------------------------------- @@ -599,22 +549,23 @@ async function createLabel(owner, repo, label) { // ---------------------------------------------------------------------------- /** - * 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, issue, cb) { + + 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, issue); } 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) { + addMigrationLine(str, issue, function(strWithMigLine) { cb(strWithMigLine.replace(userProjectRe, function(matched) { if (matched.startsWith('@')) { // this is a userid @@ -631,10 +582,16 @@ function convertIssuesAndComments(str, item, cb){ } } -function addMigrationLine(str, item, cb) { +// ---------------------------------------------------------------------------- - if (item == null || item.author == null || item.author.username == null || item.created_at == null) { - return cb(str); +/** + * Adds a line of text at the beginning of a comment that indicates who, when + * and from GitLab. + */ +function addMigrationLine(str, issue) { + + if (issue == null || issue.author == null || issue.author.username == null || issue.created_at == null) { + return str; } var dateformatOptions = { @@ -646,11 +603,13 @@ function addMigrationLine(str, item, cb) { hour12: false } - var formattedDate = new Date(item.created_at).toLocaleString('en-US', dateformatOptions); + var formattedDate = new Date(issue.created_at).toLocaleString('en-US', dateformatOptions); - return cb("In gitlab by @" +item.author.username+ " on " +formattedDate+ "\n\n" +str); + return "In GitLab by @" + issue.author.username + " on " + formattedDate + "\n\n" + str; } +// ---------------------------------------------------------------------------- + /** * Generate regular expression which finds userid and cross-project issue references * from usermap and projectmap @@ -670,6 +629,8 @@ function generateUserProjectRe() { return new RegExp(reString,'g'); } +// ---------------------------------------------------------------------------- + /** * Print out a section heading to let the user know what is happening */ From 7f1a6464962929c17dde4e14121936f7e5302521 Mon Sep 17 00:00:00 2001 From: Parker Lusk Date: Tue, 7 Aug 2018 23:07:09 -0600 Subject: [PATCH 6/9] issue and comment body is converted as before --- index.js | 70 ++++++++++++++++++++++++-------------------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/index.js b/index.js index 2b9a71e..942f738 100644 --- a/index.js +++ b/index.js @@ -242,7 +242,7 @@ async function transferIssues(owner, repo, projectId) { for (let issue of issues) { // try to find a GitHub issue that already exists for this GitLab issue let ghIssue = ghIssues.find(i => i.title.trim() === issue.title.trim()); - if (true || !ghIssue) { + if (!ghIssue) { console.log("Creating: " + issue.iid + " - " + issue.title); try { @@ -363,23 +363,15 @@ async function createIssueAndComments(owner, repo, milestones, 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: issue.description + body: bodyConverted }; - - // convertIssuesAndComments(item.description, item, function(bodyConverted) { - // props = { - // owner: settings.github.owner, - // repo: settings.github.repo, - // title: item.title.trim(), - // body: bodyConverted - // }; - // }); - // // Issue Assignee // @@ -458,24 +450,19 @@ async function createIssueComments(ghIssue, issue) { /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 await (async () => { await github.issues.createComment({ owner: settings.github.owner, repo: settings.github.repo, number: ghIssue.number, - body: note.body + body: bodyConverted }).catch(x=>{}); })(ghIssue, note); - // convertIssuesAndComments(issue.body, issue, function(bodyConverted) { - // github.issues.createComment({ - // owner: settings.github.owner, - // repo: settings.github.repo, - // number: ghIssue.number, - // body: bodyConverted - // }, cb); - // }); } }; @@ -555,30 +542,33 @@ async function createLabel(owner, repo, label) { * as the API user * - Change username from GitLab to GitHub in "mentions" (@username) */ -function convertIssuesAndComments(str, issue, cb) { +function convertIssuesAndComments(str, item) { if ((settings.usermap == null || Object.keys(settings.usermap).length == 0) && (settings.projectmap == null || Object.keys(settings.projectmap).length == 0)) { - return addMigrationLine(str, issue); + 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, issue, 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; + } } @@ -588,9 +578,9 @@ function convertIssuesAndComments(str, issue, cb) { * Adds a line of text at the beginning of a comment that indicates who, when * and from GitLab. */ -function addMigrationLine(str, issue) { +function addMigrationLine(str, item) { - if (issue == null || issue.author == null || issue.author.username == null || issue.created_at == null) { + if (item == null || item.author == null || item.author.username == null || item.created_at == null) { return str; } @@ -603,9 +593,9 @@ function addMigrationLine(str, issue) { hour12: false } - var formattedDate = new Date(issue.created_at).toLocaleString('en-US', dateformatOptions); + var formattedDate = new Date(item.created_at).toLocaleString('en-US', dateformatOptions); - return "In GitLab by @" + issue.author.username + " on " + formattedDate + "\n\n" + str; + return "In GitLab by @" + item.author.username + " on " + formattedDate + "\n\n" + str; } // ---------------------------------------------------------------------------- From 6b79f637ec128b6fee88afc2ef53325b2edde82a Mon Sep 17 00:00:00 2001 From: Parker Lusk Date: Wed, 8 Aug 2018 00:05:07 -0600 Subject: [PATCH 7/9] simplified logic; ensure serial issue creation --- index.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 942f738..bb3f826 100644 --- a/index.js +++ b/index.js @@ -246,10 +246,8 @@ async function transferIssues(owner, repo, projectId) { console.log("Creating: " + issue.iid + " - " + issue.title); try { - // process asynchronous code in sequence - await (() => { - createIssueAndComments(settings.github.owner, settings.github.repo, milestoneData, issue).catch(x=>{}); - })(issue); + // 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); @@ -453,15 +451,13 @@ async function createIssueComments(ghIssue, issue) { let bodyConverted = convertIssuesAndComments(note.body, note); - // process asynchronous code in sequence - await (async () => { - await github.issues.createComment({ - owner: settings.github.owner, - repo: settings.github.repo, - number: ghIssue.number, - body: bodyConverted - }).catch(x=>{}); - })(ghIssue, 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=>{}); } From 957ff9d64a77b34e055b8e2ff3f2f9886683df00 Mon Sep 17 00:00:00 2001 From: Parker Lusk Date: Wed, 8 Aug 2018 08:04:46 -0600 Subject: [PATCH 8/9] force sequential transfer --- index.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index bb3f826..f782e11 100644 --- a/index.js +++ b/index.js @@ -91,14 +91,20 @@ async function migrate() { password: settings.github.token }); + // + // Sequentially transfer repo things + // + // transfer GitLab milestones to GitHub - transferMilestones(settings.gitlab.projectId); + await transferMilestones(settings.gitlab.projectId); // transfer GitLab labels to GitHub - transferLabels(settings.gitlab.projectId, true, settings.conversion.useLowerCaseLabels); + await transferLabels(settings.gitlab.projectId, true, settings.conversion.useLowerCaseLabels); // Transfer issues with their comments - transferIssues(settings.github.owner, settings.github.repo, settings.gitlab.projectId); + await transferIssues(settings.github.owner, settings.github.repo, settings.gitlab.projectId); + + console.log("\n\nTransfer complete!\n\n"); } // ---------------------------------------------------------------------------- From 06bc6799fb269059e67a07123766f0ee0526e877 Mon Sep 17 00:00:00 2001 From: Parker Lusk Date: Wed, 19 Sep 2018 22:05:17 -0400 Subject: [PATCH 9/9] changes for @spruce --- index.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index f782e11..3803b7f 100644 --- a/index.js +++ b/index.js @@ -113,6 +113,8 @@ async function migrate() { * 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); @@ -122,8 +124,6 @@ async function transferMilestones(projectId) { // 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); - inform("Transferring Milestones"); - // 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)) { @@ -131,9 +131,7 @@ async function transferMilestones(projectId) { try { // process asynchronous code in sequence - await (() => { - createMilestone(settings.github.owner, settings.github.repo, milestone); - })(milestone); + await createMilestone(settings.github.owner, settings.github.repo, milestone); } catch (err) { console.error("Could not create milestone", milestone.title); @@ -152,14 +150,14 @@ async function transferMilestones(projectId) { * 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); - inform("Transferring Labels") - // create a hasAttachment label for manual attachment migration if (attachmentLabel) { const hasAttachmentLabel = {name: 'has attachment', color: '#fbca04'}; @@ -179,9 +177,7 @@ async function transferLabels(projectId, attachmentLabel = true, useLowerCase = try { // process asynchronous code in sequence - await (() => { - createLabel(settings.github.owner, settings.github.repo, label).catch(x=>{}); - })(label); + await createLabel(settings.github.owner, settings.github.repo, label).catch(x=>{}); } catch (err) { console.error("Could not create label", label.name); @@ -199,6 +195,7 @@ async function transferLabels(projectId, attachmentLabel = true, useLowerCase = * 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); @@ -213,7 +210,7 @@ async function transferIssues(owner, repo, projectId) { // 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); - inform("Transferring " + issues.length.toString() + " Issues"); + console.log("Transferring " + issues.length.toString() + " issues"); // // Create Placeholder Issues @@ -463,7 +460,11 @@ async function createIssueComments(ghIssue, issue) { repo: settings.github.repo, number: ghIssue.number, body: bodyConverted - }).catch(x=>{}); + }).catch(x=>{ + console.error("could not create GitHub issue comment!"); + console.error(x); + process.exit(1); + }); }