├── .github ├── no-response.yml └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── lib ├── main.js └── package-generator-view.js ├── menus └── package-generator.cson ├── package.json ├── spec ├── async-spec-helpers.js └── package-generator-spec.js └── styles └── package-generator.less /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an issue is closed for lack of response 4 | daysUntilClose: 28 5 | 6 | # Label requiring a response 7 | responseRequiredLabel: more-information-needed 8 | 9 | # Comment to post when closing an issue for lack of response. Set to `false` to disable. 10 | closeComment: > 11 | This issue has been automatically closed because there has been no response 12 | to our request for more information from the original author. With only the 13 | information that is currently in the issue, we don't have enough information 14 | to take action. Please reach out if you have or find the answers we need so 15 | that we can investigate further. 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | env: 6 | CI: true 7 | 8 | jobs: 9 | Test: 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | channel: [stable, beta] 14 | runs-on: ${{ matrix.os }} 15 | steps: 16 | - uses: actions/checkout@v1 17 | - uses: UziTech/action-setup-atom@v2 18 | with: 19 | version: ${{ matrix.channel }} 20 | - name: Install dependencies 21 | run: apm install 22 | - name: Run tests 23 | run: atom --test spec 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | See the [Atom contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md) 2 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### Prerequisites 10 | 11 | * [ ] Put an X between the brackets on this line if you have done all of the following: 12 | * Reproduced the problem in Safe Mode: http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode 13 | * Followed all applicable steps in the debugging guide: http://flight-manual.atom.io/hacking-atom/sections/debugging/ 14 | * Checked the FAQs on the message board for common solutions: https://discuss.atom.io/c/faq 15 | * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aatom 16 | * Checked that there is not already an Atom package that provides the described functionality: https://atom.io/packages 17 | 18 | ### Description 19 | 20 | [Description of the issue] 21 | 22 | ### Steps to Reproduce 23 | 24 | 1. [First Step] 25 | 2. [Second Step] 26 | 3. [and so on...] 27 | 28 | **Expected behavior:** [What you expect to happen] 29 | 30 | **Actual behavior:** [What actually happens] 31 | 32 | **Reproduces how often:** [What percentage of the time does it reproduce?] 33 | 34 | ### Versions 35 | 36 | You can get this information from copy and pasting the output of `atom --version` and `apm --version` from the command line. Also, please include the OS and what version of the OS you're running. 37 | 38 | ### Additional Information 39 | 40 | Any additional information, configuration or data that might be necessary to reproduce the issue. 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ### Description of the Change 7 | 8 | 13 | 14 | ### Alternate Designs 15 | 16 | 17 | 18 | ### Benefits 19 | 20 | 21 | 22 | ### Possible Drawbacks 23 | 24 | 25 | 26 | ### Applicable Issues 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### Atom and all repositories under Atom will be archived on December 15, 2022. Learn more in our [official announcement](https://github.blog/2022-06-08-sunsetting-atom/) 2 | # Package Generator package 3 | [![OS X Build Status](https://travis-ci.org/atom/package-generator.svg?branch=master)](https://travis-ci.org/atom/package-generator) 4 | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/7t1i4hdmljhigp9u/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/package-generator/branch/master) [![Dependency Status](https://david-dm.org/atom/package-generator.svg)](https://david-dm.org/atom/package-generator) 5 | 6 | 7 | Generates and opens a new sample package, language, or syntax theme in Atom. 8 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | const PackageGeneratorView = require('./package-generator-view') 2 | 3 | module.exports = { 4 | activate () { 5 | this.view = new PackageGeneratorView() 6 | }, 7 | 8 | deactivate () { 9 | if (this.view) this.view.destroy() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/package-generator-view.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const _ = require('underscore-plus') 3 | const {TextEditor, BufferedProcess, CompositeDisposable, Disposable} = require('atom') 4 | const fs = require('fs-plus') 5 | 6 | module.exports = 7 | class PackageGeneratorView { 8 | constructor () { 9 | this.disposables = new CompositeDisposable() 10 | 11 | this.element = document.createElement('div') 12 | this.element.classList.add('package-generator') 13 | 14 | this.miniEditor = new TextEditor({mini: true}) 15 | this.element.appendChild(this.miniEditor.element) 16 | 17 | this.error = document.createElement('div') 18 | this.error.classList.add('error') 19 | this.element.appendChild(this.error) 20 | 21 | this.message = document.createElement('div') 22 | this.message.classList.add('message') 23 | this.element.appendChild(this.message) 24 | 25 | this.disposables.add(atom.commands.add('atom-workspace', { 26 | 'package-generator:generate-package': () => this.attach('package'), 27 | 'package-generator:generate-language-package': () => this.attach('language'), 28 | 'package-generator:generate-syntax-theme': () => this.attach('theme') 29 | })) 30 | 31 | const blurHandler = () => this.close() 32 | this.miniEditor.element.addEventListener('blur', blurHandler) 33 | this.disposables.add(new Disposable(() => this.miniEditor.element.removeEventListener('blur', blurHandler))) 34 | this.disposables.add(atom.commands.add(this.element, { 35 | 'core:confirm': () => this.confirm(), 36 | 'core:cancel': () => this.close() 37 | })) 38 | } 39 | 40 | destroy () { 41 | if (this.panel != null) this.panel.destroy() 42 | this.disposables.dispose() 43 | } 44 | 45 | attach (mode) { 46 | this.mode = mode 47 | if (this.panel == null) this.panel = atom.workspace.addModalPanel({item: this, visible: false}) 48 | this.previouslyFocusedElement = document.activeElement 49 | this.panel.show() 50 | this.message.textContent = `Enter ${this.mode} path` 51 | if (this.mode === 'package') { 52 | this.setPathText('my-package') 53 | } else if (this.mode === 'language') { 54 | this.setPathText('language-my-language', [9, Infinity]) 55 | } else { 56 | this.setPathText('my-theme-syntax', [0, 8]) 57 | } 58 | this.miniEditor.element.focus() 59 | } 60 | 61 | setPathText (placeholderName, rangeToSelect) { 62 | if (rangeToSelect == null) rangeToSelect = [0, placeholderName.length] 63 | const packagesDirectory = this.getPackagesDirectory() 64 | this.miniEditor.setText(path.join(packagesDirectory, placeholderName)) 65 | const pathLength = this.miniEditor.getText().length 66 | const endOfDirectoryIndex = pathLength - placeholderName.length 67 | this.miniEditor.setSelectedBufferRange([[0, endOfDirectoryIndex + rangeToSelect[0]], [0, endOfDirectoryIndex + rangeToSelect[1]]]) 68 | } 69 | 70 | close () { 71 | if (!this.panel.isVisible()) return 72 | this.panel.hide() 73 | if (this.previouslyFocusedElement != null) this.previouslyFocusedElement.focus() 74 | } 75 | 76 | confirm () { 77 | if (this.validPackagePath()) { 78 | this.createPackageFiles(() => { 79 | const packagePath = this.getPackagePath() 80 | atom.open({pathsToOpen: [packagePath]}) 81 | this.close() 82 | }) 83 | } 84 | } 85 | 86 | getPackagePath () { 87 | const packagePath = fs.normalize(this.miniEditor.getText().trim()) 88 | const packageName = _.dasherize(path.basename(packagePath)) 89 | return path.join(path.dirname(packagePath), packageName) 90 | } 91 | 92 | getPackagesDirectory () { 93 | return process.env.ATOM_REPOS_HOME || atom.config.get('core.projectHome') || path.join(fs.getHomeDirectory(), 'github') 94 | } 95 | 96 | validPackagePath () { 97 | if (fs.existsSync(this.getPackagePath())) { 98 | this.error.textContent = `Path already exists at '${this.getPackagePath()}'` 99 | this.error.style.display = 'block' 100 | return false 101 | } else { 102 | return true 103 | } 104 | } 105 | 106 | getInitOptions (packagePath) { 107 | const options = [`--${this.mode}`, packagePath] 108 | if (this.mode !== 'theme') { 109 | return [...options, '--syntax', atom.config.get('package-generator.packageSyntax')] 110 | } else { 111 | return options 112 | } 113 | } 114 | 115 | initPackage (packagePath, callback) { 116 | const command = ['init'].concat(this.getInitOptions(packagePath)) 117 | this.runCommand(atom.packages.getApmPath(), command, callback) 118 | } 119 | 120 | linkPackage (packagePath, callback) { 121 | const args = ['link'] 122 | if (atom.config.get('package-generator.createInDevMode')) args.push('--dev') 123 | args.push(packagePath.toString()) 124 | 125 | this.runCommand(atom.packages.getApmPath(), args, callback) 126 | } 127 | 128 | isStoredInDotAtom (packagePath) { 129 | const packagesPath = path.join(atom.getConfigDirPath(), 'packages', path.sep) 130 | if (packagePath.startsWith(packagesPath)) return true 131 | 132 | const devPackagesPath = path.join(atom.getConfigDirPath(), 'dev', 'packages', path.sep) 133 | return packagePath.startsWith(devPackagesPath) 134 | } 135 | 136 | createPackageFiles (callback) { 137 | const packagePath = this.getPackagePath() 138 | 139 | if (this.isStoredInDotAtom(packagePath)) { 140 | this.initPackage(packagePath, callback) 141 | } else { 142 | this.initPackage(packagePath, () => this.linkPackage(packagePath, callback)) 143 | } 144 | } 145 | 146 | runCommand (command, args, exit) { 147 | this.process = new BufferedProcess({command, args, exit}) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /menus/package-generator.cson: -------------------------------------------------------------------------------- 1 | 'menu': [ 2 | 'label': 'Packages' 3 | 'submenu': [ 4 | 'label': 'Package Generator' 5 | 'submenu': [ 6 | { 'label': 'Generate Atom Package', 'command': 'package-generator:generate-package' } 7 | { 'label': 'Generate Atom Syntax Theme', 'command': 'package-generator:generate-syntax-theme' } 8 | ] 9 | ] 10 | ] 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "package-generator", 3 | "version": "1.3.0", 4 | "main": "./lib/main", 5 | "description": "Generates and opens a new sample package, language, or syntax theme.", 6 | "license": "MIT", 7 | "activationCommands": { 8 | "atom-workspace": [ 9 | "package-generator:generate-package", 10 | "package-generator:generate-language-package", 11 | "package-generator:generate-syntax-theme" 12 | ] 13 | }, 14 | "dependencies": { 15 | "fs-plus": "^3.0.0", 16 | "temp": "^0.8.1", 17 | "underscore-plus": "^1.0.0" 18 | }, 19 | "repository": "https://github.com/atom/package-generator", 20 | "engines": { 21 | "atom": "*" 22 | }, 23 | "devDependencies": { 24 | "standard": "^10.0.3" 25 | }, 26 | "configSchema": { 27 | "createInDevMode": { 28 | "default": false, 29 | "type": "boolean", 30 | "description": "When disabled, generated packages are linked into Atom in both normal mode and dev mode. When enabled, generated packages are linked into Atom only in dev mode." 31 | }, 32 | "packageSyntax": { 33 | "default": "javascript", 34 | "type": "string", 35 | "enum": [ 36 | "coffeescript", 37 | "javascript" 38 | ], 39 | "description": "The syntax to generate packages with." 40 | } 41 | }, 42 | "standard": { 43 | "env": { 44 | "atomtest": true, 45 | "browser": true, 46 | "jasmine": true, 47 | "node": true 48 | }, 49 | "globals": [ 50 | "atom" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spec/async-spec-helpers.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | export function beforeEach (fn) { 4 | global.beforeEach(function () { 5 | const result = fn() 6 | if (result instanceof Promise) { 7 | waitsForPromise(() => result) 8 | } 9 | }) 10 | } 11 | 12 | export function afterEach (fn) { 13 | global.afterEach(function () { 14 | const result = fn() 15 | if (result instanceof Promise) { 16 | waitsForPromise(() => result) 17 | } 18 | }) 19 | } 20 | 21 | ['it', 'fit', 'ffit', 'fffit'].forEach(function (name) { 22 | module.exports[name] = function (description, fn) { 23 | if (fn === undefined) { 24 | global[name](description) 25 | return 26 | } 27 | 28 | global[name](description, function () { 29 | const result = fn() 30 | if (result instanceof Promise) { 31 | waitsForPromise(() => result) 32 | } 33 | }) 34 | } 35 | }) 36 | 37 | export async function conditionPromise (condition, description = 'anonymous condition') { 38 | const startTime = Date.now() 39 | 40 | while (true) { 41 | await timeoutPromise(100) 42 | 43 | if (await condition()) { 44 | return 45 | } 46 | 47 | if (Date.now() - startTime > 5000) { 48 | throw new Error('Timed out waiting on ' + description) 49 | } 50 | } 51 | } 52 | 53 | export function timeoutPromise (timeout) { 54 | return new Promise(function (resolve) { 55 | global.setTimeout(resolve, timeout) 56 | }) 57 | } 58 | 59 | function waitsForPromise (fn) { 60 | const promise = fn() 61 | global.waitsFor('spec promise to resolve', function (done) { 62 | promise.then(done, function (error) { 63 | jasmine.getEnv().currentSpec.fail(error) 64 | done() 65 | }) 66 | }) 67 | } 68 | 69 | export function emitterEventPromise (emitter, event, timeout = 15000) { 70 | return new Promise((resolve, reject) => { 71 | const timeoutHandle = setTimeout(() => { 72 | reject(new Error(`Timed out waiting for '${event}' event`)) 73 | }, timeout) 74 | emitter.once(event, () => { 75 | clearTimeout(timeoutHandle) 76 | resolve() 77 | }) 78 | }) 79 | } 80 | 81 | export function promisify (original) { 82 | return function (...args) { 83 | return new Promise((resolve, reject) => { 84 | args.push((err, ...results) => { 85 | if (err) { 86 | reject(err) 87 | } else { 88 | resolve(...results) 89 | } 90 | }) 91 | 92 | return original(...args) 93 | }) 94 | } 95 | } 96 | 97 | export function promisifySome (obj, fnNames) { 98 | const result = {} 99 | for (const fnName of fnNames) { 100 | result[fnName] = promisify(obj[fnName]) 101 | } 102 | return result 103 | } 104 | -------------------------------------------------------------------------------- /spec/package-generator-spec.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs-plus') 3 | const temp = require('temp') 4 | const PackageGeneratorView = require('../lib/package-generator-view') 5 | 6 | const {it, fit, ffit, afterEach, beforeEach, conditionPromise} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars 7 | 8 | describe('Package Generator', () => { 9 | let packageGeneratorView = null 10 | 11 | const getWorkspaceView = () => atom.views.getView(atom.workspace) 12 | 13 | const typeToPackageNameMap = new Map([ 14 | ['package', 'my-package'], 15 | ['language', 'language-my-language'], 16 | ['theme', 'my-theme-syntax'] 17 | ]) 18 | 19 | const typeToSelectedTextMap = new Map([ 20 | ['package', 'my-package'], 21 | ['language', 'my-language'], 22 | ['theme', 'my-theme'] 23 | ]) 24 | 25 | beforeEach(async () => { 26 | await atom.workspace.open('sample.js') 27 | 28 | packageGeneratorView = new PackageGeneratorView() 29 | }) 30 | 31 | for (const [type, name] of typeToPackageNameMap) { 32 | describe(`when generating a ${type}`, () => { 33 | it('displays a mini-editor with the correct text and selection', () => { 34 | packageGeneratorView.attach(type) 35 | const editor = packageGeneratorView.miniEditor 36 | expect(editor.getSelectedText()).toEqual(typeToSelectedTextMap.get(type)) 37 | const base = atom.config.get('core.projectHome') 38 | expect(editor.getText()).toEqual(path.join(base, name)) 39 | }) 40 | }) 41 | } 42 | 43 | describe('when ATOM_REPOS_HOME is set', () => { 44 | beforeEach(() => { 45 | process.env.ATOM_REPOS_HOME = '/atom/repos/home' 46 | }) 47 | 48 | afterEach(() => { 49 | delete process.env.ATOM_REPOS_HOME 50 | }) 51 | 52 | it('overrides the default path', () => { 53 | packageGeneratorView.attach('package') 54 | const editor = packageGeneratorView.miniEditor 55 | expect(editor.getSelectedText()).toEqual('my-package') 56 | const base = '/atom/repos/home' 57 | expect(editor.getText()).toEqual(path.join(base, 'my-package')) 58 | }) 59 | }) 60 | 61 | describe('when the modal panel is canceled', () => { 62 | it('detaches from the DOM and focuses the the previously focused element', () => { 63 | jasmine.attachToDOM(getWorkspaceView()) 64 | packageGeneratorView.attach('theme') 65 | expect(packageGeneratorView.previouslyFocusedElement).not.toBeUndefined() 66 | 67 | expect(document.activeElement.closest('atom-text-editor')).toBe(packageGeneratorView.element.querySelector('atom-text-editor')) 68 | 69 | packageGeneratorView.close() 70 | expect(atom.workspace.getModalPanels()[0].isVisible()).toBe(false) 71 | expect(document.activeElement.closest('atom-text-editor')).toBe(atom.views.getView(atom.workspace.getActiveTextEditor())) 72 | }) 73 | }) 74 | 75 | describe('when a package is generated', () => { 76 | let [packageName, packagePath, packageRoot] = [] 77 | 78 | const packageInitCommandFor = (path, type = 'package', syntax = atom.config.get('package-generator.packageSyntax')) => { 79 | if (type !== 'theme') { 80 | return ['init', `--${type}`, path, '--syntax', syntax] 81 | } else { 82 | return ['init', `--${type}`, path] 83 | } 84 | } 85 | 86 | beforeEach(() => { 87 | spyOn(atom, 'open') 88 | 89 | packageRoot = temp.mkdirSync('atom') 90 | packageName = 'sweet-package-dude' 91 | packagePath = path.join(packageRoot, packageName) 92 | fs.removeSync(packageRoot) 93 | }) 94 | 95 | afterEach(() => fs.removeSync(packageRoot)) 96 | 97 | it("forces the package's name to be lowercase with dashes", () => { 98 | packageName = 'CamelCaseIsForTheBirds' 99 | packagePath = path.join(path.dirname(packagePath), packageName) 100 | 101 | packageGeneratorView.attach('package') 102 | const editor = packageGeneratorView.miniEditor 103 | editor.setText(packagePath) 104 | const apmExecute = spyOn(packageGeneratorView, 'runCommand') 105 | packageGeneratorView.confirm() 106 | 107 | expect(apmExecute).toHaveBeenCalled() 108 | expect(apmExecute.mostRecentCall.args[0]).toBe(atom.packages.getApmPath()) 109 | expect(apmExecute.mostRecentCall.args[1]).toEqual(packageInitCommandFor(`${path.join(path.dirname(packagePath), 'camel-case-is-for-the-birds')}`)) 110 | }) 111 | 112 | it("normalizes the package's path", () => { 113 | packagePath = path.join('~', 'the-package') 114 | 115 | packageGeneratorView.attach('package') 116 | const editor = packageGeneratorView.miniEditor 117 | editor.setText(packagePath) 118 | const apmExecute = spyOn(packageGeneratorView, 'runCommand') 119 | packageGeneratorView.confirm() 120 | 121 | expect(apmExecute).toHaveBeenCalled() 122 | expect(apmExecute.mostRecentCall.args[0]).toBe(atom.packages.getApmPath()) 123 | expect(apmExecute.mostRecentCall.args[1]).toEqual(packageInitCommandFor(`${fs.normalize(packagePath)}`)) 124 | }) 125 | 126 | for (const type of typeToPackageNameMap.keys()) { 127 | describe(`when creating a ${type}`, () => { 128 | let apmExecute = null 129 | 130 | const generatePackage = async (insidePackagesDirectory) => { 131 | const editor = packageGeneratorView.miniEditor 132 | spyOn(packageGeneratorView, 'isStoredInDotAtom').andReturn(insidePackagesDirectory) 133 | expect(packageGeneratorView.element.parentElement).toBeTruthy() 134 | editor.setText(packagePath) 135 | apmExecute = spyOn(packageGeneratorView, 'runCommand').andCallFake((command, args, exit) => process.nextTick(() => exit())) 136 | packageGeneratorView.confirm() 137 | await conditionPromise(() => atom.open.callCount === 1) 138 | expect(atom.open).toHaveBeenCalledWith({pathsToOpen: [packagePath]}) 139 | } 140 | 141 | beforeEach(() => { 142 | jasmine.useRealClock() 143 | jasmine.attachToDOM(getWorkspaceView()) 144 | packageGeneratorView.attach(type) 145 | }) 146 | 147 | describe(`when the ${type} is created outside of the packages directory`, () => { 148 | describe('when package-generator.createInDevMode is set to false', () => { 149 | it('calls `apm init` and `apm link`', async () => { 150 | atom.config.set('package-generator.createInDevMode', false) 151 | 152 | await generatePackage(false) 153 | expect(apmExecute.argsForCall[0][0]).toBe(atom.packages.getApmPath()) 154 | expect(apmExecute.argsForCall[0][1]).toEqual(packageInitCommandFor(`${packagePath}`, type)) 155 | expect(apmExecute.argsForCall[1][0]).toBe(atom.packages.getApmPath()) 156 | expect(apmExecute.argsForCall[1][1]).toEqual(['link', `${packagePath}`]) 157 | }) 158 | }) 159 | 160 | describe('when package-generator.createInDevMode is set to true', () => { 161 | it('calls `apm init` and `apm link --dev`', async () => { 162 | atom.config.set('package-generator.createInDevMode', true) 163 | 164 | await generatePackage(false) 165 | expect(apmExecute.argsForCall[0][0]).toBe(atom.packages.getApmPath()) 166 | expect(apmExecute.argsForCall[0][1]).toEqual(packageInitCommandFor(`${packagePath}`, type)) 167 | expect(apmExecute.argsForCall[1][0]).toBe(atom.packages.getApmPath()) 168 | expect(apmExecute.argsForCall[1][1]).toEqual(['link', '--dev', `${packagePath}`]) 169 | }) 170 | }) 171 | }) 172 | 173 | describe(`when the ${type} is created inside the packages directory`, () => { 174 | it('calls `apm init`', async () => { 175 | await generatePackage(true) 176 | expect(apmExecute.argsForCall[0][0]).toBe(atom.packages.getApmPath()) 177 | expect(apmExecute.argsForCall[0][1]).toEqual(packageInitCommandFor(`${packagePath}`, type)) 178 | expect(atom.open.argsForCall[0][0].pathsToOpen[0]).toBe(packagePath) 179 | expect(apmExecute.argsForCall[1]).toBeUndefined() 180 | }) 181 | }) 182 | 183 | describe(`when the ${type} is a coffeescript package`, () => { 184 | it('calls `apm init` with the correct syntax option', async () => { 185 | atom.config.set('package-generator.packageSyntax', 'coffeescript') 186 | await generatePackage(true) 187 | expect(apmExecute.argsForCall[0][0]).toBe(atom.packages.getApmPath()) 188 | expect(apmExecute.argsForCall[0][1]).toEqual(packageInitCommandFor(`${packagePath}`, type, 'coffeescript')) 189 | }) 190 | }) 191 | 192 | describe(`when the ${type} is a javascript package`, () => { 193 | it('calls `apm init` with the correct syntax option', async () => { 194 | atom.config.set('package-generator.packageSyntax', 'javascript') 195 | await generatePackage(true) 196 | expect(apmExecute.argsForCall[0][0]).toBe(atom.packages.getApmPath()) 197 | expect(apmExecute.argsForCall[0][1]).toEqual(packageInitCommandFor(`${packagePath}`, type, 'javascript')) 198 | }) 199 | }) 200 | 201 | describe(`when the ${type} path already exists`, () => { 202 | it('displays an error', () => { 203 | fs.makeTreeSync(packagePath) 204 | 205 | const editor = packageGeneratorView.miniEditor 206 | editor.setText(packagePath) 207 | expect(packageGeneratorView.element.parentElement).toBeTruthy() 208 | expect(packageGeneratorView.element.querySelector('.error').offsetHeight).toBe(0) 209 | 210 | packageGeneratorView.confirm() 211 | expect(packageGeneratorView.element.parentElement).toBeTruthy() 212 | expect(packageGeneratorView.element.querySelector('.error').offsetHeight).not.toBe(0) 213 | }) 214 | }) 215 | }) 216 | } 217 | }) 218 | }) 219 | -------------------------------------------------------------------------------- /styles/package-generator.less: -------------------------------------------------------------------------------- 1 | .package-generator .error { 2 | display: none; 3 | } --------------------------------------------------------------------------------