diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3fd492c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing to Salesforce DX (SFDX) Status for iTerm2 +I'm happy about any ideas, suggestions for enhancements or contributions of code you might have! + +# Reporting a Bug +If you think you have found a bug, please + +- Check that your bug has not already been reported +- Create a new GitHub issue providing + - Your iTerm version + - Your SFDX version + - An explanation of the bug, providing the exact steps if it is reproducible + - Your expected behavior + - Any debug/error output + +# Suggesting an Enhancement +If you have an idea for a new feature, or an enhancement of an existing one, please raise a new GitHub issue. + +# Contributing your Code +If you already programmed a new feature or enhancement (awesome!), please + +1. Fork it! Clone it locally to start coding. +2. Create your feature branch: `git checkout -b my-new-feature` +3. Make and commit your changes: `git commit -m 'Add new feature'` +4. Push to the feature branch: `git push origin my-new-feature` +5. Submit a Pull Request from your feature branch to the `develop` branch + +See [here](https://blog.scottlowe.org/2015/01/27/using-fork-branch-git-workflow/) for a detailed explanation. \ No newline at end of file diff --git a/Example.png b/Example.png new file mode 100644 index 0000000..3d6b7cd Binary files /dev/null and b/Example.png differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb9a160 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Salesforce DX (SFDX) Status for iTerm2 +Adds the status of Salesforce DX projects to your iTerm2 status bar. + +## Requirements + +- [iTerm2](https://github.com/gnachman/iTerm2) + +## Installation + +1. Go to *iTerm2 > Preferences > General > Magic* and check "Enable Python API" +2. Copy `SFDX.py` to `~/Library/Application Support/iTerm2/Scripts/AutoLaunch/` +3. Go to *Scripts > AutoLaunch* and check "SFDX.py" to allow the script as a Status Bar component +4. Go to *Preferences > Profiles > > Session* and click "Configure Status Bar" +5. Drag & drop the component "SFDX Status" into the Active Components pane (you might need to scroll in the upper pane) +6. Select the component in the active pane, and click "Configure Component" +7. Make your respective configurations +8. `cd` into any directory with a `.force` project folder +9. Check your status bar + +## Usage + +- Simply open any directory that contains a `.force` folder and watch your status bar change +- If a cloud is displayed but nothing else, the current directory is not a SFDX project folder +- Click on the status bar component to display the org's expiration date + +![Example prompt](Example.png) + +**Note** The fish prompt for SFDX can be found [here](https://github.com/mschmidtkorth/fish-pure-prompt-salesforce-dx). + +## How to Contribute + +Please see the [contribution guidelines](CONTRIBUTING.md). + +## Changelog + +- **0.1.0** (2020-02-19) + - Initial release \ No newline at end of file diff --git a/SFDX.py b/SFDX.py new file mode 100644 index 0000000..341e959 --- /dev/null +++ b/SFDX.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3.7 +# encoding: utf-8 +# https://github.com/mschmidtkorth/iTerm-salesforce-dx/tree/master +import iterm2 + +aliasOrUsername = '' + + +async def main(connection): + # Executing 'sfdx force:org:display' takes >5 seconds, which would timeout coro() if executed within. Therefore, 'sfdx force:org:display' is only executed on demand within the onclick handler. + @iterm2.RPC + async def displayExpirationDate(session_id): + await component.async_open_popover(session_id, "

Retrieving org expiration date... (wait)

", iterm2.Size(300, 30)) + + from subprocess import Popen, PIPE + import json + p = Popen(['sfdx force:org:display --targetusername=' + aliasOrUsername + ' --json'], stdin = PIPE, stdout = PIPE, stderr = PIPE, universal_newlines = True, shell = True) + stdout, stderr = p.communicate() + result = '' + if stdout: + result = json.loads(stdout) + if stderr: + result = json.loads(stderr) + expiration = '' + if stderr and 'status' in result and result['status'] == 1: + expiration += '(error)' + elif stdout and 'status' in result and (result['status'] == 'Active' or result['status'] == 0): + expiration += result['result']['expirationDate'] + elif stdout and 'status' in result and (result['status'] == 'Expired' or result['status'] != 0): + expiration += '(expired)' + elif stdout: + expiration += 'Never - org is a sandbox.' + + await component.async_open_popover(session_id, "

The scratch org expires on: " + expiration + '

', iterm2.Size(350, 30)) + + knobs = [iterm2.CheckboxKnob("Enable expiration date", False, "showExpirationDate")] + component = iterm2.StatusBarComponent(short_description = "SFDX Status", detailed_description = "Provides status info for SFDX project folders", knobs = knobs, exemplar = u"\u2601 SFDC-1234 user@salesforce.com", update_cadence = None, identifier = "com.msk.sfdx") + + @iterm2.StatusBarRPC + async def coro(knobs, path = iterm2.Reference("path"), cwd = iterm2.Reference("user.currentDir?")): + import os + import json + from pathlib import Path + + currentDir = path + if 'force-app' in currentDir: # User is in subdirectory + currentDir = currentDir.split('force-app')[0] # Get root directory of project folder + if Path(currentDir + '/sfdx-project.json').is_file(): # Current folder is sfdx folder + # Retrieve defaultusername = alias + dir = currentDir if Path(currentDir + '/.sfdx/sfdx-config.json').is_file() else os.environ['HOME'] # Get config from project folder if present, or global config + with open(dir + '/.sfdx/sfdx-config.json') as configFile: + config = json.load(configFile) + global aliasOrUsername + aliasOrUsername = config['defaultusername'] + + # Retrieve username for alias + with open(os.environ['HOME'] + '/.sfdx/alias.json') as aliasFile: + alias = json.load(aliasFile) + + if 'defaultusername' in config and 'orgs' in alias: + return u'\u2601 ' + config['defaultusername'] + u' \u2022 ' + alias['orgs'][config['defaultusername']] + else: + return u'\u2601' # Some error occurred, e.g. file not found or wrong content + else: + return u'\u2601' # No SFDX directory + + await component.async_register(connection, coro, timeout = 15.0, onclick = displayExpirationDate) + + +iterm2.run_forever(main)