├── .github └── no-response.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── appveyor.yml ├── lib ├── update-package-dependencies-status-view.js └── update-package-dependencies.js ├── package.json ├── spec ├── async-spec-helpers.js └── update-package-dependencies-spec.js └── styles └── update-package-dependencies.less /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an issue is closed for lack of response 4 | daysUntilClose: 28 5 | 6 | # Label requiring a response 7 | responseRequiredLabel: more-information-needed 8 | 9 | # Comment to post when closing an issue for lack of response. Set to `false` to disable. 10 | closeComment: > 11 | This issue has been automatically closed because there has been no response 12 | to our request for more information from the original author. With only the 13 | information that is currently in the issue, we don't have enough information 14 | to take action. Please reach out if you have or find the answers we need so 15 | that we can investigate further. 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | notifications: 4 | email: 5 | on_success: never 6 | on_failure: change 7 | 8 | script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 9 | 10 | branches: 11 | only: 12 | - master 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | See the [Atom contributing guide](https://atom.io/docs/latest/contributing) 2 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### Prerequisites 10 | 11 | * [ ] Put an X between the brackets on this line if you have done all of the following: 12 | * Reproduced the problem in Safe Mode: http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode 13 | * Followed all applicable steps in the debugging guide: http://flight-manual.atom.io/hacking-atom/sections/debugging/ 14 | * Checked the FAQs on the message board for common solutions: https://discuss.atom.io/c/faq 15 | * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aatom 16 | * Checked that there is not already an Atom package that provides the described functionality: https://atom.io/packages 17 | 18 | ### Description 19 | 20 | [Description of the issue] 21 | 22 | ### Steps to Reproduce 23 | 24 | 1. [First Step] 25 | 2. [Second Step] 26 | 3. [and so on...] 27 | 28 | **Expected behavior:** [What you expect to happen] 29 | 30 | **Actual behavior:** [What actually happens] 31 | 32 | **Reproduces how often:** [What percentage of the time does it reproduce?] 33 | 34 | ### Versions 35 | 36 | You can get this information from copy and pasting the output of `atom --version` and `apm --version` from the command line. Also, please include the OS and what version of the OS you're running. 37 | 38 | ### Additional Information 39 | 40 | Any additional information, configuration or data that might be necessary to reproduce the issue. 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ### Description of the Change 7 | 8 | 13 | 14 | ### Alternate Designs 15 | 16 | 17 | 18 | ### Benefits 19 | 20 | 21 | 22 | ### Possible Drawbacks 23 | 24 | 25 | 26 | ### Applicable Issues 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This package is now a part of the [core Atom repository](https://github.com/atom/atom/tree/master/packages/update-package-dependencies). Please direct all issues and pull requests there in the future! 2 | 3 | --- 4 | 5 | ## Update Package Dependencies package 6 | [![OS X Build Status](https://travis-ci.org/atom/update-package-dependencies.svg?branch=master)](https://travis-ci.org/atom/update-package-dependencies) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5xqtoc3xk1e7lt2y/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/update-package-dependencies/branch/master) [![Dependency Status](https://david-dm.org/atom/update-package-dependencies.svg)](https://david-dm.org/atom/update-package-dependencies) 7 | 8 | Runs `apm install` from the current project's directory. This will install all dependencies referenced in the `package.json` file to the `node_modules` folder. 9 | 10 | This should only be used in projects that are Atom packages. 11 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | platform: x64 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | clone_depth: 10 10 | 11 | skip_tags: true 12 | 13 | environment: 14 | APM_TEST_PACKAGES: 15 | 16 | matrix: 17 | - ATOM_CHANNEL: stable 18 | - ATOM_CHANNEL: beta 19 | 20 | install: 21 | - ps: Install-Product node 4 22 | 23 | build_script: 24 | - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) 25 | 26 | test: off 27 | deploy: off 28 | -------------------------------------------------------------------------------- /lib/update-package-dependencies-status-view.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | class UpdatePackageDependenciesStatusView { 3 | constructor (statusBar) { 4 | this.statusBar = statusBar 5 | this.element = document.createElement('update-package-dependencies-status') 6 | this.element.classList.add('update-package-dependencies-status', 'inline-block', 'is-read-only') 7 | this.spinner = document.createElement('span') 8 | this.spinner.classList.add('loading', 'loading-spinner-tiny', 'inline-block') 9 | this.element.appendChild(this.spinner) 10 | } 11 | 12 | attach () { 13 | this.tile = this.statusBar.addRightTile({item: this.element}) 14 | this.tooltip = atom.tooltips.add(this.element, {title: 'Updating package dependencies\u2026'}) 15 | } 16 | 17 | detach () { 18 | if (this.tile) this.tile.destroy() 19 | if (this.tooltip) this.tooltip.dispose() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/update-package-dependencies.js: -------------------------------------------------------------------------------- 1 | const {BufferedProcess} = require('atom') 2 | const UpdatePackageDependenciesStatusView = require('./update-package-dependencies-status-view') 3 | 4 | module.exports = { 5 | activate () { 6 | this.subscription = atom.commands.add('atom-workspace', 'update-package-dependencies:update', () => this.update()) 7 | }, 8 | 9 | deactivate () { 10 | this.subscription.dispose() 11 | if (this.updatePackageDependenciesStatusView) { 12 | this.updatePackageDependenciesStatusView.detach() 13 | this.updatePackageDependenciesStatusView = null 14 | } 15 | }, 16 | 17 | consumeStatusBar (statusBar) { 18 | this.updatePackageDependenciesStatusView = new UpdatePackageDependenciesStatusView(statusBar) 19 | }, 20 | 21 | update () { 22 | if (this.process) return // Do not allow multiple apm processes to run 23 | if (this.updatePackageDependenciesStatusView) this.updatePackageDependenciesStatusView.attach() 24 | 25 | let errorOutput = '' 26 | 27 | const command = atom.packages.getApmPath() 28 | const args = ['install', '--no-color'] 29 | const stderr = output => { errorOutput += output } 30 | const options = {cwd: this.getActiveProjectPath(), env: Object.assign({}, process.env, {NODE_ENV: 'development'})} 31 | 32 | const exit = code => { 33 | this.process = null 34 | if (this.updatePackageDependenciesStatusView) this.updatePackageDependenciesStatusView.detach() 35 | 36 | if (code === 0) { 37 | atom.notifications.addSuccess('Package dependencies updated') 38 | } else { 39 | atom.notifications.addError('Failed to update package dependencies', {detail: errorOutput, dismissable: true}) 40 | } 41 | } 42 | 43 | this.process = this.runBufferedProcess({command, args, stderr, exit, options}) 44 | }, 45 | 46 | // This function exists so that it can be spied on by tests 47 | runBufferedProcess (params) { 48 | return new BufferedProcess(params) 49 | }, 50 | 51 | getActiveProjectPath () { 52 | const activeItem = atom.workspace.getActivePaneItem() 53 | if (activeItem && typeof activeItem.getPath === 'function') { 54 | return atom.project.relativizePath(activeItem.getPath())[0] 55 | } else { 56 | return atom.project.getPaths()[0] 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "update-package-dependencies", 3 | "main": "./lib/update-package-dependencies", 4 | "version": "0.13.1", 5 | "private": true, 6 | "description": "Runs `apm install` for the current project", 7 | "repository": "https://github.com/atom/update-package-dependencies", 8 | "license": "MIT", 9 | "engines": { 10 | "atom": ">0.39.0" 11 | }, 12 | "activationCommands": { 13 | "atom-workspace": [ 14 | "update-package-dependencies:update" 15 | ] 16 | }, 17 | "consumedServices": { 18 | "status-bar": { 19 | "versions": { 20 | "^1.1.0": "consumeStatusBar" 21 | } 22 | } 23 | }, 24 | "dependencies": {}, 25 | "devDependencies": { 26 | "standard": "^10.0.3" 27 | }, 28 | "standard": { 29 | "env": { 30 | "atomtest": true, 31 | "browser": true, 32 | "jasmine": true, 33 | "node": true 34 | }, 35 | "globals": [ 36 | "atom" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spec/async-spec-helpers.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | export function beforeEach (fn) { 4 | global.beforeEach(function () { 5 | const result = fn() 6 | if (result instanceof Promise) { 7 | waitsForPromise(() => result) 8 | } 9 | }) 10 | } 11 | 12 | export function afterEach (fn) { 13 | global.afterEach(function () { 14 | const result = fn() 15 | if (result instanceof Promise) { 16 | waitsForPromise(() => result) 17 | } 18 | }) 19 | } 20 | 21 | ['it', 'fit', 'ffit', 'fffit'].forEach(function (name) { 22 | module.exports[name] = function (description, fn) { 23 | if (fn === undefined) { 24 | global[name](description) 25 | return 26 | } 27 | 28 | global[name](description, function () { 29 | const result = fn() 30 | if (result instanceof Promise) { 31 | waitsForPromise(() => result) 32 | } 33 | }) 34 | } 35 | }) 36 | 37 | export async function conditionPromise (condition, description = 'anonymous condition') { 38 | const startTime = Date.now() 39 | 40 | while (true) { 41 | await timeoutPromise(100) 42 | 43 | if (await condition()) { 44 | return 45 | } 46 | 47 | if (Date.now() - startTime > 5000) { 48 | throw new Error('Timed out waiting on ' + description) 49 | } 50 | } 51 | } 52 | 53 | export function timeoutPromise (timeout) { 54 | return new Promise(function (resolve) { 55 | global.setTimeout(resolve, timeout) 56 | }) 57 | } 58 | 59 | function waitsForPromise (fn) { 60 | const promise = fn() 61 | global.waitsFor('spec promise to resolve', function (done) { 62 | promise.then(done, function (error) { 63 | jasmine.getEnv().currentSpec.fail(error) 64 | done() 65 | }) 66 | }) 67 | } 68 | 69 | export function emitterEventPromise (emitter, event, timeout = 15000) { 70 | return new Promise((resolve, reject) => { 71 | const timeoutHandle = setTimeout(() => { 72 | reject(new Error(`Timed out waiting for '${event}' event`)) 73 | }, timeout) 74 | emitter.once(event, () => { 75 | clearTimeout(timeoutHandle) 76 | resolve() 77 | }) 78 | }) 79 | } 80 | 81 | export function promisify (original) { 82 | return function (...args) { 83 | return new Promise((resolve, reject) => { 84 | args.push((err, ...results) => { 85 | if (err) { 86 | reject(err) 87 | } else { 88 | resolve(...results) 89 | } 90 | }) 91 | 92 | return original(...args) 93 | }) 94 | } 95 | } 96 | 97 | export function promisifySome (obj, fnNames) { 98 | const result = {} 99 | for (const fnName of fnNames) { 100 | result[fnName] = promisify(obj[fnName]) 101 | } 102 | return result 103 | } 104 | -------------------------------------------------------------------------------- /spec/update-package-dependencies-spec.js: -------------------------------------------------------------------------------- 1 | const os = require('os') 2 | const path = require('path') 3 | const updatePackageDependencies = require('../lib/update-package-dependencies') 4 | 5 | const {it, fit, ffit, afterEach, beforeEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars 6 | 7 | describe('Update Package Dependencies', () => { 8 | let projectPath = null 9 | 10 | beforeEach(() => { 11 | projectPath = __dirname 12 | atom.project.setPaths([projectPath]) 13 | }) 14 | 15 | describe('updating package dependencies', () => { 16 | let {command, args, stderr, exit, options} = {} 17 | beforeEach(() => { 18 | spyOn(updatePackageDependencies, 'runBufferedProcess').andCallFake((params) => { 19 | ({command, args, stderr, exit, options} = params) 20 | return true // so that this.process isn't null 21 | }) 22 | }) 23 | 24 | afterEach(() => { 25 | if (updatePackageDependencies.process) exit(0) 26 | }) 27 | 28 | it('runs the `apm install` command', () => { 29 | updatePackageDependencies.update() 30 | 31 | expect(updatePackageDependencies.runBufferedProcess).toHaveBeenCalled() 32 | if (process.platform !== 'win32') { 33 | expect(command.endsWith('/apm')).toBe(true) 34 | } else { 35 | expect(command.endsWith('\\apm.cmd')).toBe(true) 36 | } 37 | expect(args).toEqual(['install', '--no-color']) 38 | expect(options.cwd).toEqual(projectPath) 39 | }) 40 | 41 | it('only allows one apm process to be spawned at a time', () => { 42 | updatePackageDependencies.update() 43 | expect(updatePackageDependencies.runBufferedProcess.callCount).toBe(1) 44 | 45 | updatePackageDependencies.update() 46 | updatePackageDependencies.update() 47 | expect(updatePackageDependencies.runBufferedProcess.callCount).toBe(1) 48 | 49 | exit(0) 50 | updatePackageDependencies.update() 51 | expect(updatePackageDependencies.runBufferedProcess.callCount).toBe(2) 52 | }) 53 | 54 | it('sets NODE_ENV to development in order to install devDependencies', () => { 55 | updatePackageDependencies.update() 56 | 57 | expect(options.env.NODE_ENV).toEqual('development') 58 | }) 59 | 60 | it('adds a status bar tile', async () => { 61 | const statusBar = await atom.packages.activatePackage('status-bar') 62 | 63 | const activationPromise = atom.packages.activatePackage('update-package-dependencies') 64 | atom.commands.dispatch(atom.views.getView(atom.workspace), 'update-package-dependencies:update') 65 | const {mainModule} = await activationPromise 66 | 67 | mainModule.update() 68 | 69 | let tile = statusBar.mainModule.statusBar.getRightTiles().find(tile => tile.item.matches('update-package-dependencies-status')) 70 | expect(tile.item.classList.contains('update-package-dependencies-status')).toBe(true) 71 | expect(tile.item.firstChild.classList.contains('loading')).toBe(true) 72 | 73 | exit(0) 74 | 75 | tile = statusBar.mainModule.statusBar.getRightTiles().find(tile => tile.item.matches('update-package-dependencies-status')) 76 | expect(tile).toBeUndefined() 77 | }) 78 | 79 | describe('when there are multiple project paths', () => { 80 | beforeEach(() => atom.project.setPaths([os.tmpdir(), projectPath])) 81 | 82 | it('uses the currently active one', async () => { 83 | await atom.workspace.open(path.join(projectPath, 'package.json')) 84 | 85 | updatePackageDependencies.update() 86 | expect(options.cwd).toEqual(projectPath) 87 | }) 88 | }) 89 | 90 | describe('when the update succeeds', () => { 91 | beforeEach(() => { 92 | updatePackageDependencies.update() 93 | exit(0) 94 | }) 95 | 96 | it('shows a success notification message', () => { 97 | const notification = atom.notifications.getNotifications()[0] 98 | expect(notification.getType()).toEqual('success') 99 | expect(notification.getMessage()).toEqual('Package dependencies updated') 100 | }) 101 | }) 102 | 103 | describe('when the update fails', () => { 104 | beforeEach(() => { 105 | updatePackageDependencies.update() 106 | stderr('oh bother') 107 | exit(127) 108 | }) 109 | 110 | it('shows a failure notification', () => { 111 | const notification = atom.notifications.getNotifications()[0] 112 | expect(notification.getType()).toEqual('error') 113 | expect(notification.getMessage()).toEqual('Failed to update package dependencies') 114 | expect(notification.getDetail()).toEqual('oh bother') 115 | expect(notification.isDismissable()).toBe(true) 116 | }) 117 | }) 118 | }) 119 | 120 | describe('the `update-package-dependencies:update` command', () => { 121 | beforeEach(() => spyOn(updatePackageDependencies, 'update')) 122 | 123 | it('activates the package and updates package dependencies', async () => { 124 | const activationPromise = atom.packages.activatePackage('update-package-dependencies') 125 | atom.commands.dispatch(atom.views.getView(atom.workspace), 'update-package-dependencies:update') 126 | const {mainModule} = await activationPromise 127 | expect(mainModule.update).toHaveBeenCalled() 128 | }) 129 | }) 130 | }) 131 | -------------------------------------------------------------------------------- /styles/update-package-dependencies.less: -------------------------------------------------------------------------------- 1 | .update-package-dependencies-status .loading.inline-block { 2 | vertical-align: text-bottom; 3 | } 4 | --------------------------------------------------------------------------------