From f05a7c28b251d8d6905076152a8f040de78020ae Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 16:01:39 -0800 Subject: [PATCH 01/17] Convert all of the code to TypeScript --- lib/index.coffee | 44 -------------- lib/index.ts | 40 +++++++++++++ lib/package-list.coffee | 33 ----------- lib/package-list.ts | 32 ++++++++++ lib/package-sync.coffee | 119 -------------------------------------- lib/package-sync.ts | 115 ++++++++++++++++++++++++++++++++++++ lib/season.d.ts | 7 +++ lib/status-message.coffee | 27 --------- lib/status-message.ts | 46 +++++++++++++++ package.json | 43 +++++++++++++- tsconfig.json | 8 +++ 11 files changed, 289 insertions(+), 225 deletions(-) delete mode 100644 lib/index.coffee create mode 100644 lib/index.ts delete mode 100644 lib/package-list.coffee create mode 100644 lib/package-list.ts delete mode 100644 lib/package-sync.coffee create mode 100644 lib/package-sync.ts create mode 100644 lib/season.d.ts delete mode 100644 lib/status-message.coffee create mode 100644 lib/status-message.ts create mode 100644 tsconfig.json diff --git a/lib/index.coffee b/lib/index.coffee deleted file mode 100644 index e88cb55..0000000 --- a/lib/index.coffee +++ /dev/null @@ -1,44 +0,0 @@ -PackageSync = null -packageSync = null - -# Loads the module on-demand. -loadModule = -> - PackageSync ?= require './package-sync' - packageSync ?= new PackageSync() - -module.exports = - activate: -> - atom.commands.add 'atom-workspace', 'package-sync:create-package-list', -> - loadModule() - packageSync.createPackageList() - - atom.commands.add 'atom-workspace', 'package-sync:open-package-list', -> - loadModule() - packageSync.openPackageList() - - atom.commands.add 'atom-workspace', 'package-sync:sync', -> - loadModule() - packageSync.sync() - - atom.packages.onDidActivateInitialPackages -> - atom.packages.onDidLoadPackage -> - if atom.config.get('package-sync.createOnChange') - loadModule() - packageSync.createPackageList() - - atom.packages.onDidUnloadPackage -> - if atom.config.get('package-sync.createOnChange') - loadModule() - packageSync.createPackageList() - - config: - forceOverwrite: - title: 'Overwrite packages.cson' - description: 'Overwrite packages.cson even when it is present.' - type: 'boolean' - default: false - createOnChange: - title: 'Create on change' - description: 'Create package list when packages are installed or removed.' - type: 'boolean' - default: false diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000..2a403de --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,40 @@ +import PackageSync from './package-sync' + +let packageSync: PackageSync | null = null + +function loadModule() : PackageSync { + if (!packageSync) { + const _PackageSync: typeof PackageSync = require('./package-sync') + packageSync = new _PackageSync() + } + + return packageSync +} + +export function activate() { + atom.commands.add('atom-workspace', 'package-sync:create-package-list', () => { + loadModule().createPackageList() + }) + + atom.commands.add('atom-workspace', 'package-sync:open-package-list', () => { + loadModule().openPackageList() + }) + + atom.commands.add('atom-workspace', 'package-sync:sync', () => { + loadModule().sync() + }) + + atom.packages.onDidActivateInitialPackages(() => { + atom.packages.onDidLoadPackage(() => { + if (atom.config.get('package-sync.createOnChange')) { + loadModule().createPackageList() + } + }) + + atom.packages.onDidUnloadPackage(() => { + if (atom.config.get('package-sync.createOnChange')) { + loadModule().createPackageList() + } + }) + }) +} diff --git a/lib/package-list.coffee b/lib/package-list.coffee deleted file mode 100644 index 6345d8f..0000000 --- a/lib/package-list.coffee +++ /dev/null @@ -1,33 +0,0 @@ -CSON = require 'season' -fs = require 'fs' -path = require 'path' - -# Public: Represents the list of packages that the user wants synchronized. -# -# ## Events -# -# This class has no events. -module.exports = -class PackageList - # Public: Gets the list of packages that the user wants synchronized. - # - # Returns an {Array} containing the package names. - getPackages: -> - if fs.existsSync(PackageList.getPackageListPath()) - obj = CSON.readFileSync(PackageList.getPackageListPath()) - obj['packages'] - else - [] - - # Public: Sets the list of packages to the list of available packages. - setPackages: -> - if atom.config.get('package-sync.forceOverwrite') or not fs.existsSync(PackageList.getPackageListPath()) - available = atom.packages.getAvailablePackageNames() - packages = (name for name in available when not atom.packages.isBundledPackage(name)) - CSON.writeFileSync(PackageList.getPackageListPath(), {'packages': packages}) - - # Internal: Gets the path to the package list. - # - # Returns a {String} containing the path to the list of available packages. - @getPackageListPath: -> - @packageListPath ?= path.join(atom.getConfigDirPath(), 'packages.cson') diff --git a/lib/package-list.ts b/lib/package-list.ts new file mode 100644 index 0000000..d9f6639 --- /dev/null +++ b/lib/package-list.ts @@ -0,0 +1,32 @@ +import * as CSON from 'season' +import * as fs from 'fs' +import * as path from 'path' + +interface PackagesFile { + packages: string[] +} + +export default class PackageList { + static packageListPath = path.join(atom.getConfigDirPath(), 'packages.cson') + + static getPackageListPath () { + return this.packageListPath + } + + getPackages () : string[] { + if (fs.existsSync(PackageList.getPackageListPath())) { + let obj = CSON.readFileSync(PackageList.getPackageListPath()) as PackagesFile + return obj.packages + } + + return [] + } + + setPackages () { + if (atom.config.get('package-sync.forceOverwrite') || !fs.existsSync(PackageList.getPackageListPath())) { + let available = atom.packages.getAvailablePackageNames() + let names = available.filter((name: string) => { !atom.packages.isBundledPackage(name) }) + CSON.writeFileSync(PackageList.getPackageListPath(), {'packages': names}) + } + } +} diff --git a/lib/package-sync.coffee b/lib/package-sync.coffee deleted file mode 100644 index 013b435..0000000 --- a/lib/package-sync.coffee +++ /dev/null @@ -1,119 +0,0 @@ -fs = require 'fs' - -{BufferedProcess} = require 'atom' - -PackageList = require './package-list' -StatusMessage = require './status-message' - -# Public: Performs the package synchronization. -# -# ## Events -# -# This class has no events. -module.exports = -class PackageSync - # Internal: Path to `apm`. - apmPath: atom.packages.getApmPath() - - # Internal: Process object of the current install. - currentInstall: null - - # Internal: Timeout for messages that should be up longer. - longMessageTimeout: 15000 - - # Internal: Status bar message. - message: null - - # Internal: Packages in the process of being installed. - packagesToInstall: [] - - # Internal: Timeout for messages that should be up for only a short time. - shortMessageTimeout: 1000 - - # Internal: Timeout for status bar message. - timeout: null - - # Public: Creates the package list for the user from the list of available packages. - createPackageList: -> - new PackageList().setPackages() - - # Public: Opens the package list. - openPackageList: -> - atom.workspace.open(PackageList.getPackageListPath()) - - # Public: Installs any packages that are missing from the `packages.cson` configuration file. - sync: -> - missing = @getMissingPackages() - @installPackages(missing) - - # Internal: Displays a message in the status bar. - # - # If `timeout` is specified, the message will automatically be cleared in `timeout` milliseconds. - # - # message - A {String} containing the message to be displayed. - # timeout - An optional {Number} specifying the time in milliseconds until the message will be - # cleared. - displayMessage: (message, timeout) -> - clearTimeout(@timeout) if @timeout? - if @message? - @message.setText(message) - else - @message = new StatusMessage(message) - - @setMessageTimeout(timeout) if timeout? - - # Internal: Execute APM to install the given package. - # - # pkg - A {String} containing the name of the package to install. - executeApm: (pkg) -> - @displayMessage("Installing #{pkg}") - command = @apmPath - args = ['install', pkg] - stdout = (output) -> - stderr = (output) -> - exit = (exitCode) => - if exitCode is 0 - if @packagesToInstall.length > 0 - @displayMessage("#{pkg} installed!", @shortMessageTimeout) - else - @displayMessage('Package Sync complete!', @longMessageTimeout) - else - @displayMessage("An error occurred installing #{pkg}", @longMessageTimeout) - - @currentInstall = null - @installPackage() - - @currentInstall = new BufferedProcess({command, args, stdout, stderr, exit}) - - # Internal: Gets the list of packages that are missing. - # - # Returns an {Array} of names of packages that need to be installed. - getMissingPackages: -> - list = new PackageList() - syncPackages = list.getPackages() - availablePackages = atom.packages.getAvailablePackageNames() - value for value in syncPackages when value not in availablePackages - - # Internal: Installs the next package in the list. - installPackage: -> - # Exit if there is already an installation running or if there are no more - # packages to install. - return if @currentInstall? or @packagesToInstall.length is 0 - @executeApm(@packagesToInstall.shift()) - - # Internal: Installs each of the packages in the given list. - # - # packages - An {Array} containing the names of packages to install. - installPackages: (packages) -> - @packagesToInstall.push(packages...) - @installPackage() - - # Internal: Sets a timeout to remove the status bar message. - # - # timeout - The {Number} of milliseconds until the message should be removed. - setMessageTimeout: (timeout) -> - clearTimeout(@timeout) if @timeout? - @timeout = setTimeout(=> - @message.remove() - @message = null - , timeout) diff --git a/lib/package-sync.ts b/lib/package-sync.ts new file mode 100644 index 0000000..a10045b --- /dev/null +++ b/lib/package-sync.ts @@ -0,0 +1,115 @@ +import * as fs from 'fs' + +import {BufferedProcess} from 'atom' + +import PackageList from './package-list' +import StatusMessage from './status-message' + +export default class PackageSync { + readonly apmPath = atom.packages.getApmPath() + readonly longMessageTimeout = 15000 + readonly shortMessageTimeout = 1000 + + currentInstall: BufferedProcess | null + message: StatusMessage | null + packagesToInstall: string[] + timeout: NodeJS.Timer | null + + constructor () { + this.currentInstall = null + this.message = null + this.packagesToInstall = [] + this.timeout = null + } + + createPackageList () { + new PackageList().setPackages() + } + + openPackageList () { + atom.workspace.open(PackageList.getPackageListPath()) + } + + sync () { + let missing = this.getMissingPackages() + this.installPackages(missing) + } + + displayMessage (message: string, timeout?: number) { + if (this.timeout) { + clearTimeout(this.timeout) + } + + if (this.message) { + this.message.setText(message) + } else { + this.message = new StatusMessage(message) + } + + if (timeout) { + this.setMessageTimeout(timeout) + } + } + + executeApm (pkg: string) { + this.displayMessage(`Installing ${pkg}`) + + let command = this.apmPath + let args = ['install', pkg] + let stdout = (output: string) => {} + let stderr = (output: string) => {} + let exit = (exitCode: number) => { + if (exitCode === 0) { + if (this.packagesToInstall.length > 0) { + this.displayMessage(`${pkg} installed!`, this.shortMessageTimeout) + } else { + this.displayMessage('Package sync complete!', this.longMessageTimeout) + } + } else { + this.displayMessage(`An error occurred installing ${pkg}`, this.longMessageTimeout) + } + + this.currentInstall = null + this.installPackage() + } + + this.currentInstall = new BufferedProcess({command, args, stdout, stderr, exit}) + } + + getMissingPackages () { + let list = new PackageList() + let syncPackages = list.getPackages() + let availablePackages = atom.packages.getAvailablePackageNames() + + return syncPackages.filter((value) => { !(value in availablePackages) }) + } + + installPackage () { + if (this.currentInstall) { + return + } + + let nextPackage = this.packagesToInstall.shift() + + if (nextPackage) { + this.executeApm(nextPackage) + } + } + + installPackages (packages: string[]) { + this.packagesToInstall.push(...packages) + } + + setMessageTimeout (timeout: number) { + if (this.timeout) { + clearTimeout(this.timeout) + } + + this.timeout = setTimeout(() => { + if (this.message) { + this.message.remove() + this.message = null + } + }, timeout) + } +} diff --git a/lib/season.d.ts b/lib/season.d.ts new file mode 100644 index 0000000..42642af --- /dev/null +++ b/lib/season.d.ts @@ -0,0 +1,7 @@ +// Type definitions for atom/season + +declare module 'season' { + export function readFileSync(objectPath: string): object + + export function writeFileSync(objectPath: string, obj: object): void +} diff --git a/lib/status-message.coffee b/lib/status-message.coffee deleted file mode 100644 index b8ce422..0000000 --- a/lib/status-message.coffee +++ /dev/null @@ -1,27 +0,0 @@ -# Public: Displays a message in the status bar. -module.exports = -class StatusMessage - # Public: Displays `message` in the status bar. - # - # If the status bar does not exist for whatever reason, no message is displayed and no error - # occurs. - # - # message - A {String} containing the message to display. - constructor: (message) -> - @statusBar = document.querySelector('status-bar') - if @statusBar - @item = document.createElement('div') - @item.classList.add('inline-block') - @setText(message) - - @tile = @statusBar.addLeftTile({@item}) - - # Public: Removes the message from the status bar. - remove: -> - @tile?.destroy() - - # Public: Updates the text of the message. - # - # text - A {String} containing the new message to display. - setText: (text) -> - @item.innerHTML = text if @statusBar diff --git a/lib/status-message.ts b/lib/status-message.ts new file mode 100644 index 0000000..5e7e583 --- /dev/null +++ b/lib/status-message.ts @@ -0,0 +1,46 @@ +import {StatusBar, Tile} from 'atom/status-bar' + +/** + * Displays a message in the status bar. + */ +export default class StatusMessage { + item: Element + statusBar: StatusBar + tile: Tile + + /** + * Displays `message` in the status bar. + * + * If the status bar does not exist, no message is displayed and no error + * occurs. + */ + constructor (message: string) { + this.statusBar = document.querySelector('status-bar') as StatusBar + + if (this.statusBar) { + this.item = document.createElement('div') + this.item.classList.add('inline-block') + this.setText(message) + + this.tile = this.statusBar.addLeftTile({item: this.item}) + } + } + + /** + * Removes the message from the status bar. + */ + remove () { + if (this.tile) { + this.tile.destroy() + } + } + + /** + * Updates the text of the message. + */ + setText (text: string) { + if (this.statusBar) { + this.item.innerHTML = text + } + } +} diff --git a/package.json b/package.json index bf60c04..4dc087c 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,48 @@ "repository": "https://github.com/lee-dohm/package-sync", "license": "MIT", "engines": { - "atom": ">=0.174.0 <2.0.0" + "atom": ">=1.23.1 <2.0.0" }, "dependencies": { - "season": "^5.2.0" + "atom-ts-transpiler": "^1.4.0", + "season": "^5.2.0", + "typescript": "^2.6.2", + "@types/atom": "^1.23.0", + "@types/node": "^8.5.2" + }, + "devDependencies": { + "atom-mocha-test-runner": "^1.2.0", + "chai": "^4.1.2" + }, + "atomTestRunner": "atom-mocha-test-runner", + "atomTranspilers": [ + { + "transpiler": "atom-ts-transpiler", + "glob": "{!(node_modules)/**/,}*.ts?(x)", + "options": { + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "moduleResolution": "node" + }, + "cacheKeyFiles": [], + "verbose": false + } + } + ], + "configSchema": { + "forceOverwrite": { + "title": "Overwrite packages list", + "description": "Overwrite packages list even when it is present", + "type": "boolean", + "default": false + }, + "createOnChange": { + "title": "Create on change", + "description": "Create packages list when packages or themes are installed or removed", + "type": "boolean", + "default": false + } } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e4e7528 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "moduleResolution": "node" + } +} From a73d05aac958f63a0e2fdf4f85b662dda2f9ea55 Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 16:02:33 -0800 Subject: [PATCH 02/17] Move all tests to Mocha --- spec/{package-list-spec.coffee => package-list.test.coffee} | 2 ++ spec/{package-sync-spec.coffee => package-sync.test.coffee} | 0 spec/{status-message-spec.coffee => status-message.test.coffee} | 0 3 files changed, 2 insertions(+) rename spec/{package-list-spec.coffee => package-list.test.coffee} (99%) rename spec/{package-sync-spec.coffee => package-sync.test.coffee} (100%) rename spec/{status-message-spec.coffee => status-message.test.coffee} (100%) diff --git a/spec/package-list-spec.coffee b/spec/package-list.test.coffee similarity index 99% rename from spec/package-list-spec.coffee rename to spec/package-list.test.coffee index a52d6f9..73bead1 100644 --- a/spec/package-list-spec.coffee +++ b/spec/package-list.test.coffee @@ -7,6 +7,8 @@ PackageList = require '../lib/package-list' h = require './helpers' +expect = chai.expect + describe 'PackageList', -> list = null diff --git a/spec/package-sync-spec.coffee b/spec/package-sync.test.coffee similarity index 100% rename from spec/package-sync-spec.coffee rename to spec/package-sync.test.coffee diff --git a/spec/status-message-spec.coffee b/spec/status-message.test.coffee similarity index 100% rename from spec/status-message-spec.coffee rename to spec/status-message.test.coffee From 684027081f3fa80c281d81bee9e2ff7a8e33bce1 Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 16:19:52 -0800 Subject: [PATCH 03/17] Fix some stuff that got broken --- lib/index.ts | 23 +++++++---------------- lib/package-list.ts | 5 +++-- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index 2a403de..de82c00 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,39 +1,30 @@ import PackageSync from './package-sync' -let packageSync: PackageSync | null = null - -function loadModule() : PackageSync { - if (!packageSync) { - const _PackageSync: typeof PackageSync = require('./package-sync') - packageSync = new _PackageSync() - } - - return packageSync -} - export function activate() { + let packageSync = new PackageSync() + atom.commands.add('atom-workspace', 'package-sync:create-package-list', () => { - loadModule().createPackageList() + packageSync.createPackageList() }) atom.commands.add('atom-workspace', 'package-sync:open-package-list', () => { - loadModule().openPackageList() + packageSync.openPackageList() }) atom.commands.add('atom-workspace', 'package-sync:sync', () => { - loadModule().sync() + packageSync.sync() }) atom.packages.onDidActivateInitialPackages(() => { atom.packages.onDidLoadPackage(() => { if (atom.config.get('package-sync.createOnChange')) { - loadModule().createPackageList() + packageSync.createPackageList() } }) atom.packages.onDidUnloadPackage(() => { if (atom.config.get('package-sync.createOnChange')) { - loadModule().createPackageList() + packageSync.createPackageList() } }) }) diff --git a/lib/package-list.ts b/lib/package-list.ts index d9f6639..32e1831 100644 --- a/lib/package-list.ts +++ b/lib/package-list.ts @@ -25,8 +25,9 @@ export default class PackageList { setPackages () { if (atom.config.get('package-sync.forceOverwrite') || !fs.existsSync(PackageList.getPackageListPath())) { let available = atom.packages.getAvailablePackageNames() - let names = available.filter((name: string) => { !atom.packages.isBundledPackage(name) }) - CSON.writeFileSync(PackageList.getPackageListPath(), {'packages': names}) + let names = available.filter((name: string) => { return !atom.packages.isBundledPackage(name) }) + + CSON.writeFileSync(PackageList.getPackageListPath(), {packages: names}) } } } From a7520cd4f377b75e2a7837e459bb943ca96498d0 Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 16:25:43 -0800 Subject: [PATCH 04/17] :art: --- lib/index.ts | 2 +- lib/package-list.ts | 6 +++--- lib/package-sync.ts | 20 ++++++++++---------- lib/status-message.ts | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index de82c00..fb47c90 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,6 +1,6 @@ import PackageSync from './package-sync' -export function activate() { +export function activate(): void { let packageSync = new PackageSync() atom.commands.add('atom-workspace', 'package-sync:create-package-list', () => { diff --git a/lib/package-list.ts b/lib/package-list.ts index 32e1831..edcf64a 100644 --- a/lib/package-list.ts +++ b/lib/package-list.ts @@ -9,11 +9,11 @@ interface PackagesFile { export default class PackageList { static packageListPath = path.join(atom.getConfigDirPath(), 'packages.cson') - static getPackageListPath () { + static getPackageListPath(): string { return this.packageListPath } - getPackages () : string[] { + getPackages(): string[] { if (fs.existsSync(PackageList.getPackageListPath())) { let obj = CSON.readFileSync(PackageList.getPackageListPath()) as PackagesFile return obj.packages @@ -22,7 +22,7 @@ export default class PackageList { return [] } - setPackages () { + setPackages(): void { if (atom.config.get('package-sync.forceOverwrite') || !fs.existsSync(PackageList.getPackageListPath())) { let available = atom.packages.getAvailablePackageNames() let names = available.filter((name: string) => { return !atom.packages.isBundledPackage(name) }) diff --git a/lib/package-sync.ts b/lib/package-sync.ts index a10045b..d618795 100644 --- a/lib/package-sync.ts +++ b/lib/package-sync.ts @@ -15,27 +15,27 @@ export default class PackageSync { packagesToInstall: string[] timeout: NodeJS.Timer | null - constructor () { + constructor() { this.currentInstall = null this.message = null this.packagesToInstall = [] this.timeout = null } - createPackageList () { + createPackageList(): void { new PackageList().setPackages() } - openPackageList () { + openPackageList(): void { atom.workspace.open(PackageList.getPackageListPath()) } - sync () { + sync(): void { let missing = this.getMissingPackages() this.installPackages(missing) } - displayMessage (message: string, timeout?: number) { + displayMessage(message: string, timeout?: number): void { if (this.timeout) { clearTimeout(this.timeout) } @@ -51,7 +51,7 @@ export default class PackageSync { } } - executeApm (pkg: string) { + executeApm(pkg: string): void { this.displayMessage(`Installing ${pkg}`) let command = this.apmPath @@ -76,7 +76,7 @@ export default class PackageSync { this.currentInstall = new BufferedProcess({command, args, stdout, stderr, exit}) } - getMissingPackages () { + getMissingPackages(): string[] { let list = new PackageList() let syncPackages = list.getPackages() let availablePackages = atom.packages.getAvailablePackageNames() @@ -84,7 +84,7 @@ export default class PackageSync { return syncPackages.filter((value) => { !(value in availablePackages) }) } - installPackage () { + installPackage(): void { if (this.currentInstall) { return } @@ -96,11 +96,11 @@ export default class PackageSync { } } - installPackages (packages: string[]) { + installPackages(packages: string[]): void { this.packagesToInstall.push(...packages) } - setMessageTimeout (timeout: number) { + setMessageTimeout(timeout: number): void { if (this.timeout) { clearTimeout(this.timeout) } diff --git a/lib/status-message.ts b/lib/status-message.ts index 5e7e583..64c58cc 100644 --- a/lib/status-message.ts +++ b/lib/status-message.ts @@ -14,7 +14,7 @@ export default class StatusMessage { * If the status bar does not exist, no message is displayed and no error * occurs. */ - constructor (message: string) { + constructor(message: string) { this.statusBar = document.querySelector('status-bar') as StatusBar if (this.statusBar) { @@ -29,7 +29,7 @@ export default class StatusMessage { /** * Removes the message from the status bar. */ - remove () { + remove(): void { if (this.tile) { this.tile.destroy() } @@ -38,7 +38,7 @@ export default class StatusMessage { /** * Updates the text of the message. */ - setText (text: string) { + setText(text: string): void { if (this.statusBar) { this.item.innerHTML = text } From ad3f6476266292d574bbe2234b29e944028b376d Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 17:29:10 -0800 Subject: [PATCH 05/17] :memo: --- lib/package-list.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/package-list.ts b/lib/package-list.ts index edcf64a..ec68e77 100644 --- a/lib/package-list.ts +++ b/lib/package-list.ts @@ -6,13 +6,22 @@ interface PackagesFile { packages: string[] } +/** + * Represents the stored package list. + */ export default class PackageList { static packageListPath = path.join(atom.getConfigDirPath(), 'packages.cson') + /** + * Gets the path to the stored package list. + */ static getPackageListPath(): string { return this.packageListPath } + /** + * Gets the packages from the list. + */ getPackages(): string[] { if (fs.existsSync(PackageList.getPackageListPath())) { let obj = CSON.readFileSync(PackageList.getPackageListPath()) as PackagesFile @@ -22,6 +31,10 @@ export default class PackageList { return [] } + /** + * Updates the stored package list with what is currently installed if the list doesn't exist or + * the `forceOverwrite` configuration option is set to `true`. + */ setPackages(): void { if (atom.config.get('package-sync.forceOverwrite') || !fs.existsSync(PackageList.getPackageListPath())) { let available = atom.packages.getAvailablePackageNames() From 1921afd1452e6c60ac81d175844fe889a076148f Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 17:40:18 -0800 Subject: [PATCH 06/17] =?UTF-8?q?Start=20the=20packages=20installing=20?= =?UTF-8?q?=F0=9F=98=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/package-sync.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/package-sync.ts b/lib/package-sync.ts index d618795..d10fd7c 100644 --- a/lib/package-sync.ts +++ b/lib/package-sync.ts @@ -98,6 +98,7 @@ export default class PackageSync { installPackages(packages: string[]): void { this.packagesToInstall.push(...packages) + this.installPackage() } setMessageTimeout(timeout: number): void { From a9bdb47013fa77311616d692f3dff4f4294a4564 Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 17:40:51 -0800 Subject: [PATCH 07/17] :memo: --- lib/index.ts | 3 +++ lib/package-sync.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/lib/index.ts b/lib/index.ts index fb47c90..7ccbc43 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,5 +1,8 @@ import PackageSync from './package-sync' +/** + * Activates the package. + */ export function activate(): void { let packageSync = new PackageSync() diff --git a/lib/package-sync.ts b/lib/package-sync.ts index d10fd7c..600cfe2 100644 --- a/lib/package-sync.ts +++ b/lib/package-sync.ts @@ -22,19 +22,31 @@ export default class PackageSync { this.timeout = null } + /** + * Creates the package list. + */ createPackageList(): void { new PackageList().setPackages() } + /** + * Opens the package list in the workspace. + */ openPackageList(): void { atom.workspace.open(PackageList.getPackageListPath()) } + /** + * Syncs the package list by installing any missing packages. + */ sync(): void { let missing = this.getMissingPackages() this.installPackages(missing) } + /** + * Displays the `message` for the given `timeout` in milliseconds or indefinitely. + */ displayMessage(message: string, timeout?: number): void { if (this.timeout) { clearTimeout(this.timeout) @@ -51,6 +63,12 @@ export default class PackageSync { } } + /** + * Executes apm to install the given package by name. + * + * When the given package is done installing, it attempts to install the next package in the + * `packagesToInstall` list. + */ executeApm(pkg: string): void { this.displayMessage(`Installing ${pkg}`) @@ -76,6 +94,9 @@ export default class PackageSync { this.currentInstall = new BufferedProcess({command, args, stdout, stderr, exit}) } + /** + * Gets the list of missing package names by comparing against the current package list. + */ getMissingPackages(): string[] { let list = new PackageList() let syncPackages = list.getPackages() @@ -84,6 +105,9 @@ export default class PackageSync { return syncPackages.filter((value) => { !(value in availablePackages) }) } + /** + * Installs the next package in the list, if one exists. + */ installPackage(): void { if (this.currentInstall) { return @@ -96,11 +120,17 @@ export default class PackageSync { } } + /** + * Queues up the given list of packages to be installed and starts the process. + */ installPackages(packages: string[]): void { this.packagesToInstall.push(...packages) this.installPackage() } + /** + * Sets up a timeout for the currently displayed message. + */ setMessageTimeout(timeout: number): void { if (this.timeout) { clearTimeout(this.timeout) From 37087e6c4dfcdd182ee07672a0362b617a3f115a Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 17:51:41 -0800 Subject: [PATCH 08/17] :memo: Add notes to the README --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 0f0def5..c9a7091 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,35 @@ Where the contents of the array is a list of packages to ensure are installed. No keybindings are configured by default for the commands in this package. +## Notes + +There are two primary use cases for this package: + +1. Ensure minimum set of packages installed + * Atom is allowed to have packages installed that are not in the list + * Package list is auto-generated once and then mostly manually updated +1. Ensure that the list of installed packages is kept completely in sync + * Must install any missing packages + * Must remove any extra packages + * Package list is auto-updated when packages are installed or removed + +package-sync was originally intended to only satisfy the first scenario. It has since been pressed into service for the second scenario. + +### Minimum set of packages + +Operations: + +* Create package list from installed packages +* Edit package list by hand +* Ensure all packages in list are installed by syncing + +### Synchronize packages + +Operations: + +* Automated updating of package list +* Ensure all packages are in sync by triggering sync + ## Copyright Copyright © 2014-2016 by [Lee Dohm](http://www.lee-dohm.com), [Lifted Studios](http://www.liftedstudios.com). See the [LICENSE](https://github.com/lee-dohm/package-sync/blob/master/LICENSE.md) for more details. From 47f1f003c8997be779acd690d971c712976d39ba Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 19:46:54 -0800 Subject: [PATCH 09/17] Convert tests to TypeScript --- lib/package-list.ts | 17 +- lib/package-sync.ts | 16 +- package-lock.json | 1284 +++++++++++++++++++++++++++++++ package.json | 8 +- spec/helpers.coffee | 16 - spec/helpers.ts | 22 + spec/package-list.spec.ts | 116 +++ spec/package-list.test.coffee | 83 -- spec/package-sync.spec.ts | 26 + spec/package-sync.test.coffee | 26 - spec/runner.ts | 7 + spec/status-message.test.coffee | 39 - 12 files changed, 1478 insertions(+), 182 deletions(-) create mode 100644 package-lock.json delete mode 100644 spec/helpers.coffee create mode 100644 spec/helpers.ts create mode 100644 spec/package-list.spec.ts delete mode 100644 spec/package-list.test.coffee create mode 100644 spec/package-sync.spec.ts delete mode 100644 spec/package-sync.test.coffee create mode 100644 spec/runner.ts delete mode 100644 spec/status-message.test.coffee diff --git a/lib/package-list.ts b/lib/package-list.ts index ec68e77..cadbcab 100644 --- a/lib/package-list.ts +++ b/lib/package-list.ts @@ -10,21 +10,18 @@ interface PackagesFile { * Represents the stored package list. */ export default class PackageList { - static packageListPath = path.join(atom.getConfigDirPath(), 'packages.cson') + path: string - /** - * Gets the path to the stored package list. - */ - static getPackageListPath(): string { - return this.packageListPath + constructor(configDir = atom.getConfigDirPath()) { + this.path = path.join(configDir, 'packages.cson') } /** * Gets the packages from the list. */ getPackages(): string[] { - if (fs.existsSync(PackageList.getPackageListPath())) { - let obj = CSON.readFileSync(PackageList.getPackageListPath()) as PackagesFile + if (fs.existsSync(this.path)) { + let obj = CSON.readFileSync(this.path) as PackagesFile return obj.packages } @@ -36,11 +33,11 @@ export default class PackageList { * the `forceOverwrite` configuration option is set to `true`. */ setPackages(): void { - if (atom.config.get('package-sync.forceOverwrite') || !fs.existsSync(PackageList.getPackageListPath())) { + if (atom.config.get('package-sync.forceOverwrite') || !fs.existsSync(this.path)) { let available = atom.packages.getAvailablePackageNames() let names = available.filter((name: string) => { return !atom.packages.isBundledPackage(name) }) - CSON.writeFileSync(PackageList.getPackageListPath(), {packages: names}) + CSON.writeFileSync(this.path, {packages: names}) } } } diff --git a/lib/package-sync.ts b/lib/package-sync.ts index 600cfe2..7688a7a 100644 --- a/lib/package-sync.ts +++ b/lib/package-sync.ts @@ -25,15 +25,17 @@ export default class PackageSync { /** * Creates the package list. */ - createPackageList(): void { - new PackageList().setPackages() + createPackageList(configDir?: string): void { + let list = new PackageList(configDir) + list.setPackages() } /** * Opens the package list in the workspace. */ - openPackageList(): void { - atom.workspace.open(PackageList.getPackageListPath()) + openPackageList(configDir?: string): void { + let list = new PackageList(configDir) + atom.workspace.open(list.path) } /** @@ -97,12 +99,12 @@ export default class PackageSync { /** * Gets the list of missing package names by comparing against the current package list. */ - getMissingPackages(): string[] { - let list = new PackageList() + getMissingPackages(configDir?: string): string[] { + let list = new PackageList(configDir) let syncPackages = list.getPackages() let availablePackages = atom.packages.getAvailablePackageNames() - return syncPackages.filter((value) => { !(value in availablePackages) }) + return syncPackages.filter((value) => { return !(value in availablePackages) }) } /** diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f21f74b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1284 @@ +{ + "name": "package-sync", + "version": "1.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/atom": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@types/atom/-/atom-1.23.0.tgz", + "integrity": "sha512-yJIXw3ITdV01JoQRXJAVngzGirmrNpGJPgVTSLkTAzCSMwePslPCuc5ryQlN1DF7yY92zYbvpdJ+spNSCe8odQ==", + "requires": { + "@types/jquery": "3.2.17", + "@types/node": "8.5.2" + } + }, + "@types/atom-mocha-test-runner": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/atom-mocha-test-runner/-/atom-mocha-test-runner-1.0.2.tgz", + "integrity": "sha512-49cwl/SSvOEjPCToOv6KLpparFgmOy6iPFqIzLf+60dP1sIG7AR/Wi8uoICniu9WqCIH9cDnxBMRPOEuFyIxRA==", + "dev": true, + "requires": { + "@types/atom": "1.23.0", + "@types/mocha": "2.2.45" + } + }, + "@types/chai": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.10.tgz", + "integrity": "sha512-Ejh1AXTY8lm+x91X/yar3G2z4x9RyKwdTVdyyu7Xj3dNB35fMNCnEWqTO9FgS3zjzlRNqk1MruYhgb8yhRN9rA==", + "dev": true + }, + "@types/jquery": { + "version": "3.2.17", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.2.17.tgz", + "integrity": "sha512-lt8i2ZqymxxN1JnWSOTPU7Xc3ze32lhASkVdsMd6/SZnb/jtBsmFEoYCBSa0tzGDSyO7ZB+4r4aihj+KTlDs5w==" + }, + "@types/mocha": { + "version": "2.2.45", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.45.tgz", + "integrity": "sha512-tE1SYtNG3I3atRVPELSGN2FJJJtPg3O/G0tycYSyzeDqdAbdLPRH089LhpWYA5M/iHeWHkVZq/b0OVKngcK0Eg==", + "dev": true, + "requires": { + "@types/node": "8.5.2" + } + }, + "@types/node": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.2.tgz", + "integrity": "sha512-KA4GKOpgXnrqEH2eCVhiv2CsxgXGQJgV1X0vsGlh+WCnxbeAE1GT44ZsTU1IN5dEeV/gDupKa7gWo08V5IxWVQ==" + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true, + "optional": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true, + "optional": true + }, + "assertion-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", + "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true, + "optional": true + }, + "atom-mocha-test-runner": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/atom-mocha-test-runner/-/atom-mocha-test-runner-1.2.0.tgz", + "integrity": "sha512-HVbx7cAvySjVfVNKpb2go9RO890Xs6yigWWAwoISOz4l2X5oMTMs1rIw04geuEQeTTmW3ob3nj6YN1KWf2cBHg==", + "dev": true, + "requires": { + "etch": "0.8.0", + "grim": "2.0.2", + "less": "2.7.3", + "mocha": "3.5.3", + "tmp": "0.0.31" + } + }, + "atom-transpiler-debug-tool": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/atom-transpiler-debug-tool/-/atom-transpiler-debug-tool-1.0.3.tgz", + "integrity": "sha512-lnfbrZDTKStt10dFG+ihtpyffrbBy2Z7/ePyLb3klsUel/a3oggVMKg1p5G9GxK3seYKVr/2/TZhsYhvonf8Ew==", + "dev": true, + "requires": { + "chalk": "2.3.0", + "glob": "7.1.2", + "resolve": "1.5.0" + } + }, + "atom-ts-transpiler": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/atom-ts-transpiler/-/atom-ts-transpiler-1.4.0.tgz", + "integrity": "sha512-yL+/EquJIJwSG3xm357LT1X8QYYngEROXfxRo5yN5nrI1bgAUmvrBzx4ENZG0ufhr8BsBWjMFmMgHE3QNMm/+A==" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-split": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/browser-split/-/browser-split-0.0.1.tgz", + "integrity": "sha1-ewl1dPjj6tYG+0Zk5krf3aKYGpM=", + "dev": true + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true, + "optional": true + }, + "chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "1.0.2", + "check-error": "1.0.2", + "deep-eql": "3.0.1", + "get-func-name": "2.0.0", + "pathval": "1.1.0", + "type-detect": "4.0.5" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + }, + "dependencies": { + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true, + "optional": true + }, + "coffee-script": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.9.0.tgz", + "integrity": "sha1-dJLLvD8DYcxdiGWv9yN1Uv8z4fc=" + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true, + "optional": true + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "optional": true, + "requires": { + "boom": "2.10.1" + } + }, + "cson-parser": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-1.0.9.tgz", + "integrity": "sha1-t5/BuCp3V0NoDw7/uL+tMRNNrHQ=", + "requires": { + "coffee-script": "1.9.0" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "4.0.5" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "errno": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.6.tgz", + "integrity": "sha512-IsORQDpaaSwcDP4ZZnHxgE85werpo34VYn1Ud3mq+eUsF593faR8oCZNXrROVkpFu2TsbrNhHin0aUrTsQ9vNw==", + "dev": true, + "optional": true, + "requires": { + "prr": "1.0.1" + } + }, + "error": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/error/-/error-4.4.0.tgz", + "integrity": "sha1-v2n/JR+0onnBmtzNqmth6Q2b8So=", + "dev": true, + "requires": { + "camelize": "1.0.0", + "string-template": "0.2.1", + "xtend": "4.0.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "etch": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/etch/-/etch-0.8.0.tgz", + "integrity": "sha1-VPYZV0NG+KPueXP1T7vQG1YnItY=", + "dev": true, + "requires": { + "virtual-dom": "2.1.1" + } + }, + "ev-store": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ev-store/-/ev-store-7.0.0.tgz", + "integrity": "sha1-GrDH+CE2UF3XSzHRdwHLK+bSZVg=", + "dev": true, + "requires": { + "individual": "3.0.0" + } + }, + "event-kit": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.4.0.tgz", + "integrity": "sha512-ZXd9jxUoc/f/zdLdR3OUcCzT84WnpaNWefquLyE125akIC90sDs8S3T/qihliuVsaj7Osc0z8lLL2fjooE9Z4A==", + "dev": true + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "fs-plus": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-2.10.1.tgz", + "integrity": "sha1-MgR4HXhAYR5jZOe2+wWMljJ8WqU=", + "requires": { + "async": "1.5.2", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "underscore-plus": "1.6.6" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "dev": true, + "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=", + "dev": true, + "optional": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "grim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/grim/-/grim-2.0.2.tgz", + "integrity": "sha512-Qj7hTJRfd87E/gUgfvM0YIH/g2UA2SV6niv6BYXk1o6w4mhgv+QyYM1EjOJQljvzgEj4SqSsRWldXIeKHz3e3Q==", + "dev": true, + "requires": { + "event-kit": "2.4.0" + } + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "dev": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "optional": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "individual": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/individual/-/individual-3.0.0.tgz", + "integrity": "sha1-58pPhfiVewGHNPKFdQ3CLsL5hi0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true, + "optional": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true, + "optional": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "optional": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, + "optional": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "less": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/less/-/less-2.7.3.tgz", + "integrity": "sha512-KPdIJKWcEAb02TuJtaLrhue0krtRLoRoo7x6BNJIBelO00t/CCdJQUnHW5V34OnHMWzIktSalJxRO+FvytQlCQ==", + "dev": true, + "requires": { + "errno": "0.1.6", + "graceful-fs": "4.1.11", + "image-size": "0.5.5", + "mime": "1.6.0", + "mkdirp": "0.5.1", + "promise": "7.3.1", + "request": "2.81.0", + "source-map": "0.5.7" + } + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", + "dev": true + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "dev": true, + "requires": { + "mime-db": "1.30.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=", + "dev": true, + "requires": { + "dom-walk": "0.1.1" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "dependencies": { + "glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "next-tick": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-0.2.2.tgz", + "integrity": "sha1-ddpKkn7liH45BliABltzNkE7MQ0=", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "optimist": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.4.0.tgz", + "integrity": "sha1-y47Dfy/jqphky2eidSUOfhliCiU=", + "requires": { + "wordwrap": "0.0.3" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "dev": true, + "optional": true + }, + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "optional": true, + "requires": { + "asap": "2.0.6" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true, + "optional": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "dev": true, + "optional": true + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.1.0" + } + }, + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "season": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/season/-/season-5.4.1.tgz", + "integrity": "sha1-S9baYVKn8tbwixQzzi2SBmmFPQ0=", + "requires": { + "cson-parser": "1.0.9", + "fs-plus": "2.10.1", + "optimist": "0.4.0" + } + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "optional": true, + "requires": { + "hoek": "2.16.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "optional": true + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, + "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true, + "optional": true + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + }, + "tmp": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "dev": true, + "optional": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-detect": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.5.tgz", + "integrity": "sha512-N9IvkQslUGYGC24RkJk1ba99foK6TkwC2FHAEBlQFBP0RxQZS8ZpJuAZcwiY/w9ZJHFQb1aOXBI60OdxhTrwEQ==", + "dev": true + }, + "typescript": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", + "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=" + }, + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" + }, + "underscore-plus": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.6.6.tgz", + "integrity": "sha1-ZezeG9xEGjXYnmUP1w3PE65Dmn0=", + "requires": { + "underscore": "1.6.0" + } + }, + "uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "dev": true, + "optional": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "virtual-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/virtual-dom/-/virtual-dom-2.1.1.tgz", + "integrity": "sha1-gO2i1IG57eDASRGM78tKBfIdE3U=", + "dev": true, + "requires": { + "browser-split": "0.0.1", + "error": "4.4.0", + "ev-store": "7.0.0", + "global": "4.3.2", + "is-object": "1.0.1", + "next-tick": "0.2.2", + "x-is-array": "0.1.0", + "x-is-string": "0.1.0" + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "x-is-array": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/x-is-array/-/x-is-array-0.1.0.tgz", + "integrity": "sha1-3lIBcdR7P0FvVYfWKbidJrEtwp0=", + "dev": true + }, + "x-is-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", + "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } +} diff --git a/package.json b/package.json index 4dc087c..2c97aaa 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,13 @@ "@types/node": "^8.5.2" }, "devDependencies": { + "@types/atom-mocha-test-runner": "^1.0.2", + "@types/chai": "^4.0.10", "atom-mocha-test-runner": "^1.2.0", + "atom-transpiler-debug-tool": "^1.0.3", "chai": "^4.1.2" }, - "atomTestRunner": "atom-mocha-test-runner", + "atomTestRunner": "./spec/runner", "atomTranspilers": [ { "transpiler": "atom-ts-transpiler", @@ -55,5 +58,8 @@ "type": "boolean", "default": false } + }, + "scripts": { + "debug": "atom-transpiler-debug-tool" } } diff --git a/spec/helpers.coffee b/spec/helpers.coffee deleted file mode 100644 index 90c2e33..0000000 --- a/spec/helpers.coffee +++ /dev/null @@ -1,16 +0,0 @@ -CSON = require 'season' -fs = require 'fs' -os = require 'os' -path = require 'path' - -# Module of helper methods for tests. -module.exports = - createPackages: (packages, packagesPath = @getPackagesPath()) -> - CSON.writeFileSync(packagesPath, packages) - - deletePackages: (packagesPath = @getPackagesPath()) -> - if fs.existsSync(packagesPath) - fs.unlinkSync(packagesPath) - - getPackagesPath: -> - path.join(os.tmpdir(), 'packages.cson') diff --git a/spec/helpers.ts b/spec/helpers.ts new file mode 100644 index 0000000..0a8a64b --- /dev/null +++ b/spec/helpers.ts @@ -0,0 +1,22 @@ +import * as CSON from 'season' +import * as fs from 'fs' +import * as os from 'os' +import * as path from 'path' + +export function createList(list: string[]): void { + let packagesPath = getPackagesPath() + + CSON.writeFileSync(packagesPath, {packages: list}) +} + +export function deleteList(): void { + let packagesPath = getPackagesPath() + + if (fs.existsSync(packagesPath)) { + fs.unlinkSync(packagesPath) + } +} + +export function getPackagesPath(): string { + return path.join(os.tmpdir(), 'packages.cson') +} diff --git a/spec/package-list.spec.ts b/spec/package-list.spec.ts new file mode 100644 index 0000000..cf15ea5 --- /dev/null +++ b/spec/package-list.spec.ts @@ -0,0 +1,116 @@ +import * as chai from 'chai' +import * as fs from 'fs' +import * as os from 'os' + +import PackageList from '../lib/package-list' + +import {createList, deleteList, getPackagesPath} from './helpers' + +const expect = chai.expect + +describe('PackageList', () => { + let list: PackageList + + beforeEach(() => { + list = new PackageList(os.tmpdir()) + }) + + describe('getPackages', () => { + beforeEach(() => { + deleteList() + }) + + it('gives an empty array when there is no packages.cson', () => { + expect(list.getPackages()).to.be.empty + }) + + it('gives an empty array when the package list is empty', () => { + createList([]) + expect(list.getPackages()).to.be.empty + }) + + it('gives the packages list when the list is non-empty', () => { + createList(['foo', 'bar', 'baz']) + expect(list.getPackages()).to.have.members(['foo', 'bar', 'baz']) + }) + }) + + describe('setPackages', () => { + describe('when forceOverwrite is set to false', () => { + beforeEach(() => { + atom.config.set('package-sync.forceOverwrite', false) + }) + + describe('when the file does not exist', () => { + beforeEach(() => { + deleteList() + }) + + it('creates the packages file', () => { + list.setPackages() + + expect(fs.existsSync(getPackagesPath())).to.be.true + }) + + it('includes all of the currently installed packages', () => { + let available = atom.packages.getAvailablePackageNames() + let installed = available.filter((name: string) => { return !atom.packages.isBundledPackage(name) }) + + expect(list.getPackages()).to.have.members(installed) + }) + }) + + describe('when the file does exist', () => { + beforeEach(() => { + createList([]) + }) + + it('does not change the file', () => { + list.setPackages() + + expect(list.getPackages()).to.be.empty + }) + }) + }) + + describe('when forceOverwrite is set to true', () => { + beforeEach(() => { + atom.config.set('package-sync.forceOverwrite', true) + }) + + describe('when the file does not exist', () => { + beforeEach(() => { + deleteList() + }) + + it('creates the packages file', () => { + list.setPackages() + + expect(fs.existsSync(getPackagesPath())).to.be.true + }) + + it('includes all of the currently installed packages', () => { + let available = atom.packages.getAvailablePackageNames() + let installed = available.filter((name: string) => { return !atom.packages.isBundledPackage(name) }) + + expect(list.getPackages()).to.have.members(installed) + }) + }) + + describe('when the file does exist', () => { + beforeEach(() => { + createList([]) + }) + + it('updates the contents of the file', () => { + list.setPackages() + + let available = atom.packages.getAvailablePackageNames() + let installed = available.filter((name: string) => { return !atom.packages.isBundledPackage(name) }) + + expect(list.getPackages()).to.have.members(installed) + }) + }) + }) + }) +}) diff --git a/spec/package-list.test.coffee b/spec/package-list.test.coffee deleted file mode 100644 index 73bead1..0000000 --- a/spec/package-list.test.coffee +++ /dev/null @@ -1,83 +0,0 @@ -CSON = require 'season' -fs = require 'fs' -path = require 'path' -os = require 'os' - -PackageList = require '../lib/package-list' - -h = require './helpers' - -expect = chai.expect - -describe 'PackageList', -> - list = null - - beforeEach -> - spyOn(atom, 'getConfigDirPath').andReturn(os.tmpdir()) - list = new PackageList - - afterEach -> - h.deletePackages() - - describe 'getPackages', -> - it 'gives an empty array when there is not a packages.cson', -> - expect(list.getPackages()).toEqual([]) - - it 'gives an empty array when there is an empty packages.cson', -> - h.createPackages({'packages': []}) - expect(list.getPackages()).toEqual([]) - - it 'gives the packages list when there is a non-zero packages.cson', -> - h.createPackages({'packages': ['foo', 'bar', 'baz']}) - expect(list.getPackages()).toEqual(['foo', 'bar', 'baz']) - - describe 'setPackages', -> - beforeEach -> - spyOn(atom.packages, 'getAvailablePackageNames').andReturn(['foo', 'bar', 'baz']) - - describe 'when forceOverwrite is set to false', -> - beforeEach -> - atom.config.set('package-sync.forceOverwrite', false) - - it 'creates the packages.cson if it does not exist', -> - list.setPackages() - expect(fs.existsSync(h.getPackagesPath())).toBe(true) - - it 'creates the list of packages if packages.cson does not exist', -> - list.setPackages() - - packages = list.getPackages() - available = atom.packages.getAvailablePackageNames() - for pkg in available when not atom.packages.isBundledPackage(pkg) - expect(packages).toContain(pkg) - - - it 'does not update the packages.cson if it does exist', -> - h.createPackages({'packages': []}) - - list.setPackages() - - expect(list.getPackages()).toEqual([]) - - describe 'when forceOverwrite is set to true', -> - beforeEach -> - atom.config.set('package-sync.forceOverwrite', true) - - it 'creates the packages.cson if it does not exist', -> - list.setPackages() - expect(fs.existsSync(h.getPackagesPath())).toBe(true) - - it 'creates the list of packages if packages.cson does not exist', -> - list.setPackages() - - packages = list.getPackages() - available = atom.packages.getAvailablePackageNames() - for pkg in available when not atom.packages.isBundledPackage(pkg) - expect(packages).toContain(pkg) - - it 'updates the packages.cson if it does exist', -> - h.createPackages({'packages': []}) - - list.setPackages() - - expect(list.getPackages()).not.toEqual([]) diff --git a/spec/package-sync.spec.ts b/spec/package-sync.spec.ts new file mode 100644 index 0000000..544e73c --- /dev/null +++ b/spec/package-sync.spec.ts @@ -0,0 +1,26 @@ +import * as chai from 'chai' +import * as os from 'os' + +import PackageSync from '../lib/package-sync' + +import {createList, deleteList} from './helpers' + +const expect = chai.expect + +describe('PackageSync', () => { + let sync: PackageSync + + beforeEach(() => { + deleteList() + + sync = new PackageSync() + }) + + describe('getMissingPackages', () => { + it('gets a list of missing packages', () => { + createList(['foo', 'bar', 'baz']) + + expect(sync.getMissingPackages(os.tmpdir())).to.have.members(['foo', 'bar', 'baz']) + }) + }) +}) diff --git a/spec/package-sync.test.coffee b/spec/package-sync.test.coffee deleted file mode 100644 index 619e499..0000000 --- a/spec/package-sync.test.coffee +++ /dev/null @@ -1,26 +0,0 @@ -os = require 'os' - -PackageSync = require '../lib/package-sync' - -h = require './helpers' - -describe 'PackageSync', -> - beforeEach -> - @sync = new PackageSync() - - afterEach -> - h.deletePackages() - - describe 'getMissingPackages', -> - it 'gets a list of missing packages', -> - h.createPackages({'packages': ['foo', 'bar', 'baz']}) - spyOn(atom, 'getConfigDirPath').andReturn(os.tmpdir()) - - expect(@sync.getMissingPackages()).toEqual(['foo', 'bar', 'baz']) - - it 'gets a list of missing packages, excluding ones that are not missing', -> - h.createPackages({'packages': ['foo', 'bar', 'baz']}) - spyOn(atom, 'getConfigDirPath').andReturn(os.tmpdir()) - spyOn(atom.packages, 'getAvailablePackageNames').andReturn(['foo']) - - expect(@sync.getMissingPackages()).toEqual(['bar', 'baz']) diff --git a/spec/runner.ts b/spec/runner.ts new file mode 100644 index 0000000..3c25152 --- /dev/null +++ b/spec/runner.ts @@ -0,0 +1,7 @@ +let createRunner = require('atom-mocha-test-runner').createRunner + +module.exports = createRunner({ + testSuffixes: ['spec.ts', 'spec.js'] +}, (mocha: any) => { + mocha.timeout(parseInt(process.env.MOCHA_TIMEOUT || '5000', 10)) +}) diff --git a/spec/status-message.test.coffee b/spec/status-message.test.coffee deleted file mode 100644 index 6b724d7..0000000 --- a/spec/status-message.test.coffee +++ /dev/null @@ -1,39 +0,0 @@ -StatusMessage = require '../lib/status-message' - -describe 'StatusMessage', -> - [statusBar] = [] - - beforeEach -> - workspaceElement = atom.views.getView(atom.workspace) - jasmine.attachToDOM(workspaceElement) - - waitsForPromise -> atom.packages.activatePackage('status-bar') - - runs -> - statusBar = document.querySelector 'status-bar' - - it 'displays a message in the status bar', -> - spyOn(statusBar, 'addLeftTile') - - message = new StatusMessage('Message') - - expect(statusBar.addLeftTile).toHaveBeenCalled() - - it 'does not throw if there is no status bar', -> - atom.packages.deactivatePackage('status-bar') - - new StatusMessage('Message') - - it 'removes the message', -> - message = new StatusMessage('Message') - tile = message.tile - spyOn(tile, 'destroy') - message.remove() - - expect(tile.destroy).toHaveBeenCalled() - - it 'updates the message', -> - message = new StatusMessage('Message') - message.setText('Something else') - - expect(message.item.innerHTML).toEqual('Something else') From 9e2e5549c72a0b6954674c0633f1a9e5d9d18ff4 Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 20:22:06 -0800 Subject: [PATCH 10/17] :fire: coffeelint.json --- coffeelint.json | 114 ------------------------------------------------ 1 file changed, 114 deletions(-) delete mode 100644 coffeelint.json diff --git a/coffeelint.json b/coffeelint.json deleted file mode 100644 index 97b888e..0000000 --- a/coffeelint.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "coffeescript_error": { - "level": "error" - }, - "arrow_spacing": { - "name": "arrow_spacing", - "level": "ignore" - }, - "no_tabs": { - "name": "no_tabs", - "level": "error" - }, - "no_trailing_whitespace": { - "name": "no_trailing_whitespace", - "level": "error", - "allowed_in_comments": false, - "allowed_in_empty_lines": true - }, - "max_line_length": { - "name": "max_line_length", - "value": 100, - "level": "error", - "limitComments": true - }, - "line_endings": { - "name": "line_endings", - "level": "ignore", - "value": "unix" - }, - "no_trailing_semicolons": { - "name": "no_trailing_semicolons", - "level": "error" - }, - "indentation": { - "name": "indentation", - "value": 2, - "level": "error" - }, - "camel_case_classes": { - "name": "camel_case_classes", - "level": "error" - }, - "colon_assignment_spacing": { - "name": "colon_assignment_spacing", - "level": "ignore", - "spacing": { - "left": 0, - "right": 0 - } - }, - "no_implicit_braces": { - "name": "no_implicit_braces", - "level": "ignore", - "strict": true - }, - "no_plusplus": { - "name": "no_plusplus", - "level": "ignore" - }, - "no_throwing_strings": { - "name": "no_throwing_strings", - "level": "error" - }, - "no_backticks": { - "name": "no_backticks", - "level": "error" - }, - "no_implicit_parens": { - "name": "no_implicit_parens", - "level": "ignore" - }, - "no_empty_param_list": { - "name": "no_empty_param_list", - "level": "ignore" - }, - "no_stand_alone_at": { - "name": "no_stand_alone_at", - "level": "ignore" - }, - "space_operators": { - "name": "space_operators", - "level": "ignore" - }, - "duplicate_key": { - "name": "duplicate_key", - "level": "error" - }, - "empty_constructor_needs_parens": { - "name": "empty_constructor_needs_parens", - "level": "ignore" - }, - "cyclomatic_complexity": { - "name": "cyclomatic_complexity", - "value": 10, - "level": "ignore" - }, - "newlines_after_classes": { - "name": "newlines_after_classes", - "value": 3, - "level": "ignore" - }, - "no_unnecessary_fat_arrows": { - "name": "no_unnecessary_fat_arrows", - "level": "warn" - }, - "missing_fat_arrows": { - "name": "missing_fat_arrows", - "level": "ignore" - }, - "non_empty_constructor_needs_parens": { - "name": "non_empty_constructor_needs_parens", - "level": "ignore" - } -} From bc514994dc3aa9a1f22572fba6965699410aa610 Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 20:25:59 -0800 Subject: [PATCH 11/17] Properly clean up after myself --- lib/index.ts | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index 7ccbc43..bacf558 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,34 +1,47 @@ +import {CompositeDisposable} from 'atom' + import PackageSync from './package-sync' +let disposable: CompositeDisposable + /** * Activates the package. */ export function activate(): void { + disposable = new CompositeDisposable() + let packageSync = new PackageSync() - atom.commands.add('atom-workspace', 'package-sync:create-package-list', () => { + disposable.add(atom.commands.add('atom-workspace', 'package-sync:create-package-list', () => { packageSync.createPackageList() - }) + })) - atom.commands.add('atom-workspace', 'package-sync:open-package-list', () => { + disposable.add(atom.commands.add('atom-workspace', 'package-sync:open-package-list', () => { packageSync.openPackageList() - }) + })) - atom.commands.add('atom-workspace', 'package-sync:sync', () => { + disposable.add(atom.commands.add('atom-workspace', 'package-sync:sync', () => { packageSync.sync() - }) + })) - atom.packages.onDidActivateInitialPackages(() => { - atom.packages.onDidLoadPackage(() => { + disposable.add(atom.packages.onDidActivateInitialPackages(() => { + disposable.add(atom.packages.onDidLoadPackage(() => { if (atom.config.get('package-sync.createOnChange')) { packageSync.createPackageList() } - }) + })) - atom.packages.onDidUnloadPackage(() => { + disposable.add(atom.packages.onDidUnloadPackage(() => { if (atom.config.get('package-sync.createOnChange')) { packageSync.createPackageList() } - }) - }) + })) + })) +} + +/** + * Deactivates the package. + */ +export function deactivate(): void { + disposable.dispose() } From aacdd1866c8010f0cb1ec0632efe0a29924d1f4b Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 20:42:24 -0800 Subject: [PATCH 12/17] Flesh out the season type documentation --- lib/season.d.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/lib/season.d.ts b/lib/season.d.ts index 42642af..c253565 100644 --- a/lib/season.d.ts +++ b/lib/season.d.ts @@ -1,7 +1,48 @@ // Type definitions for atom/season declare module 'season' { + /** + * Returns `true` if there is a `cson` or `json` file at the given path, minus extension; false + * otherwise. + */ + export function isObjectPath(objectPath: string): boolean + + /** + * Read the CSON or JSON object at the given path. + */ + export function readFile(objectPath: string, callback: (err: Error, obj: object) => void): void + + /** + * Read the CSON or JSON object at the given path synchronously. + */ export function readFileSync(objectPath: string): object + /** + * Resolve the path (minus extension) to a CSON or JSON object file. + * + * Returns `null` if neither was found. + */ + export function resolve(objectPath: string): string | null + + /** + * Sets the cache directory to use for storing compiled CSON files. + */ + export function setCacheDir(cacheDirectory: string): void + + /** + * Convert the object to a CSON string. + */ + export function stringify(obj: object): string + + /** + * Write the object to the given path as a CSON or JSON file depending on the path's extension. + * + * Callback is called in case of an error. + */ + export function writeFile(objectPath: string, obj: object, callback: (err: Error) => void): void + + /** + * Write the object to the given path as a CSON or JSON file depending on the path's extension. + */ export function writeFileSync(objectPath: string, obj: object): void } From 792b38bbb1fa3190f7361c129d2ed3fa4c64f375 Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 20:49:22 -0800 Subject: [PATCH 13/17] Add ConfigValues types --- lib/config.d.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 lib/config.d.ts diff --git a/lib/config.d.ts b/lib/config.d.ts new file mode 100644 index 0000000..053d8b8 --- /dev/null +++ b/lib/config.d.ts @@ -0,0 +1,9 @@ +declare module "atom" { + interface ConfigValues { + /** Create packages list when packages or themes are installed or removed */ + 'package-sync.createOnChange': boolean + + /** Overwrite packages list even when it is present */ + 'package-sync.forceOverwrite': boolean + } +} From ad3131fbf2067419154fd88d95da0e330e21e1af Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Mon, 1 Jan 2018 21:41:19 -0800 Subject: [PATCH 14/17] Convert to using atom-ide-ui's busy-signal service --- lib/busy-signal.d.ts | 31 ++++++++++++++++++ lib/config.d.ts | 9 ------ lib/index.ts | 24 ++++++++++++-- lib/package-sync.ts | 74 ++++++++++++------------------------------- lib/status-message.ts | 46 --------------------------- package.json | 7 ++++ 6 files changed, 80 insertions(+), 111 deletions(-) create mode 100644 lib/busy-signal.d.ts delete mode 100644 lib/config.d.ts delete mode 100644 lib/status-message.ts diff --git a/lib/busy-signal.d.ts b/lib/busy-signal.d.ts new file mode 100644 index 0000000..e060202 --- /dev/null +++ b/lib/busy-signal.d.ts @@ -0,0 +1,31 @@ +declare module 'busy-signal' { + export interface BusyMessage { + /** + * Clears the busy message. + */ + dispose(): void + + /** + * Sets the title of the busy message. + */ + setTitle(title: string): void + } + + export interface BusySignalOptions { + /** + * If set to `true`, the busy signal tooltip will be immediately revealed + * when it first becomes visible (without explicit mouse interaction). + */ + revealTooltip?: boolean + } + + export interface BusySignalService { + /** + * Activates the busy signal. + * + * The title can be updated in the returned `BusyMessage` object and the signal can be + * deactivated by calling `dispose` on the `BusyMessage` object. + */ + reportBusy(title: string, options?: BusySignalOptions): BusyMessage + } +} diff --git a/lib/config.d.ts b/lib/config.d.ts deleted file mode 100644 index 053d8b8..0000000 --- a/lib/config.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -declare module "atom" { - interface ConfigValues { - /** Create packages list when packages or themes are installed or removed */ - 'package-sync.createOnChange': boolean - - /** Overwrite packages list even when it is present */ - 'package-sync.forceOverwrite': boolean - } -} diff --git a/lib/index.ts b/lib/index.ts index bacf558..adedaad 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,8 +1,19 @@ +/// + import {CompositeDisposable} from 'atom' +import {BusySignalService} from 'busy-signal' import PackageSync from './package-sync' +let busySignal: BusySignalService let disposable: CompositeDisposable +let packageSync: PackageSync + +function loadModule(): void { + if (!packageSync) { + packageSync = new PackageSync() + } +} /** * Activates the package. @@ -10,35 +21,42 @@ let disposable: CompositeDisposable export function activate(): void { disposable = new CompositeDisposable() - let packageSync = new PackageSync() - disposable.add(atom.commands.add('atom-workspace', 'package-sync:create-package-list', () => { + loadModule() packageSync.createPackageList() })) disposable.add(atom.commands.add('atom-workspace', 'package-sync:open-package-list', () => { + loadModule() packageSync.openPackageList() })) disposable.add(atom.commands.add('atom-workspace', 'package-sync:sync', () => { - packageSync.sync() + loadModule() + packageSync.sync(busySignal) })) disposable.add(atom.packages.onDidActivateInitialPackages(() => { disposable.add(atom.packages.onDidLoadPackage(() => { if (atom.config.get('package-sync.createOnChange')) { + loadModule() packageSync.createPackageList() } })) disposable.add(atom.packages.onDidUnloadPackage(() => { if (atom.config.get('package-sync.createOnChange')) { + loadModule() packageSync.createPackageList() } })) })) } +export function consumeBusySignal(busySignalService: BusySignalService): void { + busySignal = busySignalService +} + /** * Deactivates the package. */ diff --git a/lib/package-sync.ts b/lib/package-sync.ts index 7688a7a..f34a757 100644 --- a/lib/package-sync.ts +++ b/lib/package-sync.ts @@ -1,25 +1,24 @@ +/// + import * as fs from 'fs' import {BufferedProcess} from 'atom' +import {BusyMessage, BusySignalService} from 'busy-signal' import PackageList from './package-list' -import StatusMessage from './status-message' export default class PackageSync { readonly apmPath = atom.packages.getApmPath() - readonly longMessageTimeout = 15000 - readonly shortMessageTimeout = 1000 + busyMessage: BusyMessage + busySignal: BusySignalService | null currentInstall: BufferedProcess | null - message: StatusMessage | null packagesToInstall: string[] - timeout: NodeJS.Timer | null constructor() { + this.busySignal = null this.currentInstall = null - this.message = null this.packagesToInstall = [] - this.timeout = null } /** @@ -41,28 +40,14 @@ export default class PackageSync { /** * Syncs the package list by installing any missing packages. */ - sync(): void { - let missing = this.getMissingPackages() - this.installPackages(missing) - } - - /** - * Displays the `message` for the given `timeout` in milliseconds or indefinitely. - */ - displayMessage(message: string, timeout?: number): void { - if (this.timeout) { - clearTimeout(this.timeout) - } - - if (this.message) { - this.message.setText(message) - } else { - this.message = new StatusMessage(message) + sync(busySignal: BusySignalService | null): void { + if (busySignal) { + this.busySignal = busySignal + this.busyMessage = this.busySignal.reportBusy('Starting package sync', {revealTooltip: true}) } - if (timeout) { - this.setMessageTimeout(timeout) - } + let missing = this.getMissingPackages() + this.installPackages(missing) } /** @@ -72,21 +57,13 @@ export default class PackageSync { * `packagesToInstall` list. */ executeApm(pkg: string): void { - this.displayMessage(`Installing ${pkg}`) - let command = this.apmPath let args = ['install', pkg] let stdout = (output: string) => {} let stderr = (output: string) => {} let exit = (exitCode: number) => { - if (exitCode === 0) { - if (this.packagesToInstall.length > 0) { - this.displayMessage(`${pkg} installed!`, this.shortMessageTimeout) - } else { - this.displayMessage('Package sync complete!', this.longMessageTimeout) - } - } else { - this.displayMessage(`An error occurred installing ${pkg}`, this.longMessageTimeout) + if (exitCode !== 0) { + this.busyMessage.setTitle(`An error occurred installing ${pkg}`) } this.currentInstall = null @@ -118,7 +95,14 @@ export default class PackageSync { let nextPackage = this.packagesToInstall.shift() if (nextPackage) { + if (this.busyMessage) { + this.busyMessage.setTitle(`Installing ${nextPackage}`) + } + this.executeApm(nextPackage) + } else { + this.busyMessage.dispose() + this.busySignal = null } } @@ -129,20 +113,4 @@ export default class PackageSync { this.packagesToInstall.push(...packages) this.installPackage() } - - /** - * Sets up a timeout for the currently displayed message. - */ - setMessageTimeout(timeout: number): void { - if (this.timeout) { - clearTimeout(this.timeout) - } - - this.timeout = setTimeout(() => { - if (this.message) { - this.message.remove() - this.message = null - } - }, timeout) - } } diff --git a/lib/status-message.ts b/lib/status-message.ts deleted file mode 100644 index 64c58cc..0000000 --- a/lib/status-message.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {StatusBar, Tile} from 'atom/status-bar' - -/** - * Displays a message in the status bar. - */ -export default class StatusMessage { - item: Element - statusBar: StatusBar - tile: Tile - - /** - * Displays `message` in the status bar. - * - * If the status bar does not exist, no message is displayed and no error - * occurs. - */ - constructor(message: string) { - this.statusBar = document.querySelector('status-bar') as StatusBar - - if (this.statusBar) { - this.item = document.createElement('div') - this.item.classList.add('inline-block') - this.setText(message) - - this.tile = this.statusBar.addLeftTile({item: this.item}) - } - } - - /** - * Removes the message from the status bar. - */ - remove(): void { - if (this.tile) { - this.tile.destroy() - } - } - - /** - * Updates the text of the message. - */ - setText(text: string): void { - if (this.statusBar) { - this.item.innerHTML = text - } - } -} diff --git a/package.json b/package.json index 2c97aaa..4f2acec 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,13 @@ "default": false } }, + "consumedServices": { + "atom-ide-busy-signal": { + "versions": { + "0.1.0": "consumeBusySignal" + } + } + }, "scripts": { "debug": "atom-transpiler-debug-tool" } From 652314a1fbe28507e3d44b1748f1c622550eaca3 Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Tue, 2 Jan 2018 06:06:24 -0800 Subject: [PATCH 15/17] :art: Clean up busy signal code --- lib/package-sync.ts | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/lib/package-sync.ts b/lib/package-sync.ts index f34a757..f521116 100644 --- a/lib/package-sync.ts +++ b/lib/package-sync.ts @@ -50,6 +50,16 @@ export default class PackageSync { this.installPackages(missing) } + /** + * Clears the busy message, if one is set. + */ + clearBusyMessage(): void { + if (this.busySignal) { + this.busyMessage.dispose() + this.busySignal = null + } + } + /** * Executes apm to install the given package by name. * @@ -63,7 +73,7 @@ export default class PackageSync { let stderr = (output: string) => {} let exit = (exitCode: number) => { if (exitCode !== 0) { - this.busyMessage.setTitle(`An error occurred installing ${pkg}`) + this.setBusyMessage(`An error occurred installing ${pkg}`) } this.currentInstall = null @@ -95,14 +105,10 @@ export default class PackageSync { let nextPackage = this.packagesToInstall.shift() if (nextPackage) { - if (this.busyMessage) { - this.busyMessage.setTitle(`Installing ${nextPackage}`) - } - + this.setBusyMessage(`Installing ${nextPackage}`) this.executeApm(nextPackage) } else { - this.busyMessage.dispose() - this.busySignal = null + this.clearBusyMessage() } } @@ -113,4 +119,13 @@ export default class PackageSync { this.packagesToInstall.push(...packages) this.installPackage() } + + /** + * Updates the busy message, if one is set. + */ + setBusyMessage(message: string): void { + if (this.busySignal) { + this.busyMessage.setTitle(message) + } + } } From 10968d170691813c85f73ee65e0db46f432d2cea Mon Sep 17 00:00:00 2001 From: Lee Dohm <1038121+lee-dohm@users.noreply.github.com> Date: Tue, 2 Jan 2018 16:09:41 -0800 Subject: [PATCH 16/17] :memo: --- lib/package-list.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/package-list.ts b/lib/package-list.ts index cadbcab..5799bae 100644 --- a/lib/package-list.ts +++ b/lib/package-list.ts @@ -2,6 +2,9 @@ import * as CSON from 'season' import * as fs from 'fs' import * as path from 'path' +/** + * Information that gets stored in the `packages.cson` file. + */ interface PackagesFile { packages: string[] } From fe40fbc228b6003cc18de60c688068e03bacdee5 Mon Sep 17 00:00:00 2001 From: lee-dohm <1038121+lee-dohm@users.noreply.github.com> Date: Wed, 3 Jan 2018 11:57:19 -0800 Subject: [PATCH 17/17] Add visibility signifiers to class definitions --- lib/package-list.ts | 17 ++++++++++----- lib/package-sync.ts | 52 ++++++++++++++++++++++----------------------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/lib/package-list.ts b/lib/package-list.ts index 5799bae..6914a77 100644 --- a/lib/package-list.ts +++ b/lib/package-list.ts @@ -6,23 +6,23 @@ import * as path from 'path' * Information that gets stored in the `packages.cson` file. */ interface PackagesFile { - packages: string[] + readonly packages: string[] } /** * Represents the stored package list. */ export default class PackageList { - path: string + private path: string - constructor(configDir = atom.getConfigDirPath()) { + public constructor(configDir = atom.getConfigDirPath()) { this.path = path.join(configDir, 'packages.cson') } /** * Gets the packages from the list. */ - getPackages(): string[] { + public getPackages(): string[] { if (fs.existsSync(this.path)) { let obj = CSON.readFileSync(this.path) as PackagesFile return obj.packages @@ -31,11 +31,18 @@ export default class PackageList { return [] } + /** + * Get the path where the list is stored. + */ + public getPath(): string { + return this.path + } + /** * Updates the stored package list with what is currently installed if the list doesn't exist or * the `forceOverwrite` configuration option is set to `true`. */ - setPackages(): void { + public setPackages(): void { if (atom.config.get('package-sync.forceOverwrite') || !fs.existsSync(this.path)) { let available = atom.packages.getAvailablePackageNames() let names = available.filter((name: string) => { return !atom.packages.isBundledPackage(name) }) diff --git a/lib/package-sync.ts b/lib/package-sync.ts index f521116..29667f3 100644 --- a/lib/package-sync.ts +++ b/lib/package-sync.ts @@ -8,14 +8,14 @@ import {BusyMessage, BusySignalService} from 'busy-signal' import PackageList from './package-list' export default class PackageSync { - readonly apmPath = atom.packages.getApmPath() + private readonly apmPath = atom.packages.getApmPath() - busyMessage: BusyMessage - busySignal: BusySignalService | null - currentInstall: BufferedProcess | null - packagesToInstall: string[] + private busyMessage: BusyMessage + private busySignal: BusySignalService | null + private currentInstall: BufferedProcess | null + private packagesToInstall: string[] - constructor() { + public constructor() { this.busySignal = null this.currentInstall = null this.packagesToInstall = [] @@ -24,23 +24,34 @@ export default class PackageSync { /** * Creates the package list. */ - createPackageList(configDir?: string): void { + public createPackageList(configDir?: string): void { let list = new PackageList(configDir) list.setPackages() } + /** + * Gets the list of missing package names by comparing against the current package list. + */ + public getMissingPackages(configDir?: string): string[] { + let list = new PackageList(configDir) + let syncPackages = list.getPackages() + let availablePackages = atom.packages.getAvailablePackageNames() + + return syncPackages.filter((value) => { return !(value in availablePackages) }) + } + /** * Opens the package list in the workspace. */ - openPackageList(configDir?: string): void { + public openPackageList(configDir?: string): void { let list = new PackageList(configDir) - atom.workspace.open(list.path) + atom.workspace.open(list.getPath()) } /** * Syncs the package list by installing any missing packages. */ - sync(busySignal: BusySignalService | null): void { + public sync(busySignal: BusySignalService | null): void { if (busySignal) { this.busySignal = busySignal this.busyMessage = this.busySignal.reportBusy('Starting package sync', {revealTooltip: true}) @@ -53,7 +64,7 @@ export default class PackageSync { /** * Clears the busy message, if one is set. */ - clearBusyMessage(): void { + private clearBusyMessage(): void { if (this.busySignal) { this.busyMessage.dispose() this.busySignal = null @@ -66,7 +77,7 @@ export default class PackageSync { * When the given package is done installing, it attempts to install the next package in the * `packagesToInstall` list. */ - executeApm(pkg: string): void { + private executeApm(pkg: string): void { let command = this.apmPath let args = ['install', pkg] let stdout = (output: string) => {} @@ -83,21 +94,10 @@ export default class PackageSync { this.currentInstall = new BufferedProcess({command, args, stdout, stderr, exit}) } - /** - * Gets the list of missing package names by comparing against the current package list. - */ - getMissingPackages(configDir?: string): string[] { - let list = new PackageList(configDir) - let syncPackages = list.getPackages() - let availablePackages = atom.packages.getAvailablePackageNames() - - return syncPackages.filter((value) => { return !(value in availablePackages) }) - } - /** * Installs the next package in the list, if one exists. */ - installPackage(): void { + private installPackage(): void { if (this.currentInstall) { return } @@ -115,7 +115,7 @@ export default class PackageSync { /** * Queues up the given list of packages to be installed and starts the process. */ - installPackages(packages: string[]): void { + private installPackages(packages: string[]): void { this.packagesToInstall.push(...packages) this.installPackage() } @@ -123,7 +123,7 @@ export default class PackageSync { /** * Updates the busy message, if one is set. */ - setBusyMessage(message: string): void { + private setBusyMessage(message: string): void { if (this.busySignal) { this.busyMessage.setTitle(message) }