-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 82a997f
Showing
6 changed files
with
231 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
dist | ||
node_modules | ||
npm-switch*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# NPM Switch |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
const { app, Tray, Menu, BrowserWindow, shell, globalShortcut, Notification } = require('electron'); | ||
const fs = require('fs').promises; | ||
const constants = require('fs').constants; | ||
const path = require('path'); | ||
const os = require('os'); | ||
const chokidar = require('chokidar'); | ||
const debug = require('debug'); | ||
const log = debug('npm-switch'); | ||
|
||
const icon = path.join(__dirname, 'npm_icon.png'); | ||
const configDir = path.resolve(os.homedir(), '.npm_switch'); | ||
const npmrcDir = path.resolve(configDir, '.npmrc'); | ||
const configFile = path.join(configDir, 'config.json'); | ||
const defaultNpmrc = path.resolve(os.homedir(), '.npmrc'); | ||
|
||
let tray; | ||
|
||
// look into for a lighter alternative | ||
// https://github.com/zaaack/node-systray | ||
// https://github.com/mikaelbr/node-notifier | ||
|
||
// https://nwjs.readthedocs.io/en/latest/References/Tray/ | ||
|
||
async function loadConfig() { | ||
let config; | ||
try { | ||
const buffer = await fs.readFile(path.join(configDir, 'config.json')); | ||
config = JSON.parse(buffer.toString()); | ||
} catch { | ||
// no config | ||
config = {}; | ||
} | ||
const files = await fs.readdir(npmrcDir); | ||
config.files = files; | ||
return config; | ||
} | ||
|
||
async function getContextMenu(config) { | ||
globalShortcut.unregisterAll(); | ||
const none = []; | ||
if (!config.selected) { | ||
none.push({ | ||
enabled: false, | ||
label: '.npmrc is unmanaged', | ||
type: 'radio', | ||
checked: true | ||
}); | ||
} | ||
const contextMenu = Menu.buildFromTemplate([ | ||
...none, | ||
...config.files.map((label, i) => { | ||
const accelerator = `CommandOrControl+Shift+${i + 1}`; | ||
const action = async () => { | ||
if (config.selected === label) { | ||
return; | ||
} | ||
// select item; | ||
// pop up system notification "switched npm to..." | ||
await copyNpmrc(label); | ||
config.selected = label; | ||
await updateConfig(config); | ||
const notification = new Notification({ | ||
title: 'NPM Switcher', | ||
icon, | ||
body: 'Switched .npmrc to ' + label, | ||
}); | ||
notification.show(); | ||
} | ||
globalShortcut.register(accelerator, action); | ||
return { | ||
label, | ||
accelerator, | ||
type: 'radio', | ||
click: action, | ||
checked: config.selected === label | ||
} | ||
}), | ||
{ | ||
type: 'separator' | ||
}, | ||
{ | ||
label: 'Configure...', | ||
click: () => { | ||
shell.openPath(npmrcDir); | ||
} | ||
}, | ||
{ | ||
label: 'Close NPM switcher', | ||
type: 'normal', | ||
role: 'quit', | ||
click: () => { | ||
app.quit(); | ||
} | ||
} | ||
]); | ||
return contextMenu; | ||
} | ||
|
||
async function loadContextMenu(tray) { | ||
const config = await loadConfig(); | ||
const contextMenu = await getContextMenu(config); | ||
tray.setContextMenu(contextMenu); | ||
log('config and menu loaded'); | ||
return contextMenu; | ||
} | ||
|
||
async function ensureDir(dir) { | ||
try { | ||
log('access ensureDir', dir); | ||
await fs.access(dir, constants.F_OK | constants.W_OK); | ||
} catch (err) { | ||
await fs.mkdir(dir); | ||
} | ||
} | ||
|
||
async function ensureFile(file, defaultData) { | ||
try { | ||
log('access ensureFile', file); | ||
await fs.access(file, constants.F_OK | constants.W_OK); | ||
} catch (err) { | ||
await fs.writeFile(file, defaultData); | ||
} | ||
} | ||
|
||
async function backupExisting() { | ||
const dir = path.resolve(configDir, '.npmrc.bak'); | ||
log('backing up existing .npmrc to', dir); | ||
await fs.copyFile(defaultNpmrc, dir); | ||
} | ||
|
||
async function ensureConfig() { | ||
await ensureDir(configDir); | ||
await ensureFile(configFile, JSON.stringify({ | ||
target: defaultNpmrc | ||
}, null, '\t')); | ||
await ensureDir(npmrcDir); | ||
} | ||
|
||
async function updateConfig({ selected, target }) { | ||
return fs.writeFile(configFile, JSON.stringify({ | ||
selected, | ||
target | ||
}, null, '\t')); | ||
} | ||
|
||
async function copyNpmrc(source) { | ||
try { | ||
log('checking access permissions', defaultNpmrc); | ||
await fs.access(defaultNpmrc, constants.F_OK | constants.W_OK); | ||
const stats = await fs.lstat(defaultNpmrc); | ||
if (!stats.isSymbolicLink()) { | ||
log('existing .npmrc is unmanaged, backing up...'); | ||
await backupExisting(); | ||
} | ||
log('removing existing file'); | ||
await fs.unlink(defaultNpmrc); | ||
} catch (err) { | ||
console.log(err); | ||
} | ||
const newSource = path.resolve(npmrcDir, source); | ||
log('creating link to', newSource); | ||
await fs.symlink(newSource, defaultNpmrc, 'file'); | ||
} | ||
|
||
app.on('ready', async () => { | ||
log('app ready'); | ||
try { | ||
await ensureConfig(); | ||
win = new BrowserWindow({ show: false }); | ||
tray = new Tray(icon); | ||
await loadContextMenu(tray); | ||
log('watching changes in', npmrcDir); | ||
const confWatcher = chokidar.watch(configFile); | ||
confWatcher.on('ready', () => { | ||
confWatcher.on('change', async (event, path) => { | ||
try { | ||
log('change detected in', configFile, event, path); | ||
await loadContextMenu(tray); | ||
} catch (err) { | ||
console.log(err); | ||
app.quit(1); | ||
} | ||
}); | ||
}); | ||
|
||
const watcher = chokidar.watch(npmrcDir); | ||
watcher.on('ready', () => { | ||
watcher.on('all', async (event, path) => { | ||
if (event === 'change') { | ||
return; | ||
} | ||
try { | ||
log('change detected in', npmrcDir, event, path); | ||
await loadContextMenu(tray); | ||
} catch (err) { | ||
console.log(err); | ||
app.quit(1); | ||
} | ||
}); | ||
}); | ||
} catch (err) { | ||
console.log(err); | ||
app.quit(1); | ||
} | ||
}); | ||
|
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"name": "npm-switch", | ||
"version": "1.0.0", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"build": "electron-packager . npm-switch --overwrite --icon=npm_icon.png", | ||
"start": "electron ." | ||
}, | ||
"author": "", | ||
"license": "ISC", | ||
"devDependencies": { | ||
"electron": "^9.0.5", | ||
"electron-packager": "^15.0.0" | ||
}, | ||
"dependencies": { | ||
"debug": "^4.1.1", | ||
"chokidar": "^3.4.0" | ||
} | ||
} |