'")
20 | | {{ tab | lastNameInPath}}
21 | a(ng-click="content.closeProjectTab(tab)")
22 | i(class="fa fa-remove")
23 | include ./top.pug
24 | div(ng-show="tab")
25 | .row.table-header
26 | .col-xs-4.clickable(ng-click="sortTableBy('name')")
27 | | Package
28 | i(class="fa", ng-class="{'fa-sort': !tableOrderBy.includes('-name') || !tableOrderBy.includes('name'), 'fa-sort-down': tableOrderBy.includes('-name'), 'fa-sort-up': tableOrderBy.includes('name')}")
29 | .col-xs-2
30 | | Current
31 | .col-xs-2
32 | | Wanted
33 | .col-xs-2
34 | | Latest
35 | .col-xs-2.clickable(ng-click="sortTableBy('kind')")
36 | | Env
37 | i(class="fa", ng-class="{'fa-sort': !tableOrderBy.includes('-kind') || !tableOrderBy.includes('kind'), 'fa-sort-down': tableOrderBy.includes('-kind'), 'fa-sort-up': tableOrderBy.includes('kind')}")
38 | .table-body(ng-table-keyboard, ng-class="{'freezed': showLoadingSelectedRow}")
39 | .table-loader(ng-show="loading && !loaded")
40 | .table-loader-content
41 | img(src='img/loading.svg')
42 | | Loading packages...
43 |
44 | .row.table-row.disabled(ng-repeat='aPackage in packageInformations', ng-if="isGlobalProject && aPackage.name === 'npm'", title="Do not perform npm global actions from here")
45 | .col-xs-4 {{ aPackage.name }}
46 | .col-xs-2
47 | span(ng-class="{'color-positive font-light': !aPackage.wanted && !aPackage.latest}")
48 | | {{ aPackage.current }}
49 | .col-xs-2
50 | i(class="fa fa-check", ng-if="!aPackage.wanted && !aPackage.latest")
51 | | {{ aPackage.wanted }}
52 | .col-xs-2
53 | b(ng-if="aPackage.latest")
54 | | {{ aPackage.latest }}
55 | i(class="fa fa-check color-positive", ng-if="!aPackage.wanted && !aPackage.latest")
56 | .col-xs-2
57 | | {{ aPackage.kind }}
58 |
59 | .row.table-row(ng-repeat='aPackage in packageInformations | orderBy: tableOrderBy', ng-hide="!packageInformations", id="table-item-{{$index}}", ng-table-keyboard-selected-items="selectedPackages", ng-if="!isGlobalProject || isGlobalProject && aPackage.name !== 'npm'", selection-model, selection-model-mode="'multiple'", selection-model-selected-items="selectedPackages", ng-click="selectPackages(selectedPackages)", ng-class="{'active': selectedPackages.includes(aPackage), 'table-row-loading': currentSelectedPackages.includes(aPackage) && showLoadingSelectedRow}")
60 | .col-xs-4 {{ aPackage.name }}
61 | .col-xs-2
62 | span(ng-class="{'color-positive font-light': !aPackage.wanted && !aPackage.latest}")
63 | | {{ aPackage.current }}
64 | .col-xs-2
65 | i(class="fa fa-check", ng-if="!aPackage.wanted && !aPackage.latest")
66 | | {{ aPackage.wanted }}
67 | .col-xs-2
68 | b(ng-if="aPackage.latest")
69 | | {{ aPackage.latest }}
70 | i(class="fa fa-check color-positive", ng-if="!aPackage.wanted && !aPackage.latest")
71 | .col-xs-2
72 | | {{ aPackage.kind }}
73 | div
74 | div.table-infos
75 | include ./package-informations.pug
76 |
--------------------------------------------------------------------------------
/lib/js/directives/ng-ace-editor.js:
--------------------------------------------------------------------------------
1 | /*global require window*/
2 | import angular from 'angular';
3 | const moduleName = 'npm-ui.ng-ace-editor'
4 | , fs = require('fs')
5 | , ace = window.ace;
6 |
7 | angular.module(moduleName, [])
8 | .directive('ngAceEditor', /*@ngInject*/ function ngAceEditor($rootScope, $document) {
9 | return {
10 | 'require': '?ngModel',
11 | 'link': (scope, element, attrs, ngModel) => {
12 |
13 | const editorElement = element[0].querySelector('.ng-ace-editor')
14 | , aceEditor = ace.edit(editorElement)
15 | , aceSession = aceEditor.getSession()
16 | , theme = attrs.ngAceEditorTheme
17 | , readonly = scope.$eval(attrs.ngAceEditorReadonly)
18 | , setAceMode = () => {
19 | if (attrs.ngAceFileName.endsWith('.json')) {
20 |
21 | aceSession.setMode('ace/mode/json');
22 | } else if (attrs.ngAceFileName.startsWith('.')) {
23 | aceSession.setMode('ace/mode/text');
24 | }
25 | }
26 | , unregisterSavedFile = $rootScope.$on('ace-editor:saved-file', () => {
27 | scope.$evalAsync(() => {
28 | scope.savingFile = false;
29 | scope.savedFile = true;
30 | });
31 | })
32 | , unregisterSavingFile = $rootScope.$on('ace-editor:saving-file', () => {
33 | scope.$evalAsync(() => {
34 | scope.savingFile = true;
35 | scope.savedFile = false;
36 | });
37 | })
38 | , unregisterLoadedFile = $rootScope.$on('ace-editor:loaded-file', () => {
39 | scope.$evalAsync(() => {
40 | scope.loadingFile = false;
41 | aceEditor.focus();
42 | });
43 | })
44 | , unregisterLoadingFile = $rootScope.$on('ace-editor:loading-file', () => {
45 | scope.$evalAsync(() => {
46 | scope.loadingFile = true;
47 | scope.savedFile = false;
48 | scope.savingFile = false;
49 | setAceMode();
50 | });
51 | });
52 |
53 | attrs.$observe('ngAceFile', filePath => {
54 | if (filePath) {
55 | $rootScope.$emit('ace-editor:loading-file', {
56 | 'path': filePath
57 | });
58 | try {
59 | if (fs.existsSync(filePath)) {
60 | scope.aceFileModel = fs.readFileSync(filePath).toString();
61 | } else {
62 | scope.aceFileModel = '';
63 | aceEditor.setValue('');
64 | }
65 | } catch (e) {
66 | scope.aceFileModel = '';
67 | aceEditor.setValue('');
68 | }
69 |
70 | $rootScope.$emit('ace-editor:loaded-file', {
71 | 'path': filePath,
72 | 'content': scope.aceFileModel
73 | });
74 | }
75 | });
76 |
77 | attrs.$observe('ngAceSource', source => {
78 | if (source) {
79 | scope.aceFileModel = source;
80 | } else {
81 | scope.aceFileModel = '';
82 | }
83 | });
84 |
85 | scope.saveFile = () => {
86 | $rootScope.$emit('ace-editor:saving-file', {
87 | 'path': attrs.ngAceFile,
88 | 'content': scope.aceFileModel
89 | });
90 | fs.writeFileSync(attrs.ngAceFile, scope.aceFileModel, {'flag': 'w'}, 'utf8');
91 | $rootScope.$emit('ace-editor:saved-file', {
92 | 'path': attrs.ngAceFile,
93 | 'content': scope.aceFileModel
94 | });
95 | };
96 |
97 | scope.$watch(() => {
98 | return [editorElement.offsetWidth, editorElement.offsetHeight];
99 | }, () => {
100 | aceEditor.resize();
101 | aceEditor.setOptions({
102 | 'showInvisibles': true,
103 | 'cursorStyle': 'smooth',
104 | 'highlightSelectedWord': true,
105 | 'theme': `ace/theme/${theme}`,
106 | 'readOnly': readonly
107 | });
108 | aceEditor.renderer.updateFull();
109 | }, true);
110 |
111 | aceSession.on('change', () => {
112 | if (aceSession.getValue()) {
113 | ngModel.$setViewValue(aceSession.getValue());
114 | }
115 | });
116 |
117 | ngModel.$render = () => {
118 | if (ngModel.$viewValue) {
119 | aceSession.setValue(ngModel.$viewValue);
120 | }
121 | };
122 |
123 | $document.on('mousedown mouseup mouseover', () => {
124 | aceEditor.resize();
125 | });
126 |
127 | element.on('$destroy', () => {
128 | aceSession.$stopWorker();
129 | aceEditor.destroy();
130 | unregisterSavingFile();
131 | unregisterSavedFile();
132 | unregisterLoadingFile();
133 | unregisterLoadedFile();
134 | });
135 | }
136 | };
137 | });
138 |
139 | export default moduleName;
140 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ndm",
3 | "version": "1.2.0",
4 | "description": "npm desktop manager",
5 | "main": "index.js",
6 | "homepage": "https://720kb.github.io/ndm",
7 | "github": "https://github.com/720kb/ndm",
8 | "donate": {
9 | "opencollective": "https://opencollective.com/ndm"
10 | },
11 | "author": {
12 | "name": "720kb",
13 | "email": "tech@720kb.net",
14 | "url": "http://720kb.net"
15 | },
16 | "authors": [
17 | "720kb",
18 | "Filippo Oretti",
19 | "Dario Andrei"
20 | ],
21 | "repository": {
22 | "type": "git",
23 | "url": "https://github.com/720kb/ndm.git"
24 | },
25 | "copyright": "©720kb, Filippo Oretti, Dario Andrei",
26 | "bugs": {
27 | "url": "https://github.com/720kb/ndm/issues"
28 | },
29 | "license": {
30 | "type": "GPL-3.0",
31 | "url": "https://github.com/720kb/ndm/blob/master/LICENSE.md"
32 | },
33 | "build": {
34 | "appId": "net.720kb.ndm",
35 | "copyright": "© 720kb - Filippo Oretti - Dario Andrei",
36 | "asar": false,
37 | "productName": "ndm",
38 | "icon": "icon.icns",
39 | "mac": {
40 | "category": "public.app-category.productivity",
41 | "target": [
42 | "dmg",
43 | "zip"
44 | ]
45 | },
46 | "dmg": {
47 | "backgroundColor": "#cdcdcd"
48 | },
49 | "linux": {
50 | "maintainer": "720kb.net",
51 | "category": "Utility",
52 | "description": "npm desktop manager",
53 | "packageCategory": "Utility",
54 | "target": [
55 | "deb",
56 | "rpm",
57 | "zip"
58 | ]
59 | },
60 | "win": {
61 | "icon": "icon.ico",
62 | "target": [
63 | "zip",
64 | "nsis"
65 | ]
66 | },
67 | "files": [
68 | "node_modules",
69 | "dist",
70 | "index.js",
71 | "menu.js",
72 | "icon.ico",
73 | "LICENSE.md"
74 | ],
75 | "directories": {
76 | "output": "./releases"
77 | }
78 | },
79 | "social": {
80 | "twitter": {
81 | "url": "https://twitter.com/720kb_"
82 | },
83 | "gitter": {
84 | "url": "https://gitter.im/720kb/ndm"
85 | }
86 | },
87 | "appTemplate": {
88 | "title": "ndm",
89 | "width": 640,
90 | "height": 420,
91 | "minWidth": 640,
92 | "minHeight": 420,
93 | "show": false,
94 | "center": true,
95 | "movable": true,
96 | "resizable": true,
97 | "minimizable": true,
98 | "maximizable": true,
99 | "closable": true,
100 | "fullscreenable": true
101 | },
102 | "scripts": {
103 | "precommit": "npm run lint",
104 | "lint": "gulp lint",
105 | "prestart": "npm install && gulp dist --platform=mac",
106 | "start": "electron .",
107 | "mac": "LANG=en_US.UTF-8 && gulp dist --platform=mac && electron .",
108 | "linux": "gulp dist --platform=linux && electron .",
109 | "win": "gulp dist --platform=win && electron .",
110 | "build": "npm run build-mac && npm run build-linux && npm run build-win",
111 | "build-mac": "gulp distify --platform=mac && build --mac --publish=never && echo 'IMPORTANT! if build fails see https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build , you probably miss some library on your OS, just install them and retry :).'",
112 | "build-win": "gulp distify --platform=win && build --win --publish=never && echo 'IMPORTANT! if build fails see https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build , you probably miss some library on your OS, just install them and retry :).'",
113 | "build-linux": "gulp distify --platform=linux && build --linux --publish=never && echo 'IMPORTANT! if build fails see https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build , you probably miss some library on your OS, just install them and retry :).'",
114 | "build-linux-deb": "gulp distify --platform=linux && build --linux deb --publish=never && echo 'IMPORTANT! if build fails see https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build , you probably miss some library on your OS, just install them and retry :).'",
115 | "postversion": "git push && git push --tags"
116 | },
117 | "dependencies": {
118 | "ace-builds": "^1.2.5",
119 | "adm-zip": "^0.4.7",
120 | "angular": "^1.6.0",
121 | "bootstrap": "^3.3.6",
122 | "electron-storage": "^1.0.6",
123 | "fs-extra": "^2.0.0",
124 | "node-fetch": "^1.6.3",
125 | "npm": "^4.4.0",
126 | "rimraf": "^2.6.1",
127 | "selection-model": "^0.11.0",
128 | "shell-path": "^2.0.0",
129 | "universal-analytics": "^0.4.8",
130 | "uuid": "^3.0.1"
131 | },
132 | "devDependencies": {
133 | "babel-preset-es2015-rollup": "^3.0.0",
134 | "del": "^2.2.0",
135 | "electron": "^1.6.6",
136 | "electron-builder": "^17.3.1",
137 | "eslint": "^3.13.1",
138 | "gulp": "^3.9.1",
139 | "gulp-clean-css": "^2.2.0",
140 | "gulp-eslint": "^3.0.1",
141 | "gulp-ng-annotate": "^2.0.0",
142 | "gulp-plumber": "^1.1.0",
143 | "gulp-pug": "^3.2.0",
144 | "gulp-sass": "^3.1.0",
145 | "gulp-sourcemaps": "^2.3.1",
146 | "gulp-uglify": "^2.0.0",
147 | "husky": "^0.12.0",
148 | "pug": "^2.0.0-beta6",
149 | "require-dir": "^0.3.1",
150 | "rollup": "^0.41.1",
151 | "rollup-plugin-babel": "^2.4.0",
152 | "rollup-plugin-json": "^2.0.0",
153 | "run-sequence": "^1.1.5",
154 | "yargs": "^6.5.0"
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/lib/js/update.js:
--------------------------------------------------------------------------------
1 | /*globals require,__dirname,process,window,Buffer*/
2 | import angular from 'angular';
3 |
4 | const path = require('path')
5 | , {remote} = require('electron')
6 | , fs = require('fs')
7 | , AdmZip = require('adm-zip')
8 | , fse = require('fs-extra')
9 | , tmpDirectory = require('os').tmpdir()
10 | , tmpFolder = path.resolve(tmpDirectory, 'ndm-')
11 | , packageJSON = require(path.resolve(__dirname, '..', 'package.json'));
12 |
13 | angular.module('ndm-updater', [])
14 | .constant('updateUrl', 'https://api.github.com/repos/720kb/ndm/releases/latest')
15 | .controller('ShellController', /* @ngInject */function ShellController(
16 | $scope,
17 | $document,
18 | $log,
19 | updateUrl
20 | ) {
21 | const onUpdateCheckEnd = event => {
22 |
23 | let updateMetaInfo
24 | , indexOfZipName;
25 |
26 | if (process.platform === 'darwin') {
27 | indexOfZipName = 'mac.zip';
28 | } else if (process.platform === 'win32') {
29 | indexOfZipName = 'win.zip';
30 | }
31 |
32 | $scope.$apply(() => {
33 |
34 | this.checking = false;
35 | if (!event.target.responseText) {
36 |
37 | this.error = 'No response';
38 | this.errorChecking = true;
39 | return;
40 | }
41 |
42 | try {
43 |
44 | updateMetaInfo = JSON.parse(event.target.responseText);
45 | $log.info('Github Fetched Metas', updateMetaInfo);
46 | } catch (parsingError) {
47 |
48 | this.error = parsingError;
49 | this.errorChecking = true;
50 | return;
51 | }
52 |
53 | if (updateMetaInfo.tag_name === `v${packageJSON.version}` ||
54 | updateMetaInfo.tag_name.indexOf('v') === -1) {
55 |
56 | //Nothing to update
57 | this.toUpdate = false;
58 | return;
59 | }
60 |
61 | //There is something to update
62 | this.nextVesion = updateMetaInfo.name.replace('v', '');
63 | this.toUpdate = true;
64 | this.url = updateMetaInfo.assets
65 | .filter(element => element.name.indexOf(indexOfZipName) > -1)
66 | .reduce(prev => prev).browser_download_url;
67 | });
68 | }
69 | , onUpdateCheckErr = () => {
70 |
71 | $scope.$apply(() => {
72 |
73 | this.checking = false;
74 | this.errorChecking = true;
75 | });
76 | }
77 | , onUpdateErr = () => {
78 |
79 | $scope.$apply(() => {
80 |
81 | this.checking = false;
82 | this.errorUpdating = true;
83 | this.progress = 100;
84 | });
85 | }
86 | , onUpdateEnd = event => {
87 |
88 | fs.mkdtemp(tmpFolder, (err, folder) => {
89 | const fileToSave = path.resolve(folder, `npm_${new Date().getTime()}`);
90 | let newResources;
91 |
92 | if (process.platform === 'darwin') {
93 | newResources = path.resolve(folder, path.join('ndm.app', 'Contents', 'Resources', 'app'));
94 | } else if (process.platform === 'win32') {
95 | newResources = path.resolve(folder, path.join('win-ia32-unpacked', 'resources', 'app'));
96 | }
97 |
98 | if (err) {
99 |
100 | throw err;
101 | }
102 |
103 | fs.appendFile(fileToSave, new Buffer(event.target.response), appendErr => {
104 |
105 | if (appendErr) {
106 |
107 | throw appendErr;
108 | }
109 | try {
110 | const zip = new AdmZip(fileToSave);
111 |
112 | zip.extractAllTo(/*target path*/folder, /*overwrite*/true);
113 | } catch (excp) {
114 | $log.warn(excp);
115 | }
116 |
117 | fse.move(newResources, remote.app.getAppPath(), {
118 | 'overwrite': true
119 | }, moveErr => {
120 |
121 | if (moveErr) {
122 |
123 | throw moveErr;
124 | }
125 | remote.app.relaunch();
126 | remote.app.exit(0);
127 | });
128 | });
129 | });
130 | }
131 | , onUpdateProgress = event => {
132 |
133 | $scope.$apply(() => {
134 |
135 | if (event.lengthComputable) {
136 | this.progress = (event.loaded / event.total) * 100;
137 | } else {
138 |
139 | this.progress = (this.progress + 10) % 100;
140 | }
141 | });
142 | };
143 |
144 | this.checkNow = () => {
145 | if (!this.checking) {
146 |
147 | this.errorChecking = false;
148 | this.checking = true;
149 | const updateRequest = new window.XMLHttpRequest();
150 |
151 | delete this.toUpdate;
152 | updateRequest.addEventListener('load', onUpdateCheckEnd);
153 | updateRequest.addEventListener('error', onUpdateCheckErr);
154 | updateRequest.open('GET', updateUrl);
155 | updateRequest.send();
156 | }
157 | };
158 |
159 | this.updateIt = () => {
160 | if (!this.updating) {
161 |
162 | this.errorUpdating = false;
163 | this.updating = true;
164 | const updating = new window.XMLHttpRequest();
165 |
166 | delete this.toUpdate;
167 | updating.addEventListener('load', onUpdateEnd);
168 | updating.addEventListener('error', onUpdateErr);
169 | updating.addEventListener('progress', onUpdateProgress);
170 |
171 | updating.open('GET', this.url);
172 | updating.responseType = 'arraybuffer';
173 | updating.send();
174 | }
175 | };
176 |
177 | this.currentVersion = packageJSON.version;
178 | this.checkNow();
179 |
180 | $document[0].addEventListener('visibilitychange', () => {
181 | if ($document[0].visibilityState === 'visible' &&
182 | !this.checking &&
183 | !this.updating) {
184 | this.checkNow();
185 | }
186 | });
187 | });
188 |
--------------------------------------------------------------------------------
/menu.js:
--------------------------------------------------------------------------------
1 | /*global module process*/
2 | (function withNode() {
3 |
4 | module.exports = (mainWindow, updateWindow, shell, packageJSON, app) => {
5 |
6 | let menuTemplate;
7 |
8 | const aboutMenuItem = {
9 | 'submenu': [
10 | {
11 | 'role': 'about'
12 | },
13 | {
14 | 'label': `Version ${packageJSON.version}`,
15 | 'enabled': false
16 | },
17 | {
18 | 'label': 'Check for Updates...',
19 | click() {
20 |
21 | mainWindow.webContents.send('loading:freeze-app');
22 | updateWindow.setMenu(null);
23 | updateWindow.show();
24 | }
25 | },
26 | {
27 | 'type': 'separator'
28 | },
29 | {
30 | 'label': 'Visit Website',
31 | click() {
32 | shell.openExternal(packageJSON.homepage);
33 | }
34 | }
35 | ]
36 | }
37 | , fileMenuItem = {
38 | 'label': 'File',
39 | 'submenu': [
40 | {
41 | 'label': 'Add Project...',
42 | 'accelerator': 'CmdOrCtrl+O',
43 | click() {
44 | mainWindow.webContents.send('menu:add-project-folder');
45 | }
46 | }
47 | ]
48 | }
49 | , editMenuItem = {
50 | 'label': 'Edit',
51 | 'submenu': [
52 | {
53 | 'role': 'undo'
54 | },
55 | {
56 | 'role': 'redo'
57 | },
58 | {
59 | 'type': 'separator'
60 | },
61 | {
62 | 'role': 'cut'
63 | },
64 | {
65 | 'role': 'copy'
66 | },
67 | {
68 | 'role': 'paste'
69 | },
70 | {
71 | 'role': 'pasteandmatchstyle'
72 | },
73 | {
74 | 'role': 'delete'
75 | },
76 | {
77 | 'role': 'selectall'
78 | }
79 | ]
80 | }
81 | , viewMenuItem = {
82 | 'label': 'View',
83 | 'submenu': [
84 | {
85 | 'role': 'togglefullscreen'
86 | },
87 | {
88 | 'type': 'separator'
89 | },
90 | {
91 | 'label': 'Developer',
92 | 'submenu': [{
93 | 'label': 'Open DevTools',
94 | click(item, focusedWindow) {
95 | if (focusedWindow) {
96 | focusedWindow.openDevTools();
97 | }
98 | }
99 | }]
100 | }
101 | ]
102 | }
103 | , windowMenuItem = {
104 | 'role': 'window',
105 | 'submenu': [
106 | {
107 | 'role': 'minimize'
108 | },
109 | {
110 | 'role': 'close'
111 | }
112 | ]
113 | }
114 | , helpMenuItem = {
115 | 'role': 'help',
116 | 'submenu': [
117 | {
118 | 'label': 'More About',
119 | click() {
120 | shell.openExternal(`${packageJSON.github}`);
121 | }
122 | },
123 | {
124 | 'label': 'Report an issue',
125 | click() {
126 | shell.openExternal(`${packageJSON.bugs.url}`);
127 | }
128 | },
129 | {
130 | 'type': 'separator'
131 | },
132 | {
133 | 'label': 'Donate',
134 | click() {
135 | shell.openExternal(packageJSON.donate.opencollective);
136 | }
137 | },
138 | {
139 | 'type': 'separator'
140 | },
141 | {
142 | 'label': 'Join Chat',
143 | click() {
144 | shell.openExternal(`${packageJSON.social.gitter.url}`);
145 | }
146 | },
147 | {
148 | 'label': 'Follow on Twitter',
149 | click() {
150 | shell.openExternal(`${packageJSON.social.twitter.url}`);
151 | }
152 | }
153 | ]
154 | };
155 |
156 | if (process.platform !== 'darwin' &&
157 | process.platform !== 'win32') {
158 |
159 | //if linux no need for "check for updates"
160 | aboutMenuItem.submenu.splice(2, 1);
161 | }
162 |
163 | if (process.platform &&
164 | process.platform === 'darwin') {
165 | aboutMenuItem.label = packageJSON.name;
166 | aboutMenuItem.submenu.push({
167 | 'type': 'separator'
168 | });
169 | aboutMenuItem.submenu.push({
170 | 'role': 'hide'
171 | });
172 | aboutMenuItem.submenu.push({
173 | 'role': 'hideothers'
174 | });
175 | aboutMenuItem.submenu.push({
176 | 'role': 'unhide'
177 | });
178 | aboutMenuItem.submenu.push({
179 | 'type': 'separator'
180 | });
181 | aboutMenuItem.submenu.push({
182 | 'label': 'Restart',
183 | 'accelerator': 'CmdOrCtrl+R',
184 | click() {
185 | app.relaunch();
186 | app.quit();
187 | }
188 | });
189 | aboutMenuItem.submenu.push({
190 | 'role': 'quit'
191 | });
192 |
193 | menuTemplate = [
194 | aboutMenuItem,
195 | fileMenuItem,
196 | editMenuItem,
197 | viewMenuItem,
198 | windowMenuItem,
199 | helpMenuItem
200 | ];
201 | } else {
202 | aboutMenuItem.label = 'About';
203 |
204 | viewMenuItem.submenu.unshift({
205 | 'label': 'Toggle menu',
206 | click() {
207 | mainWindow.setAutoHideMenuBar(true);
208 | if (mainWindow.isMenuBarVisible()) {
209 |
210 | mainWindow.setMenuBarVisibility(false);
211 | } else {
212 |
213 | mainWindow.setMenuBarVisibility(true);
214 | }
215 | }
216 | });
217 |
218 | menuTemplate = [
219 | fileMenuItem,
220 | editMenuItem,
221 | viewMenuItem,
222 | helpMenuItem,
223 | aboutMenuItem
224 | ];
225 | }
226 |
227 | return menuTemplate;
228 | };
229 | }());
230 |
--------------------------------------------------------------------------------
/lib/js/directives/ng-tag-input.js:
--------------------------------------------------------------------------------
1 | /*global document window*/
2 | import angular from 'angular';
3 | const moduleName = 'npm-ui.ng-tag-input';
4 |
5 | angular.module(moduleName, [])
6 | .directive('ngTagInput', /*@ngInject*/ function ngTagInput($rootScope, $log) {
7 | return {
8 | 'require': '?ngModel',
9 | 'link': (scope, element, attrs, ngModel) => {
10 | let ngTagInputIdentifier = attrs.tabPathId
11 | , documentRange
12 | , windowSelection
13 | , focusTheEnd = () => {
14 | try {
15 | element[0].focus();
16 | documentRange = document.createRange();
17 | documentRange.selectNodeContents(element[0].lastChild);
18 | documentRange.collapse(false);
19 | windowSelection = window.getSelection();
20 | windowSelection.removeAllRanges();
21 | windowSelection.addRange(documentRange);
22 | } catch (excp) {
23 | $log.warn('ng-tag-input warning when setting focus', excp);
24 | }
25 | }
26 | , createTags = () => {
27 | try {
28 | let digits = element[0].innerText.split(' ')
29 | , tags = [];
30 |
31 | if (digits &&
32 | digits.length > 0) {
33 | digits.forEach(tag => {
34 | if (tag.trim().length > 0) {
35 | tags.push(`${tag.trim()}`);
36 | } else {
37 | tags.push(tag);
38 | }
39 | });
40 | if (tags &&
41 | tags.length > 0) {
42 | element.html(`${tags.join(' ')} `);
43 | focusTheEnd();
44 | }
45 | }
46 | } catch (excp) {
47 | $log.warn('ng-tag-input warning', excp);
48 | }
49 | }
50 | , updateModel = () => {
51 |
52 | let packages = element[0].innerText.trim().split(' ')
53 | , modelValue = []
54 | , pkgName
55 | , pkgVersion;
56 |
57 | packages.forEach(item => {
58 | if (item.trim() &&
59 | item.trim().length > 0) {
60 |
61 | if (item.includes('@')) {
62 | pkgName = item.split('@')[0].replace('@', '').trim();
63 | pkgVersion = item.split('@')[1].replace('@', '').trim();
64 | } else {
65 | pkgName = item.trim();
66 | pkgVersion = false;
67 | }
68 | modelValue.push({
69 | 'name': pkgName,
70 | 'version': pkgVersion
71 | });
72 | }
73 | });
74 | ngModel.$setViewValue(modelValue);
75 | }
76 | , onBlur = () => {
77 | updateModel();
78 | }
79 | , onKeyUp = event => {
80 | updateModel();
81 | if (element[0].innerText.trim().length > 0 &&
82 | ((event.keyCode && event.keyCode === 32) ||
83 | (event.which && event.which === 32))
84 | ) {
85 | createTags();
86 | updateModel();
87 | }
88 | }
89 | , onKeyDown = event => {
90 | updateModel();
91 | if (event &&
92 | event.keyCode &&
93 | event.keyCode.toString() === '13' &&
94 | element[0].innerText.trim().length > 0) { //enter key to submit form
95 | try {
96 | createTags();
97 | updateModel();
98 | //find button to submit form
99 | angular.element(document.querySelector('#install-new-packages-button'))[0].click();
100 | } catch (excp) {
101 | $log.warn('Cannot find form to submit', excp);
102 | }
103 | }
104 | }
105 | , onKeyPress = () => {
106 | updateModel();
107 | }
108 | , onPaste = () => {
109 | scope.$evalAsync(() => {
110 | createTags();
111 | updateModel();
112 | });
113 | }
114 | , onTrigger = () => {
115 | focusTheEnd();
116 | }
117 | , onKeypressDisabled = event => {
118 | return event.preventDefault();
119 | }
120 | , updateOnSearchChoosenPackage = $rootScope.$on('top-menu:search-choosen-package', (eventInformation, data) => {
121 | if (data &&
122 | data.data &&
123 | data.tabPath &&
124 | data.tabPath === ngTagInputIdentifier) { //if search input is showing on this specific tab
125 |
126 | let newInputValue = '';
127 |
128 | data.data.forEach(pkg => {
129 | newInputValue += pkg.name;
130 | if (pkg.version &&
131 | pkg.version.length > 0) {
132 | newInputValue += `@${pkg.version}`;
133 | }
134 | newInputValue += ' '; //leave a blank space at the end of the string to split into tags again
135 | });
136 |
137 | element[0].innerText = newInputValue;
138 | createTags();
139 | updateModel();
140 | }
141 | });
142 |
143 | element.on('mousedown', onTrigger);
144 | element.on('click', onTrigger);
145 | element.on('paste', onPaste);
146 | element.on('blur', onBlur);
147 | element.on('keyup', onKeyUp);
148 | element.on('keydown', onKeyDown);
149 | element.on('keypress', onKeyPress);
150 | //disable input on submit
151 | attrs.$observe('disabled', value => {
152 | if (value === 'disabled') {
153 | element.on('keypress', onKeypressDisabled);
154 | } else {
155 | element.unbind('keypress', onKeypressDisabled);
156 | }
157 | });
158 | scope.$on('$destroy', () => {
159 | updateOnSearchChoosenPackage();
160 | element.unbind('keyup', onKeyUp);
161 | element.unbind('keydown', onKeyDown);
162 | element.unbind('paste', onPaste);
163 | element.unbind('mousedown', onTrigger);
164 | element.unbind('click', onTrigger);
165 | element.unbind('keypress', onKeyPress);
166 | element.unbind('blur', onBlur);
167 | element.unbind('keypress', onKeypressDisabled);
168 | });
169 | }
170 | };
171 | });
172 |
173 | export default moduleName;
174 |
--------------------------------------------------------------------------------
/lib/scss/prompt.scss:
--------------------------------------------------------------------------------
1 | .dialog {
2 | background: $bg-light;
3 | box-shadow: 1px .5px 3px rgba(0, 0, 0, .29), -.5px .5px .5px rgba(0, 0, 0, .2);
4 | float: none;
5 | left: calc(199px + ((100vw - 199px) / 2) - 226px);
6 | padding: 3px 5px;
7 | position: absolute;
8 | margin: 0 auto;
9 | width: 100%;
10 | max-width: 432px;
11 | z-index: 999999999;
12 | top: 34px;
13 |
14 | &:not(.dialog-window) {
15 | z-index: 999;
16 | border-radius: 1.5px 1.5px 0 0;
17 | left: 194px;
18 | border: 1px solid $color-ddd;
19 | width: calc(100vw - 208px);
20 | box-shadow: none;
21 | max-width: none;
22 | margin-top: 2px;
23 | min-height: 25px;
24 | border-bottom: 0;
25 | padding: 0;
26 |
27 | form {
28 | padding: 1px 3px;
29 | }
30 | }
31 |
32 | i {
33 | margin-left: 4px;
34 | font-size: 15px;
35 | }
36 |
37 | button {
38 | min-width: 13%;
39 | width: auto;
40 | margin-left: 8px;
41 | line-height: 13px;
42 | margin-top: 2px;
43 | font-size: 12px;
44 |
45 | img {
46 | width: 12px;
47 | top: -1px;
48 | position: relative;
49 | margin: 0;
50 | }
51 |
52 | &.disabled {
53 | pointer-events: none;
54 | opacity: .5;
55 | }
56 | }
57 |
58 | &:not(.dialog-window) {
59 | button {
60 | width: 55px;
61 | min-width: auto;
62 |
63 | img {
64 | width: 13px;
65 | }
66 |
67 | &.button-close-prompt {
68 | width: 18px;
69 | max-width: 18px;
70 | min-width: 18px;
71 | text-indent: -2.5px;
72 |
73 | i {
74 | font-size: 11px;
75 | left: -6.5px;
76 | position: relative;
77 | float: left;
78 | }
79 | }
80 | }
81 | }
82 |
83 | select {
84 | width: 77px;
85 | height: 17px;
86 | font-size: 11px;
87 | position: relative;
88 | bottom: -1px;
89 | }
90 |
91 | input {
92 |
93 | &[type="checkbox"] {
94 | vertical-align: middle;
95 | margin: 0;
96 | }
97 | &[type='text'] {
98 | border: none;
99 | line-height: initial;
100 | border-radius: $border-radius-inputs;
101 | box-shadow: 0 1px 1px rgba(0, 0, 0, .25) inset;
102 | font-size: 12px;
103 | padding: 3px 4px 2.5px 4px;
104 | width: 18%;
105 |
106 | &:first-child {
107 | width: 57%;
108 | margin-right: 0;
109 | }
110 | }
111 | }
112 |
113 | .tags-input {
114 | line-height: initial;
115 | border-radius: 3px;
116 | box-shadow: 0 1px 1px rgba(0, 0, 0, .25) inset;
117 | font-size: 12px;
118 | padding: 3px 4px 2.5px 4px;
119 | width: 66%;
120 | background: white;
121 | white-space: nowrap;
122 | overflow: hidden;
123 | float: left;
124 | margin-right: 2%;
125 |
126 | //placeholder for fake contenteditable input
127 | &:empty:before {
128 | color: $color-muted;
129 | content: attr(placeholder);
130 | }
131 | br {
132 | display: none;
133 | }
134 |
135 | * {
136 | display: inline;
137 | white-space: nowrap;
138 | }
139 |
140 | b {
141 | font-weight: normal;
142 | position: relative;
143 | bottom: -.07em;
144 | }
145 |
146 | span {
147 | background: $bg-tags;
148 | border-radius: $border-radius-tags;
149 | border: $border-tags;
150 | padding: 0 3px 1px 3px;
151 | margin: 1px 0 0 0;
152 | }
153 | }
154 |
155 | &.dialog-window {
156 | height: calc(100vh - 39px);
157 | position: fixed;
158 | left: 190px;
159 | border: 0;
160 | box-shadow: none;
161 | top: 10px;
162 | width: 100%;
163 | padding: 0;
164 | border-radius: 0;
165 | background: white;
166 | z-index: 9999;
167 | max-width: calc(100vw - 200px);
168 | border: 1px solid $color-ccc;
169 | }
170 |
171 | .window {
172 | float: none;
173 | height: calc(100vh - 65px);
174 | overflow: auto;
175 | background: white;
176 | font-size: 12.5px;
177 | margin: 0 auto;
178 | margin-top: -4px;
179 |
180 | .logs {
181 | padding: 1px 6px;
182 |
183 | &:first-child {
184 | margin-top: 5px;
185 | }
186 | }
187 | .logs-error {
188 | color: $color-error;
189 | }
190 |
191 | .ng-ace-editor {
192 | height: 100%;
193 | }
194 | }
195 | }
196 |
197 | .prompt-history-item {
198 | line-height: 23px;
199 | width: 100%;
200 | margin: 0 auto;
201 |
202 | &:first-child {
203 | padding-top: 3px;
204 | }
205 | &:hover {
206 | &:not(.active) {
207 | background: $bg-lighter;
208 | }
209 | }
210 |
211 | &.active {
212 | background-color: $bg-muted-invisible;
213 | }
214 |
215 | i {
216 | font-size: 13px;
217 | color: $color-primary;
218 |
219 | &.fa-caret-right {
220 | color: $color-222;
221 | }
222 | }
223 | }
224 |
225 | .prompt-history-status {
226 | width: 100%;
227 | margin: 0 auto;
228 | float: none;
229 | height: 300px;
230 | overflow: auto;
231 | }
232 |
233 | .prompt-window-infos {
234 | font-size: 12.5px;
235 | max-width: 200px;
236 | white-space: nowrap;
237 | overflow: hidden;
238 | text-overflow: ellipsis;
239 | display: inline-block;
240 | }
241 | .prompt-window-holder {
242 | line-height: 100px;
243 | text-align: center;
244 | }
245 | .prompt-window-options {
246 | padding: 0 6px;
247 | width: 100%;
248 | display: inline-block;
249 | background: $bg-light;
250 | margin: 0 auto;
251 | line-height: 25px;
252 | float: none;
253 | height: 30px;
254 |
255 | i {
256 | margin-left: 0;
257 | color: $color-777;
258 | margin-right: 5px;
259 | font-size: 13px;
260 | }
261 |
262 | button {
263 | height: 19.5px;
264 | line-height: 13px;
265 | font-size: 12px;
266 | float: right;
267 | margin-top: 3px;
268 | }
269 |
270 | img {
271 | width: 13px;
272 | }
273 |
274 | .prompt-resize-holder {
275 | width: 130%;
276 | height: 40px;
277 | z-index: -1;
278 | position: absolute;
279 | bottom: -10px;
280 | left: -5%;
281 |
282 | &:hover, &:focus, &:active {
283 | cursor: ns-resize;
284 | }
285 | }
286 | }
287 |
288 | .prompt-search {
289 | font-size: 11.5px;
290 | margin-top: 3px;
291 | color: $color-muted;
292 | overflow: hidden;
293 | overflow-y: auto;
294 | max-height: 39.8vh;
295 | border-bottom: 1px solid $color-ddd;
296 | box-shadow: 10px -10px 10px $bg-eee inset;
297 |
298 | .prompt-search-loader {
299 | color: $color-222;
300 | padding: 1.5px 3.5px 2.5px 3.5px;
301 |
302 | img {
303 | vertical-align: bottom;
304 | margin: 0;
305 | margin-right: 2px;
306 | width: 16px;
307 | }
308 | }
309 |
310 | h5 {
311 | color: $color-222;
312 | margin: 0;
313 | padding: 0;
314 | margin-top: 5px;
315 | font-weight: 500;
316 | font-size: 12px;
317 | margin-bottom: 3px;
318 | }
319 |
320 | .prompt-search-item {
321 | padding: 3px 6px;
322 | border-bottom: 1px solid $bg-muted-invisible;
323 |
324 | &:last-child {
325 | border: 0;
326 | }
327 |
328 | &:hover {
329 | background: $bg-muted-invisible;
330 | }
331 | }
332 | }
333 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true
4 | },
5 | parserOptions: {
6 | ecmaVersion: 6,
7 | sourceType: "module"
8 | },
9 | "rules": {
10 | "comma-dangle": [
11 | 2,
12 | "never"
13 | ],
14 | "no-cond-assign": [
15 | 2,
16 | "always"
17 | ],
18 | "no-console": 1,
19 | "no-constant-condition": 2,
20 | "no-control-regex": 2,
21 | "no-debugger": 2,
22 | "no-dupe-args": 2,
23 | "no-dupe-keys": 2,
24 | "no-duplicate-case": 2,
25 | "no-empty-character-class": 2,
26 | "no-empty": 2,
27 | "no-ex-assign": 1,
28 | "no-extra-boolean-cast": 1,
29 | "no-extra-parens": [
30 | 2,
31 | "functions"
32 | ],
33 | "no-extra-semi": 2,
34 | "no-func-assign": 1,
35 | "no-inner-declarations": [
36 | 2,
37 | "both"
38 | ],
39 | "no-invalid-regexp": 2,
40 | "no-irregular-whitespace": 2,
41 | "no-negated-in-lhs": 2,
42 | "no-obj-calls": 2,
43 | "no-regex-spaces": 2,
44 | "no-sparse-arrays": 2,
45 | "no-unexpected-multiline": 2,
46 | "no-unreachable": 2,
47 | "use-isnan": 2,
48 | "valid-typeof": 2,
49 | "accessor-pairs": 2,
50 | "block-scoped-var": 2,
51 | "consistent-return": 0,
52 | "curly": [
53 | 2,
54 | "all"
55 | ],
56 | "default-case": 2,
57 | "dot-location": [
58 | 2,
59 | "property"
60 | ],
61 | "dot-notation": 2,
62 | "eqeqeq": [
63 | 2,
64 | "smart"
65 | ],
66 | "guard-for-in": 1,
67 | "no-alert": 1,
68 | "no-caller": 2,
69 | "no-case-declarations": 0,
70 | "no-div-regex": 2,
71 | "no-else-return": 2,
72 | "no-labels": 2,
73 | "no-empty-pattern": 2,
74 | "no-eq-null": 2,
75 | "no-eval": 2,
76 | "no-extend-native": 2,
77 | "no-extra-bind": 2,
78 | "no-fallthrough": 2,
79 | "no-floating-decimal": 2,
80 | "no-implicit-coercion": [2,
81 | {
82 | "boolean": true,
83 | "number": true,
84 | "string": true
85 | }
86 | ],
87 | "no-implied-eval": 2,
88 | "no-invalid-this": 2,
89 | "no-iterator": 2,
90 | "no-lone-blocks": 2,
91 | "no-loop-func": 2,
92 | "no-magic-numbers": 0,
93 | "no-multi-spaces": 2,
94 | "no-multi-str": 2,
95 | "no-native-reassign": 2,
96 | "no-new-func": 2,
97 | "no-new-wrappers": 2,
98 | "no-new": 2,
99 | "no-octal-escape": 2,
100 | "no-octal": 2,
101 | "no-param-reassign": 2,
102 | "no-process-env": 1,
103 | "no-proto": 2,
104 | "no-redeclare": [2, {
105 | "builtinGlobals": true
106 | }
107 | ],
108 | "no-return-assign": 2,
109 | "no-script-url": 2,
110 | "no-self-compare": 2,
111 | "no-sequences": 2,
112 | "no-throw-literal": 1,
113 | "no-unused-expressions": 2,
114 | "no-useless-call": 2,
115 | "no-useless-concat": 2,
116 | "no-void": 2,
117 | "no-with": 2,
118 | "radix": 2,
119 | "vars-on-top": 2,
120 | "wrap-iife": [
121 | 2,
122 | "outside"
123 | ],
124 | "yoda": 2,
125 | "strict": [
126 | 2,
127 | "safe"
128 | ],
129 | "no-catch-shadow": 2,
130 | "no-delete-var": 2,
131 | "no-label-var": 2,
132 | "no-shadow-restricted-names": 2,
133 | "no-shadow": [
134 | 2,
135 | {
136 | "builtinGlobals": true
137 | }
138 | ],
139 | "no-undef-init": 2,
140 | "no-undef": 2,
141 | "no-unused-vars": [
142 | 2,
143 | {
144 | "vars": "all",
145 | "args": "after-used"
146 | }
147 | ],
148 | "no-use-before-define": 2,
149 | "callback-return": 1,
150 | "handle-callback-err": 2,
151 | "no-new-require": 2,
152 | "no-path-concat": 2,
153 | "no-process-exit": 0,
154 | "no-sync": 1,
155 | "array-bracket-spacing": [
156 | 2,
157 | "never"
158 | ],
159 | "block-spacing": [
160 | 2,
161 | "always"
162 | ],
163 | "brace-style": [
164 | 2,
165 | "1tbs",
166 | {
167 | "allowSingleLine": false
168 | }
169 | ],
170 | "camelcase": [
171 | 2,
172 | {
173 | "properties": "always"
174 | }
175 | ],
176 | "comma-spacing": [
177 | 2,
178 | {
179 | "before": false,
180 | "after": true
181 | }
182 | ],
183 | "comma-style": [
184 | 2,
185 | "last",
186 | {
187 | "exceptions": {
188 | "VariableDeclaration": true
189 | }
190 | }
191 | ],
192 | "computed-property-spacing": [
193 | 2,
194 | "never"
195 | ],
196 | "consistent-this": [
197 | 2,
198 | "that"
199 | ],
200 | "eol-last": 2,
201 | "func-names": 1,
202 | "func-style": [
203 | 1,
204 | "expression",
205 | {
206 | "allowArrowFunctions": true
207 | }
208 | ],
209 | "id-length": 1,
210 | "key-spacing": [
211 | 2,
212 | {
213 | "beforeColon": false,
214 | "afterColon": true
215 | }
216 | ],
217 | "linebreak-style": [
218 | 2,
219 | "unix"
220 | ],
221 | "new-cap": 2,
222 | "new-parens": 2,
223 | "newline-after-var": [
224 | 2,
225 | "always"
226 | ],
227 | "no-array-constructor": 2,
228 | "no-bitwise": 2,
229 | "no-continue": 2,
230 | "no-lonely-if": 2,
231 | "no-mixed-spaces-and-tabs": 2,
232 | "no-multiple-empty-lines": [
233 | 1,
234 | {
235 | "max": 2,
236 | "maxEOF": 1
237 | }
238 | ],
239 | "no-negated-condition": 1,
240 | "no-nested-ternary": 2,
241 | "no-new-object": 2,
242 | "no-plusplus": 2,
243 | "no-spaced-func": 2,
244 | "no-ternary": 0,
245 | "no-trailing-spaces": [
246 | 2,
247 | {
248 | "skipBlankLines": false
249 | }
250 | ],
251 | "no-underscore-dangle": 2,
252 | "no-unneeded-ternary": 2,
253 | "object-curly-spacing": [
254 | 2,
255 | "never"
256 | ],
257 | "one-var": 2,
258 | "operator-assignment": [
259 | 2,
260 | "always"
261 | ],
262 | "operator-linebreak": [
263 | 2,
264 | "after"
265 | ],
266 | "quote-props": 2,
267 | "quotes": [
268 | 2,
269 | "single",
270 | "avoid-escape"
271 | ],
272 | "semi-spacing": 2,
273 | "semi": [
274 | 2,
275 | "always"
276 | ],
277 | "keyword-spacing": [
278 | 2,
279 | {
280 | "before": true,
281 | "after": true
282 | }
283 | ],
284 | "space-before-blocks": [
285 | 2,
286 | "always"
287 | ],
288 | "space-before-function-paren": [
289 | 2,
290 | "never"
291 | ],
292 | "space-in-parens": [
293 | 2,
294 | "never"
295 | ],
296 | "space-infix-ops": 2,
297 | "space-unary-ops": [
298 | 2,
299 | {
300 | "words": true,
301 | "nonwords": false
302 | }
303 | ],
304 | "wrap-regex": 2,
305 | "arrow-parens": [
306 | 2,
307 | "as-needed"
308 | ],
309 | "arrow-spacing": [
310 | 2,
311 | {
312 | "before": true,
313 | "after": true
314 | }
315 | ],
316 | "constructor-super": 2,
317 | "generator-star-spacing": [
318 | 2,
319 | {
320 | "before": false,
321 | "after": true
322 | }
323 | ],
324 | "no-confusing-arrow": 1,
325 | "no-class-assign": 2,
326 | "no-const-assign": 2,
327 | "no-dupe-class-members": 1,
328 | "no-this-before-super": 2,
329 | "no-var": 1,
330 | "object-shorthand": [
331 | 2,
332 | "always"
333 | ],
334 | "prefer-arrow-callback": 1,
335 | "prefer-const": 1,
336 | "prefer-spread": 1,
337 | "prefer-template": 2,
338 | "require-yield": 2
339 | }
340 | }
341 |
--------------------------------------------------------------------------------
/lib/js/npm/npm-api.js:
--------------------------------------------------------------------------------
1 | /*global require navigator process,__dirname*/
2 | import angular from 'angular';
3 | const moduleName = 'npm-api.service'
4 | , fs = require('fs')
5 | , path = require('path')
6 | , cp = require('child_process');
7 |
8 | angular.module(moduleName, [])
9 | .service('npm', /*@ngInject*/ function NpmService($rootScope, $log, errorsService) {
10 | const configureNpm = (folder, isGlobal) => new Promise(resolve => {
11 | const forkFactory = (command, param1, param2) => new Promise((forkFactoryResolved, forkFactoryRejected) => {
12 | const theParams = [folder, isGlobal]
13 | .concat([command, param1, param2])
14 | .map(element => {
15 |
16 | if (Object.prototype.toString.call(element) === '[object Object]') {
17 |
18 | return JSON.stringify(element);
19 | }
20 |
21 | return element;
22 | })
23 | , child = cp.fork(path.resolve(__dirname, 'npm-runner.js'), theParams, {
24 | 'cwd': __dirname,
25 | 'silent': true
26 | });
27 |
28 | child.stdout.on('data', data => {
29 | $log.info(`stdout: ${data}`);
30 | });
31 |
32 | child.stderr.on('data', data => {
33 | forkFactoryRejected(data);
34 | $log.error(`stderr: ${data}`);
35 | });
36 |
37 | child.on('close', code => {
38 | $rootScope.$emit('npm:log:end', {
39 | 'type': command,
40 | 'message': code
41 | });
42 | $log.info(`End of command ${command}`);
43 | $log.info(`child process exited with code ${code}`);
44 | });
45 |
46 | child.on('message', message => {
47 |
48 | if (command === message.type) {
49 |
50 | forkFactoryResolved(message.payload);
51 | } else if (message.type === 'log') {
52 |
53 | $rootScope.$emit('npm:log:log', {
54 | 'type': command,
55 | 'data': message.payload
56 | });
57 | } else {
58 |
59 | $log.debug(message);
60 | }
61 | });
62 | });
63 |
64 | resolve({
65 | 'ping': () => {
66 | return forkFactory('ping');
67 | },
68 | 'launchInstall': () => {
69 | return forkFactory('launchInstall');
70 | },
71 | 'search': keyword => {
72 | return forkFactory('search', keyword);
73 | },
74 | 'run': scriptName => {
75 | return forkFactory('run', scriptName);
76 | },
77 | 'view': packageName => {
78 | return forkFactory('view', packageName);
79 | },
80 | 'build': buildFolder => {
81 | return forkFactory('build', buildFolder);
82 | },
83 | 'rebuild': () => {
84 | return forkFactory('rebuild');
85 | },
86 | 'install': (dependency, version) => {
87 | return forkFactory('install', dependency, version);
88 | },
89 | 'installLatest': dependency => {
90 | return forkFactory('installLatest', dependency);
91 | },
92 | 'update': dependency => {
93 | return forkFactory('update', dependency);
94 | },
95 | 'rm': dependency => {
96 | return forkFactory('rm', dependency);
97 | },
98 | 'listOutdated': () => {
99 | return forkFactory('listOutdated');
100 | },
101 | 'outdated': () => {
102 | return forkFactory('outdated');
103 | },
104 | 'prune': () => {
105 | return forkFactory('prune');
106 | },
107 | 'dedupe': () => {
108 | return forkFactory('dedupe');
109 | },
110 | 'list': () => {
111 | return forkFactory('list');
112 | },
113 | 'shrinkwrap': () => {
114 | return forkFactory('shrinkwrap');
115 | },
116 | 'doctor': () => {
117 | return forkFactory('doctor');
118 | },
119 | 'root': () => {
120 | return forkFactory('root');
121 | }
122 | });
123 | })
124 | , getNpmVersion = () => new Promise(resolve => {
125 | cp.exec('npm -v', (err, stdout, stderr) => {
126 | let npmVersion;
127 |
128 | $log.warn(err, stderr);
129 |
130 | if (stdout &&
131 | stdout.length > 0) {
132 | npmVersion = stdout.toString();
133 | }
134 | resolve(npmVersion);
135 | });
136 | })
137 | , isNpmGloballyInstalled = () => new Promise((resolve, reject) => {
138 | cp.exec('npm -v', err => {
139 | if (err) {
140 | reject(err);
141 | } else {
142 | resolve();
143 | }
144 | });
145 | })
146 | , pingRegistry = () => new Promise((resolve, reject) => {
147 | if (navigator.onLine) {
148 | return configureNpm('').then(npm => {
149 |
150 | return npm.ping()
151 | .then(resolve)
152 | .catch(reject);
153 | })
154 | .catch(err => {
155 | $log.warn('Ping registry', err);
156 | reject(err);
157 | });
158 | }
159 |
160 | $log.warn('You are offline: unable to ping registry.');
161 | return reject();
162 | })
163 | , outdatedGlobalVersion = () => new Promise(resolve => {
164 | this.npmGlobal()
165 | .catch(error => errorsService.showErrorBox('Npm error', `Error during configuring npm for asking the globally installed version: ${error}`))
166 | .then(npmInFolder => {
167 |
168 | return npmInFolder.outdated()
169 | .then(resolve)
170 | .catch(error => errorsService.showErrorBox('Npm error', `Error asking the globally installed version: ${error}`));
171 | });
172 | });
173 |
174 | this.updateNpmGlobally = () => {
175 | const npmLib = {
176 | 'name': 'npm'
177 | };
178 |
179 | return new Promise((resolve, reject) => {
180 |
181 | this.npmGlobal()
182 | .catch(error => errorsService.showErrorBox('Npm error', `Error configuring npm for installing latest ${npmLib.name}: ${error} `))
183 | .then(npmInFolder => {
184 |
185 | $rootScope.$emit('npm:log:start');
186 | npmInFolder.installLatest(npmLib)
187 | .then(() => {
188 |
189 | $rootScope.$emit('npm:log:stop');
190 | resolve();
191 | })
192 | .catch(error => {
193 |
194 | $rootScope.$emit('npm:log:stop');
195 | reject(error);
196 | });
197 | });
198 | });
199 | };
200 |
201 | $rootScope.$on('dom:ready', () => {
202 | $log.info('DOM is ready for npm');
203 | //sync shell path or app will not work, yep.
204 | process.env.PATH = require('shell-path').sync();
205 |
206 | cp.exec('npm root -g', (err, stdout, stderr) => {
207 |
208 | if (err) {
209 |
210 | throw new Error(err);
211 | }
212 |
213 | if (stderr) {
214 |
215 | $log.warn(stderr);
216 | }
217 |
218 | let globalFolder = stdout
219 | , nodeModulesExt = ''; //important
220 |
221 | if (process.platform != 'win32') {
222 |
223 | globalFolder = globalFolder.replace('/lib/node_modules', '');
224 | } else {
225 |
226 | globalFolder = globalFolder.replace('node_modules', '');
227 | }
228 |
229 | globalFolder = globalFolder.trim();
230 |
231 | this.npmGlobal = () => {
232 |
233 | return configureNpm(globalFolder, true);
234 | };
235 |
236 | if (process.platform &&
237 | process.platform !== 'win32') {
238 | //on windows it doesn't exists
239 | nodeModulesExt = '/lib/node_modules';
240 | }
241 |
242 | fs.stat(`${globalFolder}${nodeModulesExt}`, (statError, stats) => {
243 |
244 | if (statError) {
245 |
246 | $log.error(statError);
247 | }
248 |
249 | if (process.platform === 'win32') {
250 | //win if error occur it means that folder is not writable and so we set the check to fail.
251 | if (statError) {
252 | $rootScope.$apply(scope => {
253 |
254 | scope.$emit('npm:global-privilege-check', {
255 | 'user': -1,
256 | 'processUser': 1,
257 | 'group': -1,
258 | 'processGroup': 1
259 | });
260 | });
261 | }
262 |
263 | return $rootScope.$apply(scope => {
264 |
265 | scope.$emit('npm:global-privilege-check', {
266 | 'user': 1,
267 | 'processUser': 1,
268 | 'group': 1,
269 | 'processGroup': 1
270 | });
271 | });
272 | }
273 |
274 | return $rootScope.$apply(scope => {
275 |
276 | scope.$emit('npm:global-privilege-check', {
277 | 'user': stats.uid,
278 | 'processUser': process.getuid(),
279 | 'group': stats.gid,
280 | 'processGroup': process.getgid()
281 | });
282 | });
283 | });
284 |
285 | $rootScope.$emit('npm:ready');
286 | });
287 | });
288 |
289 | this.npmInFolder = configureNpm;
290 | this.outdatedGlobalVersion = outdatedGlobalVersion;
291 | this.getNpmVersion = getNpmVersion;
292 | this.isNpmGloballyInstalled = isNpmGloballyInstalled;
293 | this.pingRegistry = pingRegistry;
294 | });
295 |
296 | export default moduleName;
297 |
--------------------------------------------------------------------------------
/lib/icons/fontello/font/fontello.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ndm
2 |
3 | 
4 |
5 |
6 | The Open Source npm desktop GUI.
7 |
8 | Runs on Linux, MacOS and Windows.
9 |
10 | **ndm** stands for **"npm desktop manager"**.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | About ndm
31 | |
32 | Develop it |
33 | Build it
34 | |
35 | Contribute
36 | |
37 | Recommendations
38 | |
39 | FAQ
40 | |
41 | License
42 |
43 |
44 |
45 | ## Download
46 | **[Download for MacOS](https://720kb.github.io/ndm#mac)** | **[Download for Linux](https://720kb.github.io/ndm#linux)** | **[Download for Windows](https://720kb.github.io/ndm#win)**
47 |
48 | ###### You can browse all the releases at [github.com/720kb/ndm/releases](https://github.com/720kb/ndm/releases)
49 |
50 |
51 |
52 | ## Homebrew
53 |
54 | On MacOS you can install **ndm** also with [Homebrew Cask](https://caskroom.github.io/):
55 |
56 | ```bash
57 | $ brew update
58 | $ brew cask install ndm
59 | ```
60 |
61 | ## Arch Linux
62 |
63 | On Linux you can install **ndm** also like this:
64 |
65 | ```bash
66 | $ yaourt -S ndm
67 | ```
68 |
69 | ## Debian
70 |
71 | On Debian based linux is possible to install **ndm** doing:
72 |
73 | ```bash
74 | $ echo "deb [trusted=yes] https://apt.fury.io/720kb/ /" | sudo tee
75 | /etc/apt/sources.list.d/ndm.list && sudo apt-get update && sudo apt-get install ndm
76 | ```
77 |
78 | ## RedHat
79 |
80 | On RedHat based linux is possible to install **ndm** doing:
81 |
82 | ```bash
83 | echo "[fury]
84 | name=ndm repository
85 | baseurl=https://repo.fury.io/720kb/
86 | enabled=1
87 | gpgcheck=0" | sudo tee /etc/yum.repos.d/ndm.repo && sudo yum update && sudo yum install ndm
88 | ```
89 |
90 | **Core team**
91 | [720kb](https://720kb.net)
92 |
93 | **Contributors** [All the awesome contributors](https://github.com/720kb/ndm/graphs/contributors)
94 |
95 |
96 | ## Support ndm
97 |
98 | > Donating to an open source project is the best way to tie your love for it.
99 |
100 | If you enjoy **ndm** consider donating to the project and help mantain and continuously improve it!
101 |
102 | **Backers**
103 |
104 | Support us with a monthly donation and help us continue our activities. [Become a backer](https://opencollective.com/ndm#backer)
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | **Sponsors**
138 |
139 | Become a sponsor and get your logo on our README on Github with a link to your site. [Become a sponsor](https://opencollective.com/ndm#sponsor)
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/lib/js/npm/npm-operations.js:
--------------------------------------------------------------------------------
1 | /* global process,require */
2 | const isGlobalSym = Symbol('isGlobal')
3 | , npmSym = Symbol('npm')
4 | , whereSym = Symbol('where')
5 | , fetch = require('node-fetch')
6 | , swapFolderAndGlobal = function SwapFolderAndGlobal(prefix, isGlobal) {
7 |
8 | const oldPrefix = this[npmSym].config.prefix
9 | , oldGlobal = this[npmSym].config.global;
10 |
11 | this[npmSym].config.prefix = prefix;
12 | this[npmSym].config.global = isGlobal;
13 |
14 | return [oldPrefix, oldGlobal];
15 | };
16 |
17 |
18 | class NpmOperations {
19 |
20 | constructor(where, configuredNpm, isGlobal) {
21 |
22 | this[whereSym] = where;
23 | this[npmSym] = configuredNpm;
24 | this[isGlobalSym] = isGlobal;
25 | }
26 |
27 | ping() {
28 | return new Promise((resolvePing, rejectPing) => {
29 | this[npmSym].commands.ping('', err => {
30 | if (err) {
31 |
32 | return rejectPing(err);
33 | }
34 |
35 | return resolvePing();
36 | });
37 | });
38 | }
39 |
40 | launchInstall() {
41 |
42 | return new Promise((resolveInstall, rejectInstall) => {
43 | this[npmSym].commands.install(this[whereSym], [], err => {
44 | if (err) {
45 |
46 | return rejectInstall(err);
47 | }
48 |
49 | return resolveInstall();
50 | });
51 | });
52 | }
53 |
54 | search(keyword) {
55 |
56 | return fetch(`https://registry.npmjs.org/-/v1/search?text=${keyword}&size=25`)
57 | .then(res => res.json());
58 | }
59 |
60 | run(scriptName) {
61 | return new Promise((resolveRun, rejectRun) => {
62 | this[npmSym].commands.runScript([scriptName], (err, infos) => {
63 | if (err) {
64 |
65 | return rejectRun(err);
66 | }
67 |
68 | return resolveRun(infos);
69 | });
70 | });
71 | }
72 |
73 | view(packageName) {
74 | return new Promise((resolveView, rejectView) => {
75 |
76 | this[npmSym].commands.view([packageName], (err, infos) => {
77 | if (err) {
78 |
79 | return rejectView(err);
80 | }
81 |
82 | return resolveView(infos);
83 | });
84 | });
85 | }
86 |
87 | build(folder) {
88 |
89 | return new Promise((resolveBuild, rejectBuild) => {
90 |
91 | this[npmSym].commands.build([folder], (err, infos) => {
92 | if (err) {
93 |
94 | return rejectBuild(err);
95 | }
96 |
97 | return resolveBuild(infos);
98 | });
99 | });
100 | }
101 |
102 | rebuild() {
103 |
104 | return new Promise((resolveRebuild, rejectRebuild) => {
105 |
106 | this[npmSym].commands.rebuild([], (err, infos) => {
107 | if (err) {
108 | return rejectRebuild(err);
109 | }
110 | return resolveRebuild(infos);
111 | });
112 | });
113 | }
114 |
115 | install(dependency, version) {
116 | let dependencyToSubmit = dependency.name;
117 |
118 | if (version &&
119 | version !== 'false' &&
120 | version !== 'undefined') {
121 |
122 | dependencyToSubmit += `@${version}`;
123 | }
124 |
125 | return new Promise((resolveInstall, rejectInstall) => {
126 | const toInstall = [dependencyToSubmit]
127 | , whereToInstall = this[isGlobalSym] ? '' : this[whereSym];
128 |
129 | if (!this[isGlobalSym] &&
130 | dependency.kind === 'dev') {
131 |
132 | this[npmSym].config.set('save-dev', true);
133 | } else if (!this[isGlobalSym]) {
134 |
135 | this[npmSym].config.set('save', true);
136 | }
137 |
138 | this[npmSym].commands.install(whereToInstall, toInstall, err => {
139 |
140 | if (err) {
141 |
142 | return rejectInstall(err);
143 | }
144 |
145 | if (!this[isGlobalSym]) {
146 |
147 | this[npmSym].config.set('save-dev', false);
148 | this[npmSym].config.set('save', false);
149 | }
150 | return resolveInstall();
151 | });
152 | });
153 | }
154 |
155 | installLatest(dependency) {
156 |
157 | return this.install(dependency, dependency.latest);
158 | }
159 |
160 | update(dependency) {
161 |
162 | return new Promise((resolveUpdate, rejectUpdate) => {
163 | const toUpdate = [dependency.name];
164 |
165 | if (!this[isGlobalSym] &&
166 | dependency.kind === 'dev') {
167 |
168 | this[npmSym].config.set('save-dev', true);
169 | } else if (!this[isGlobalSym]) {
170 |
171 | this[npmSym].config.set('save', true);
172 | }
173 |
174 | this[npmSym].commands.update(toUpdate, err => {
175 |
176 | if (err) {
177 |
178 | return rejectUpdate(err);
179 | }
180 |
181 | if (!this[isGlobalSym]) {
182 |
183 | this[npmSym].config.set('save-dev', false);
184 | this[npmSym].config.set('save', false);
185 | }
186 | return resolveUpdate();
187 | });
188 | });
189 | }
190 |
191 | rm(dependency) {
192 |
193 | return new Promise((resolveRm, rejectRm) => {
194 | const toRemove = [dependency.name];
195 |
196 | if (!this[isGlobalSym] &&
197 | dependency.kind === 'dev') {
198 |
199 | this[npmSym].config.set('save-dev', true);
200 | } else if (!this[isGlobalSym]) {
201 |
202 | this[npmSym].config.set('save', true);
203 | }
204 |
205 | this[npmSym].commands.rm(toRemove, err => {
206 |
207 | if (err) {
208 |
209 | return rejectRm(err);
210 | }
211 |
212 | if (!this[isGlobalSym]) {
213 |
214 | this[npmSym].config.set('save-dev', false);
215 | this[npmSym].config.set('save', false);
216 | }
217 | return resolveRm();
218 | });
219 | });
220 | }
221 |
222 | listOutdated() {
223 | return new Promise((listOutdatedResolve, listOutdatedReject) => {
224 |
225 | Promise.all([this.list(), this.outdated()])
226 | .then(resolved => {
227 |
228 | if (resolved &&
229 | Array.isArray(resolved) &&
230 | resolved.length === 2) {
231 | const outdatedList = resolved[1]
232 | , listList = resolved[0];
233 | let toResolve = [];
234 |
235 | listList.forEach(element => {
236 |
237 | if (element &&
238 | element.name) {
239 | const outdatedData = outdatedList.filter(filterElement => {
240 | return filterElement && filterElement.name === element.name;
241 | }).map(mapElement => ({
242 | 'name': element.name,
243 | 'kind': element.kind,
244 | 'current': mapElement.current,
245 | 'wanted': mapElement.wanted,
246 | 'latest': mapElement.latest
247 | }));
248 |
249 | if (outdatedData.length > 0) {
250 |
251 | toResolve = toResolve.concat(outdatedData);
252 | } else {
253 |
254 | toResolve.push(element);
255 | }
256 | }
257 | });
258 |
259 | return listOutdatedResolve(toResolve);
260 | }
261 |
262 | return listOutdatedReject('Output from list and oudated commands wrong!');
263 | })
264 | .catch(err => listOutdatedReject(err));
265 | });
266 | }
267 |
268 | outdated() {
269 | return new Promise((resolveOutdated, rejectOutdated) => {
270 | const [oldPrefix, oldGlobal] = swapFolderAndGlobal.apply(this, [this[whereSym], this[isGlobalSym]]);
271 |
272 | this[npmSym].commands.outdated([], true, (outdatedError, packageInformations) => {
273 |
274 | if (outdatedError) {
275 |
276 | this[npmSym].config.prefix = oldPrefix;
277 | this[npmSym].config.global = oldGlobal;
278 | return rejectOutdated(outdatedError);
279 | }
280 |
281 | if (packageInformations &&
282 | Array.isArray(packageInformations)) {
283 | const toResolve = [];
284 |
285 | for (const aPackageInformation of packageInformations) {
286 |
287 | toResolve.push({
288 | 'name': aPackageInformation[1],
289 | 'current': aPackageInformation[2],
290 | 'wanted': aPackageInformation[3],
291 | 'latest': aPackageInformation[4]
292 | });
293 | }
294 |
295 | this[npmSym].config.prefix = oldPrefix;
296 | this[npmSym].config.global = oldGlobal;
297 | return resolveOutdated(toResolve);
298 | }
299 |
300 | return rejectOutdated('Package informations from outdated are wrong!');
301 | });
302 | });
303 | }
304 |
305 | prune() {
306 | return new Promise((resolvePrune, rejectPrune) => {
307 | const oldCWD = process.cwd();
308 |
309 | process.chdir(this[whereSym]);
310 | this[npmSym].commands.prune([], (pruneError, packageInformations) => {
311 |
312 | process.chdir(oldCWD);
313 | if (pruneError) {
314 |
315 | return rejectPrune(pruneError);
316 | }
317 |
318 | return resolvePrune(packageInformations);
319 | });
320 | });
321 | }
322 |
323 | dedupe() {
324 | return new Promise((resolveDedupe, rejectDedupe) => {
325 |
326 | this[npmSym].commands.dedupe([], (DedupeError, packageInformations) => {
327 |
328 | if (DedupeError) {
329 |
330 | return rejectDedupe(DedupeError);
331 | }
332 |
333 | return resolveDedupe(packageInformations);
334 | });
335 | });
336 | }
337 |
338 | list() {
339 | return new Promise((resolveList, rejectList) => {
340 | const [oldPrefix, oldGlobal] = swapFolderAndGlobal.apply(this, [this[whereSym], this[isGlobalSym]]);
341 |
342 | this[npmSym].commands.list([], true, (listError, packageInformations) => {
343 |
344 | if (listError) {
345 |
346 | this[npmSym].config.prefix = oldPrefix;
347 | this[npmSym].config.global = oldGlobal;
348 | return rejectList(listError);
349 | }
350 |
351 | if (packageInformations &&
352 | packageInformations.dependencies &&
353 | packageInformations.devDependencies) {
354 | const toResolve = []
355 | , dependenciesKeys = Object.keys(packageInformations.dependencies)
356 | , dependenciesKeysLength = dependenciesKeys.length
357 | , devDependenciesKeys = Object.keys(packageInformations.devDependencies)
358 | , filteringFunction = function FilteringFunction(element) {
359 |
360 | return element && element.name !== packageInformations.dependencies[this];
361 | }
362 | , filterIsADevDependency = function filterIsADevDependency(aDependency, element) {
363 |
364 | return element && element === aDependency;
365 | };
366 |
367 | for (let dependenciesKeysIndex = 0; dependenciesKeysIndex < dependenciesKeysLength; dependenciesKeysIndex += 1) {
368 | const aDependencyKey = dependenciesKeys[dependenciesKeysIndex];
369 |
370 | if (aDependencyKey &&
371 | toResolve.every(filteringFunction, aDependencyKey)) {
372 | const aDependency = packageInformations.dependencies[aDependencyKey]
373 | , isADevDependency = devDependenciesKeys.filter(filterIsADevDependency.bind(devDependenciesKeys, aDependencyKey));
374 |
375 | toResolve.push({
376 | 'name': aDependencyKey,
377 | 'current': aDependency.version,
378 | 'kind': isADevDependency[0] ? 'dev' : ''
379 | });
380 | }
381 | }
382 |
383 | this[npmSym].config.prefix = oldPrefix;
384 | this[npmSym].config.global = oldGlobal;
385 | return resolveList(toResolve);
386 | }
387 |
388 | return rejectList('Package informations from list command are wrong!');
389 | });
390 | });
391 | }
392 |
393 | shrinkwrap() {
394 | return new Promise((resolveShrink, rejectShrink) => {
395 |
396 | this[npmSym].commands.shrinkwrap([], (shrinkError, infos) => {
397 |
398 | if (shrinkError) {
399 |
400 | return rejectShrink(shrinkError);
401 | }
402 |
403 | return resolveShrink(infos);
404 | });
405 | });
406 | }
407 |
408 | doctor() {
409 | return new Promise((resolveDoctor, rejectDoctor) => {
410 |
411 | this[npmSym].commands.doctor((doctorErr, doctorInfo) => {
412 |
413 | if (doctorErr) {
414 |
415 | return rejectDoctor(doctorErr);
416 | }
417 |
418 | return resolveDoctor(doctorInfo);
419 | });
420 | });
421 | }
422 |
423 | root() {
424 | return new Promise((resolveRoot, rejectRoot) => {
425 |
426 | this[npmSym].commands.root([], (rootError, rootInfo) => {
427 |
428 | if (rootError) {
429 |
430 | return rejectRoot(rootError);
431 | }
432 |
433 | return resolveRoot(rootInfo);
434 | });
435 | });
436 | }
437 | }
438 |
439 | export default NpmOperations;
440 |
--------------------------------------------------------------------------------