Merge commit '1d9a4cafcf6cc288d675512db8fd984e13aab869' into dw-windows-separate-channels

This commit is contained in:
Rafael Oleza 2019-06-01 00:28:37 +02:00
commit 79f6836349
300 changed files with 57696 additions and 45988 deletions

View File

@ -1,4 +1,4 @@
spec/fixtures/**/*.js
**/spec/fixtures/**/*.js
node_modules
/vendor/
/out/

View File

@ -25,7 +25,7 @@
"rules": {
"standard/no-callback-literal": ["off"],
"node/no-deprecated-api": ["off"],
"prettier/prettier": ["off"] // disable prettier rules for now.
"prettier/prettier": ["error"]
},
"overrides": [
{

View File

@ -1,4 +1,3 @@
{
"semi": false,
"singleQuote": true
}

View File

@ -1,72 +1,75 @@
const Chart = require('chart.js')
const glob = require('glob')
const fs = require('fs-plus')
const path = require('path')
const Chart = require('chart.js');
const glob = require('glob');
const fs = require('fs-plus');
const path = require('path');
module.exports = async ({test, benchmarkPaths}) => {
document.body.style.backgroundColor = '#ffffff'
document.body.style.overflow = 'auto'
module.exports = async ({ test, benchmarkPaths }) => {
document.body.style.backgroundColor = '#ffffff';
document.body.style.overflow = 'auto';
let paths = []
let paths = [];
for (const benchmarkPath of benchmarkPaths) {
if (fs.isDirectorySync(benchmarkPath)) {
paths = paths.concat(glob.sync(path.join(benchmarkPath, '**', '*.bench.js')))
paths = paths.concat(
glob.sync(path.join(benchmarkPath, '**', '*.bench.js'))
);
} else {
paths.push(benchmarkPath)
paths.push(benchmarkPath);
}
}
while (paths.length > 0) {
const benchmark = require(paths.shift())({test})
let results
const benchmark = require(paths.shift())({ test });
let results;
if (benchmark instanceof Promise) {
results = await benchmark
results = await benchmark;
} else {
results = benchmark
results = benchmark;
}
const dataByBenchmarkName = {}
for (const {name, duration, x} of results) {
dataByBenchmarkName[name] = dataByBenchmarkName[name] || {points: []}
dataByBenchmarkName[name].points.push({x, y: duration})
const dataByBenchmarkName = {};
for (const { name, duration, x } of results) {
dataByBenchmarkName[name] = dataByBenchmarkName[name] || { points: [] };
dataByBenchmarkName[name].points.push({ x, y: duration });
}
const benchmarkContainer = document.createElement('div')
document.body.appendChild(benchmarkContainer)
const benchmarkContainer = document.createElement('div');
document.body.appendChild(benchmarkContainer);
for (const key in dataByBenchmarkName) {
const data = dataByBenchmarkName[key]
const data = dataByBenchmarkName[key];
if (data.points.length > 1) {
const canvas = document.createElement('canvas')
benchmarkContainer.appendChild(canvas)
const canvas = document.createElement('canvas');
benchmarkContainer.appendChild(canvas);
// eslint-disable-next-line no-new
new Chart(canvas, {
type: 'line',
data: {
datasets: [{label: key, fill: false, data: data.points}]
datasets: [{ label: key, fill: false, data: data.points }]
},
options: {
showLines: false,
scales: {xAxes: [{type: 'linear', position: 'bottom'}]}
scales: { xAxes: [{ type: 'linear', position: 'bottom' }] }
}
})
});
const textualOutput = `${key}:\n\n` + data.points.map((p) => `${p.x}\t${p.y}`).join('\n')
console.log(textualOutput)
const textualOutput =
`${key}:\n\n` + data.points.map(p => `${p.x}\t${p.y}`).join('\n');
console.log(textualOutput);
} else {
const title = document.createElement('h2')
title.textContent = key
benchmarkContainer.appendChild(title)
const duration = document.createElement('p')
duration.textContent = `${data.points[0].y}ms`
benchmarkContainer.appendChild(duration)
const title = document.createElement('h2');
title.textContent = key;
benchmarkContainer.appendChild(title);
const duration = document.createElement('p');
duration.textContent = `${data.points[0].y}ms`;
benchmarkContainer.appendChild(duration);
const textualOutput = `${key}: ${data.points[0].y}`
console.log(textualOutput)
const textualOutput = `${key}: ${data.points[0].y}`;
console.log(textualOutput);
}
await global.atom.reset()
await global.atom.reset();
}
}
return 0
}
return 0;
};

View File

@ -1,88 +1,100 @@
const {TextEditor, TextBuffer} = require('atom')
const { TextEditor, TextBuffer } = require('atom');
const MIN_SIZE_IN_KB = 0 * 1024
const MAX_SIZE_IN_KB = 10 * 1024
const SIZE_STEP_IN_KB = 1024
const LINE_TEXT = 'Lorem ipsum dolor sit amet\n'
const TEXT = LINE_TEXT.repeat(Math.ceil(MAX_SIZE_IN_KB * 1024 / LINE_TEXT.length))
const MIN_SIZE_IN_KB = 0 * 1024;
const MAX_SIZE_IN_KB = 10 * 1024;
const SIZE_STEP_IN_KB = 1024;
const LINE_TEXT = 'Lorem ipsum dolor sit amet\n';
const TEXT = LINE_TEXT.repeat(
Math.ceil((MAX_SIZE_IN_KB * 1024) / LINE_TEXT.length)
);
module.exports = async ({test}) => {
const data = []
module.exports = async ({ test }) => {
const data = [];
document.body.appendChild(atom.workspace.getElement())
document.body.appendChild(atom.workspace.getElement());
atom.packages.loadPackages()
await atom.packages.activate()
atom.packages.loadPackages();
await atom.packages.activate();
for (let pane of atom.workspace.getPanes()) {
pane.destroy()
pane.destroy();
}
for (let sizeInKB = MIN_SIZE_IN_KB; sizeInKB < MAX_SIZE_IN_KB; sizeInKB += SIZE_STEP_IN_KB) {
const text = TEXT.slice(0, sizeInKB * 1024)
console.log(text.length / 1024)
for (
let sizeInKB = MIN_SIZE_IN_KB;
sizeInKB < MAX_SIZE_IN_KB;
sizeInKB += SIZE_STEP_IN_KB
) {
const text = TEXT.slice(0, sizeInKB * 1024);
console.log(text.length / 1024);
let t0 = window.performance.now()
const buffer = new TextBuffer({text})
const editor = new TextEditor({buffer, autoHeight: false, largeFileMode: true})
atom.grammars.autoAssignLanguageMode(buffer)
atom.workspace.getActivePane().activateItem(editor)
let t1 = window.performance.now()
let t0 = window.performance.now();
const buffer = new TextBuffer({ text });
const editor = new TextEditor({
buffer,
autoHeight: false,
largeFileMode: true
});
atom.grammars.autoAssignLanguageMode(buffer);
atom.workspace.getActivePane().activateItem(editor);
let t1 = window.performance.now();
data.push({
name: 'Opening a large file',
x: sizeInKB,
duration: t1 - t0
})
});
const tickDurations = []
const tickDurations = [];
for (let i = 0; i < 20; i++) {
await timeout(50)
t0 = window.performance.now()
await timeout(0)
t1 = window.performance.now()
tickDurations[i] = t1 - t0
await timeout(50);
t0 = window.performance.now();
await timeout(0);
t1 = window.performance.now();
tickDurations[i] = t1 - t0;
}
data.push({
name: 'Max time event loop was blocked after opening a large file',
x: sizeInKB,
duration: Math.max(...tickDurations)
})
});
t0 = window.performance.now()
editor.setCursorScreenPosition(editor.element.screenPositionForPixelPosition({
t0 = window.performance.now();
editor.setCursorScreenPosition(
editor.element.screenPositionForPixelPosition({
top: 100,
left: 30
}))
t1 = window.performance.now()
})
);
t1 = window.performance.now();
data.push({
name: 'Clicking the editor after opening a large file',
x: sizeInKB,
duration: t1 - t0
})
});
t0 = window.performance.now()
editor.element.setScrollTop(editor.element.getScrollTop() + 100)
t1 = window.performance.now()
t0 = window.performance.now();
editor.element.setScrollTop(editor.element.getScrollTop() + 100);
t1 = window.performance.now();
data.push({
name: 'Scrolling down after opening a large file',
x: sizeInKB,
duration: t1 - t0
})
});
editor.destroy()
buffer.destroy()
await timeout(10000)
editor.destroy();
buffer.destroy();
await timeout(10000);
}
atom.workspace.getElement().remove()
atom.workspace.getElement().remove();
return data
}
return data;
};
function timeout (duration) {
return new Promise((resolve) => setTimeout(resolve, duration))
function timeout(duration) {
return new Promise(resolve => setTimeout(resolve, duration));
}

View File

@ -1,95 +1,105 @@
const path = require('path')
const fs = require('fs')
const {TextEditor, TextBuffer} = require('atom')
const path = require('path');
const fs = require('fs');
const { TextEditor, TextBuffer } = require('atom');
const SIZES_IN_KB = [
512,
1024,
2048
]
const REPEATED_TEXT = fs.readFileSync(path.join(__dirname, '..', 'spec', 'fixtures', 'sample.js'), 'utf8').replace(/\n/g, '')
const TEXT = REPEATED_TEXT.repeat(Math.ceil(SIZES_IN_KB[SIZES_IN_KB.length - 1] * 1024 / REPEATED_TEXT.length))
const SIZES_IN_KB = [512, 1024, 2048];
const REPEATED_TEXT = fs
.readFileSync(
path.join(__dirname, '..', 'spec', 'fixtures', 'sample.js'),
'utf8'
)
.replace(/\n/g, '');
const TEXT = REPEATED_TEXT.repeat(
Math.ceil((SIZES_IN_KB[SIZES_IN_KB.length - 1] * 1024) / REPEATED_TEXT.length)
);
module.exports = async ({test}) => {
const data = []
module.exports = async ({ test }) => {
const data = [];
const workspaceElement = atom.workspace.getElement()
document.body.appendChild(workspaceElement)
const workspaceElement = atom.workspace.getElement();
document.body.appendChild(workspaceElement);
atom.packages.loadPackages()
await atom.packages.activate()
atom.packages.loadPackages();
await atom.packages.activate();
console.log(atom.getLoadSettings().resourcePath);
for (let pane of atom.workspace.getPanes()) {
pane.destroy()
pane.destroy();
}
for (const sizeInKB of SIZES_IN_KB) {
const text = TEXT.slice(0, sizeInKB * 1024)
console.log(text.length / 1024)
const text = TEXT.slice(0, sizeInKB * 1024);
console.log(text.length / 1024);
let t0 = window.performance.now()
const buffer = new TextBuffer({text})
const editor = new TextEditor({buffer, autoHeight: false, largeFileMode: true})
atom.grammars.assignLanguageMode(buffer, 'source.js')
atom.workspace.getActivePane().activateItem(editor)
let t1 = window.performance.now()
let t0 = window.performance.now();
const buffer = new TextBuffer({ text });
const editor = new TextEditor({
buffer,
autoHeight: false,
largeFileMode: true
});
atom.grammars.assignLanguageMode(buffer, 'source.js');
atom.workspace.getActivePane().activateItem(editor);
let t1 = window.performance.now();
data.push({
name: 'Opening a large single-line file',
x: sizeInKB,
duration: t1 - t0
})
});
const tickDurations = []
const tickDurations = [];
for (let i = 0; i < 20; i++) {
await timeout(50)
t0 = window.performance.now()
await timeout(0)
t1 = window.performance.now()
tickDurations[i] = t1 - t0
await timeout(50);
t0 = window.performance.now();
await timeout(0);
t1 = window.performance.now();
tickDurations[i] = t1 - t0;
}
data.push({
name: 'Max time event loop was blocked after opening a large single-line file',
name:
'Max time event loop was blocked after opening a large single-line file',
x: sizeInKB,
duration: Math.max(...tickDurations)
})
});
t0 = window.performance.now()
editor.setCursorScreenPosition(editor.element.screenPositionForPixelPosition({
t0 = window.performance.now();
editor.setCursorScreenPosition(
editor.element.screenPositionForPixelPosition({
top: 100,
left: 30
}))
t1 = window.performance.now()
})
);
t1 = window.performance.now();
data.push({
name: 'Clicking the editor after opening a large single-line file',
x: sizeInKB,
duration: t1 - t0
})
});
t0 = window.performance.now()
editor.element.setScrollTop(editor.element.getScrollTop() + 100)
t1 = window.performance.now()
t0 = window.performance.now();
editor.element.setScrollTop(editor.element.getScrollTop() + 100);
t1 = window.performance.now();
data.push({
name: 'Scrolling down after opening a large single-line file',
x: sizeInKB,
duration: t1 - t0
})
});
editor.destroy()
buffer.destroy()
await timeout(10000)
editor.destroy();
buffer.destroy();
await timeout(10000);
}
workspaceElement.remove()
workspaceElement.remove();
return data
}
return data;
};
function timeout (duration) {
return new Promise((resolve) => setTimeout(resolve, duration))
function timeout(duration) {
return new Promise(resolve => setTimeout(resolve, duration));
}

View File

@ -1,12 +1,12 @@
const TextBuffer = require('text-buffer')
const {Point, Range} = TextBuffer
const {File, Directory} = require('pathwatcher')
const {Emitter, Disposable, CompositeDisposable} = require('event-kit')
const BufferedNodeProcess = require('../src/buffered-node-process')
const BufferedProcess = require('../src/buffered-process')
const GitRepository = require('../src/git-repository')
const Notification = require('../src/notification')
const {watchPath} = require('../src/path-watcher')
const TextBuffer = require('text-buffer');
const { Point, Range } = TextBuffer;
const { File, Directory } = require('pathwatcher');
const { Emitter, Disposable, CompositeDisposable } = require('event-kit');
const BufferedNodeProcess = require('../src/buffered-node-process');
const BufferedProcess = require('../src/buffered-process');
const GitRepository = require('../src/git-repository');
const Notification = require('../src/notification');
const { watchPath } = require('../src/path-watcher');
const atomExport = {
BufferedNodeProcess,
@ -22,23 +22,23 @@ const atomExport = {
Disposable,
CompositeDisposable,
watchPath
}
};
// Shell integration is required by both Squirrel and Settings-View
if (process.platform === 'win32') {
Object.defineProperty(atomExport, 'WinShell', {
enumerable: true,
get () {
return require('../src/main-process/win-shell')
get() {
return require('../src/main-process/win-shell');
}
})
});
}
// The following classes can't be used from a Task handler and should therefore
// only be exported when not running as a child node process
if (process.type === 'renderer') {
atomExport.Task = require('../src/task')
atomExport.TextEditor = require('../src/text-editor')
atomExport.Task = require('../src/task');
atomExport.TextEditor = require('../src/text-editor');
}
module.exports = atomExport
module.exports = atomExport;

View File

@ -1,7 +1,9 @@
module.exports = require('electron').clipboard
module.exports = require('electron').clipboard;
const Grim = require('grim')
Grim.deprecate('Use `require("electron").clipboard` instead of `require("clipboard")`')
const Grim = require('grim');
Grim.deprecate(
'Use `require("electron").clipboard` instead of `require("clipboard")`'
);
// Ensure each package that requires this shim causes a deprecation warning
delete require.cache[__filename]
delete require.cache[__filename];

View File

@ -1,7 +1,9 @@
module.exports = require('electron').ipcRenderer
module.exports = require('electron').ipcRenderer;
const Grim = require('grim')
Grim.deprecate('Use `require("electron").ipcRenderer` instead of `require("ipc")`')
const Grim = require('grim');
Grim.deprecate(
'Use `require("electron").ipcRenderer` instead of `require("ipc")`'
);
// Ensure each package that requires this shim causes a deprecation warning
delete require.cache[__filename]
delete require.cache[__filename];

View File

@ -1,7 +1,9 @@
module.exports = require('electron').remote
module.exports = require('electron').remote;
const Grim = require('grim')
Grim.deprecate('Use `require("electron").remote` instead of `require("remote")`')
const Grim = require('grim');
Grim.deprecate(
'Use `require("electron").remote` instead of `require("remote")`'
);
// Ensure each package that requires this shim causes a deprecation warning
delete require.cache[__filename]
delete require.cache[__filename];

View File

@ -1,7 +1,7 @@
module.exports = require('electron').shell
module.exports = require('electron').shell;
const Grim = require('grim')
Grim.deprecate('Use `require("electron").shell` instead of `require("shell")`')
const Grim = require('grim');
Grim.deprecate('Use `require("electron").shell` instead of `require("shell")`');
// Ensure each package that requires this shim causes a deprecation warning
delete require.cache[__filename]
delete require.cache[__filename];

View File

@ -1,7 +1,9 @@
module.exports = require('electron').webFrame
module.exports = require('electron').webFrame;
const Grim = require('grim')
Grim.deprecate('Use `require("electron").webFrame` instead of `require("web-frame")`')
const Grim = require('grim');
Grim.deprecate(
'Use `require("electron").webFrame` instead of `require("web-frame")`'
);
// Ensure each package that requires this shim causes a deprecation warning
delete require.cache[__filename]
delete require.cache[__filename];

View File

@ -1,67 +1,67 @@
const { CompositeDisposable, Emitter } = require('atom')
const AboutView = require('./components/about-view')
const { CompositeDisposable, Emitter } = require('atom');
const AboutView = require('./components/about-view');
// Deferred requires
let shell
let shell;
module.exports = class About {
constructor (initialState) {
this.subscriptions = new CompositeDisposable()
this.emitter = new Emitter()
constructor(initialState) {
this.subscriptions = new CompositeDisposable();
this.emitter = new Emitter();
this.state = initialState
this.state = initialState;
this.views = {
aboutView: null
}
};
this.subscriptions.add(
atom.workspace.addOpener(uriToOpen => {
if (uriToOpen === this.state.uri) {
return this.deserialize()
return this.deserialize();
}
})
)
);
this.subscriptions.add(
atom.commands.add('atom-workspace', 'about:view-release-notes', () => {
shell = shell || require('electron').shell
shell = shell || require('electron').shell;
shell.openExternal(
this.state.updateManager.getReleaseNotesURLForCurrentVersion()
)
);
})
)
);
}
destroy () {
if (this.views.aboutView) this.views.aboutView.destroy()
this.views.aboutView = null
destroy() {
if (this.views.aboutView) this.views.aboutView.destroy();
this.views.aboutView = null;
if (this.state.updateManager) this.state.updateManager.dispose()
this.setState({ updateManager: null })
if (this.state.updateManager) this.state.updateManager.dispose();
this.setState({ updateManager: null });
this.subscriptions.dispose()
this.subscriptions.dispose();
}
setState (newState) {
setState(newState) {
if (newState && typeof newState === 'object') {
let { state } = this
this.state = Object.assign({}, state, newState)
let { state } = this;
this.state = Object.assign({}, state, newState);
this.didChange()
this.didChange();
}
}
didChange () {
this.emitter.emit('did-change')
didChange() {
this.emitter.emit('did-change');
}
onDidChange (callback) {
this.emitter.on('did-change', callback)
onDidChange(callback) {
this.emitter.on('did-change', callback);
}
deserialize (state) {
deserialize(state) {
if (!this.views.aboutView) {
this.setState(state)
this.setState(state);
this.views.aboutView = new AboutView({
uri: this.state.uri,
@ -71,14 +71,14 @@ module.exports = class About {
currentChromeVersion: this.state.currentChromeVersion,
currentNodeVersion: this.state.currentNodeVersion,
availableVersion: this.state.updateManager.getAvailableVersion()
})
this.handleStateChanges()
});
this.handleStateChanges();
}
return this.views.aboutView
return this.views.aboutView;
}
handleStateChanges () {
handleStateChanges() {
this.onDidChange(() => {
if (this.views.aboutView) {
this.views.aboutView.update({
@ -88,12 +88,12 @@ module.exports = class About {
currentChromeVersion: this.state.currentChromeVersion,
currentNodeVersion: this.state.currentNodeVersion,
availableVersion: this.state.updateManager.getAvailableVersion()
})
});
}
})
});
this.state.updateManager.onDidChange(() => {
this.didChange()
})
this.didChange();
});
}
}
};

View File

@ -1,38 +1,38 @@
const { CompositeDisposable } = require('atom')
const etch = require('etch')
const EtchComponent = require('../etch-component')
const { CompositeDisposable } = require('atom');
const etch = require('etch');
const EtchComponent = require('../etch-component');
const $ = etch.dom
const $ = etch.dom;
module.exports = class AboutStatusBar extends EtchComponent {
constructor () {
super()
this.subscriptions = new CompositeDisposable()
constructor() {
super();
this.subscriptions = new CompositeDisposable();
this.subscriptions.add(
atom.tooltips.add(this.element, {
title:
'An update will be installed the next time Atom is relaunched.<br/><br/>Click the squirrel icon for more information.'
})
)
);
}
handleClick () {
atom.workspace.open('atom://about')
handleClick() {
atom.workspace.open('atom://about');
}
render () {
render() {
return $.div(
{
className: 'about-release-notes inline-block',
onclick: this.handleClick.bind(this)
},
$.span({ type: 'button', className: 'icon icon-squirrel' })
)
);
}
destroy () {
super.destroy()
this.subscriptions.dispose()
destroy() {
super.destroy();
this.subscriptions.dispose();
}
}
};

View File

@ -1,77 +1,77 @@
const { Disposable } = require('atom')
const etch = require('etch')
const shell = require('shell')
const AtomLogo = require('./atom-logo')
const EtchComponent = require('../etch-component')
const UpdateView = require('./update-view')
const { Disposable } = require('atom');
const etch = require('etch');
const shell = require('shell');
const AtomLogo = require('./atom-logo');
const EtchComponent = require('../etch-component');
const UpdateView = require('./update-view');
const $ = etch.dom
const $ = etch.dom;
module.exports = class AboutView extends EtchComponent {
handleAtomVersionClick (e) {
e.preventDefault()
atom.clipboard.write(this.props.currentAtomVersion)
handleAtomVersionClick(e) {
e.preventDefault();
atom.clipboard.write(this.props.currentAtomVersion);
}
handleElectronVersionClick (e) {
e.preventDefault()
atom.clipboard.write(this.props.currentElectronVersion)
handleElectronVersionClick(e) {
e.preventDefault();
atom.clipboard.write(this.props.currentElectronVersion);
}
handleChromeVersionClick (e) {
e.preventDefault()
atom.clipboard.write(this.props.currentChromeVersion)
handleChromeVersionClick(e) {
e.preventDefault();
atom.clipboard.write(this.props.currentChromeVersion);
}
handleNodeVersionClick (e) {
e.preventDefault()
atom.clipboard.write(this.props.currentNodeVersion)
handleNodeVersionClick(e) {
e.preventDefault();
atom.clipboard.write(this.props.currentNodeVersion);
}
handleReleaseNotesClick (e) {
e.preventDefault()
handleReleaseNotesClick(e) {
e.preventDefault();
shell.openExternal(
this.props.updateManager.getReleaseNotesURLForAvailableVersion()
)
);
}
handleLicenseClick (e) {
e.preventDefault()
handleLicenseClick(e) {
e.preventDefault();
atom.commands.dispatch(
atom.views.getView(atom.workspace),
'application:open-license'
)
);
}
handleTermsOfUseClick (e) {
e.preventDefault()
shell.openExternal('https://atom.io/terms')
handleTermsOfUseClick(e) {
e.preventDefault();
shell.openExternal('https://atom.io/terms');
}
handleHowToUpdateClick (e) {
e.preventDefault()
handleHowToUpdateClick(e) {
e.preventDefault();
shell.openExternal(
'https://flight-manual.atom.io/getting-started/sections/installing-atom/'
)
);
}
handleShowMoreClick (e) {
e.preventDefault()
var showMoreDiv = document.querySelector('.show-more')
var showMoreText = document.querySelector('.about-more-expand')
handleShowMoreClick(e) {
e.preventDefault();
var showMoreDiv = document.querySelector('.show-more');
var showMoreText = document.querySelector('.about-more-expand');
switch (showMoreText.textContent) {
case 'Show more':
showMoreDiv.classList.toggle('hidden')
showMoreText.textContent = 'Hide'
break
showMoreDiv.classList.toggle('hidden');
showMoreText.textContent = 'Hide';
break;
case 'Hide':
showMoreDiv.classList.toggle('hidden')
showMoreText.textContent = 'Show more'
break
showMoreDiv.classList.toggle('hidden');
showMoreText.textContent = 'Show more';
break;
}
}
render () {
render() {
return $.div(
{ className: 'pane-item native-key-bindings about' },
$.div(
@ -204,29 +204,29 @@ module.exports = class AboutView extends EtchComponent {
'Atom Community'
)
)
)
);
}
serialize () {
serialize() {
return {
deserializer: this.constructor.name,
uri: this.props.uri
}
};
}
onDidChangeTitle () {
return new Disposable()
onDidChangeTitle() {
return new Disposable();
}
onDidChangeModified () {
return new Disposable()
onDidChangeModified() {
return new Disposable();
}
getTitle () {
return 'About'
getTitle() {
return 'About';
}
getIconName () {
return 'info'
getIconName() {
return 'info';
}
}
};

View File

@ -1,10 +1,10 @@
const etch = require('etch')
const EtchComponent = require('../etch-component')
const etch = require('etch');
const EtchComponent = require('../etch-component');
const $ = etch.dom
const $ = etch.dom;
module.exports = class AtomLogo extends EtchComponent {
render () {
render() {
return $.svg(
{
className: 'about-logo',
@ -74,6 +74,6 @@ module.exports = class AtomLogo extends EtchComponent {
)
)
)
)
);
}
}
};

View File

@ -1,46 +1,46 @@
const etch = require('etch')
const EtchComponent = require('../etch-component')
const UpdateManager = require('../update-manager')
const etch = require('etch');
const EtchComponent = require('../etch-component');
const UpdateManager = require('../update-manager');
const $ = etch.dom
const $ = etch.dom;
module.exports = class UpdateView extends EtchComponent {
constructor (props) {
super(props)
constructor(props) {
super(props);
if (
this.props.updateManager.getAutoUpdatesEnabled() &&
this.props.updateManager.getState() === UpdateManager.State.Idle
) {
this.props.updateManager.checkForUpdate()
this.props.updateManager.checkForUpdate();
}
}
handleAutoUpdateCheckbox (e) {
atom.config.set('core.automaticallyUpdate', e.target.checked)
handleAutoUpdateCheckbox(e) {
atom.config.set('core.automaticallyUpdate', e.target.checked);
}
shouldUpdateActionButtonBeDisabled () {
let { state } = this.props.updateManager
shouldUpdateActionButtonBeDisabled() {
let { state } = this.props.updateManager;
return (
state === UpdateManager.State.CheckingForUpdate ||
state === UpdateManager.State.DownloadingUpdate
)
);
}
executeUpdateAction () {
executeUpdateAction() {
if (
this.props.updateManager.state ===
UpdateManager.State.UpdateAvailableToInstall
) {
this.props.updateManager.restartAndInstallUpdate()
this.props.updateManager.restartAndInstallUpdate();
} else {
this.props.updateManager.checkForUpdate()
this.props.updateManager.checkForUpdate();
}
}
renderUpdateStatus () {
let updateStatus = ''
renderUpdateStatus() {
let updateStatus = '';
switch (this.props.updateManager.state) {
case UpdateManager.State.Idle:
@ -52,8 +52,8 @@ module.exports = class UpdateView extends EtchComponent {
this.props.updateManager.getAutoUpdatesEnabled()
? 'Atom will check for updates automatically'
: 'Automatic updates are disabled please check manually'
)
break
);
break;
case UpdateManager.State.CheckingForUpdate:
updateStatus = $.div(
{ className: 'about-updates-item app-checking-for-updates' },
@ -61,15 +61,15 @@ module.exports = class UpdateView extends EtchComponent {
{ className: 'about-updates-label icon icon-search' },
'Checking for updates...'
)
)
break
);
break;
case UpdateManager.State.DownloadingUpdate:
updateStatus = $.div(
{ className: 'about-updates-item app-downloading-update' },
$.span({ className: 'loading loading-spinner-tiny inline-block' }),
$.span({ className: 'about-updates-label' }, 'Downloading update')
)
break
);
break;
case UpdateManager.State.UpdateAvailableToInstall:
updateStatus = $.div(
{ className: 'about-updates-item app-update-available-to-install' },
@ -88,8 +88,8 @@ module.exports = class UpdateView extends EtchComponent {
},
'Release Notes'
)
)
break
);
break;
case UpdateManager.State.UpToDate:
updateStatus = $.div(
{ className: 'about-updates-item app-up-to-date' },
@ -98,8 +98,8 @@ module.exports = class UpdateView extends EtchComponent {
{ className: 'about-updates-label is-strong' },
'Atom is up to date!'
)
)
break
);
break;
case UpdateManager.State.Unsupported:
updateStatus = $.div(
{ className: 'about-updates-item app-unsupported' },
@ -114,8 +114,8 @@ module.exports = class UpdateView extends EtchComponent {
},
'How to update'
)
)
break
);
break;
case UpdateManager.State.Error:
updateStatus = $.div(
{ className: 'about-updates-item app-update-error' },
@ -124,14 +124,14 @@ module.exports = class UpdateView extends EtchComponent {
{ className: 'about-updates-label app-error-message is-strong' },
this.props.updateManager.getErrorMessage()
)
)
break
);
break;
}
return updateStatus
return updateStatus;
}
render () {
render() {
return $.div(
{ className: 'about-updates group-start' },
$.div(
@ -176,6 +176,6 @@ module.exports = class UpdateView extends EtchComponent {
$.span({}, 'Automatically download updates')
)
)
)
);
}
}
};

View File

@ -1,15 +1,15 @@
const etch = require('etch')
const etch = require('etch');
/*
Public: Abstract class for handling the initialization
boilerplate of an Etch component.
*/
module.exports = class EtchComponent {
constructor (props) {
this.props = props
constructor(props) {
this.props = props;
etch.initialize(this)
EtchComponent.setScheduler(atom.views)
etch.initialize(this);
EtchComponent.setScheduler(atom.views);
}
/*
@ -17,8 +17,8 @@ module.exports = class EtchComponent {
Returns a {Scheduler}
*/
static getScheduler () {
return etch.getScheduler()
static getScheduler() {
return etch.getScheduler();
}
/*
@ -26,8 +26,8 @@ module.exports = class EtchComponent {
* `scheduler` {Scheduler}
*/
static setScheduler (scheduler) {
etch.setScheduler(scheduler)
static setScheduler(scheduler) {
etch.setScheduler(scheduler);
}
/*
@ -37,20 +37,20 @@ module.exports = class EtchComponent {
* `props` an {Object} representing the properties you want to update
*/
update (props) {
let oldProps = this.props
this.props = Object.assign({}, oldProps, props)
return etch.update(this)
update(props) {
let oldProps = this.props;
this.props = Object.assign({}, oldProps, props);
return etch.update(this);
}
/*
Public: Destroys the component, removing it from the DOM.
*/
destroy () {
etch.destroy(this)
destroy() {
etch.destroy(this);
}
render () {
throw new Error('Etch components must implement a `render` method')
render() {
throw new Error('Etch components must implement a `render` method');
}
}
};

View File

@ -1,26 +1,26 @@
const { CompositeDisposable } = require('atom')
const semver = require('semver')
const UpdateManager = require('./update-manager')
const About = require('./about')
const StatusBarView = require('./components/about-status-bar')
let updateManager
const { CompositeDisposable } = require('atom');
const semver = require('semver');
const UpdateManager = require('./update-manager');
const About = require('./about');
const StatusBarView = require('./components/about-status-bar');
let updateManager;
// The local storage key for the available update version.
const AvailableUpdateVersion = 'about:version-available'
const AboutURI = 'atom://about'
const AvailableUpdateVersion = 'about:version-available';
const AboutURI = 'atom://about';
module.exports = {
activate () {
this.subscriptions = new CompositeDisposable()
activate() {
this.subscriptions = new CompositeDisposable();
this.createModel()
this.createModel();
let availableVersion = window.localStorage.getItem(AvailableUpdateVersion)
let availableVersion = window.localStorage.getItem(AvailableUpdateVersion);
if (
atom.getReleaseChannel() === 'dev' ||
(availableVersion && semver.lte(availableVersion, atom.getVersion()))
) {
this.clearUpdateState()
this.clearUpdateState();
}
this.subscriptions.add(
@ -32,48 +32,48 @@ module.exports = {
window.localStorage.setItem(
AvailableUpdateVersion,
updateManager.getAvailableVersion()
)
this.showStatusBarIfNeeded()
);
this.showStatusBarIfNeeded();
}
})
)
);
this.subscriptions.add(
atom.commands.add('atom-workspace', 'about:clear-update-state', () => {
this.clearUpdateState()
this.clearUpdateState();
})
)
);
},
deactivate () {
this.model.destroy()
if (this.statusBarTile) this.statusBarTile.destroy()
deactivate() {
this.model.destroy();
if (this.statusBarTile) this.statusBarTile.destroy();
if (updateManager) {
updateManager.dispose()
updateManager = undefined
updateManager.dispose();
updateManager = undefined;
}
},
clearUpdateState () {
window.localStorage.removeItem(AvailableUpdateVersion)
clearUpdateState() {
window.localStorage.removeItem(AvailableUpdateVersion);
},
consumeStatusBar (statusBar) {
this.statusBar = statusBar
this.showStatusBarIfNeeded()
consumeStatusBar(statusBar) {
this.statusBar = statusBar;
this.showStatusBarIfNeeded();
},
deserializeAboutView (state) {
deserializeAboutView(state) {
if (!this.model) {
this.createModel()
this.createModel();
}
return this.model.deserialize(state)
return this.model.deserialize(state);
},
createModel () {
updateManager = updateManager || new UpdateManager()
createModel() {
updateManager = updateManager || new UpdateManager();
this.model = new About({
uri: AboutURI,
@ -82,28 +82,28 @@ module.exports = {
currentChromeVersion: process.versions.chrome,
currentNodeVersion: process.version,
updateManager: updateManager
})
});
},
isUpdateAvailable () {
let availableVersion = window.localStorage.getItem(AvailableUpdateVersion)
return availableVersion && semver.gt(availableVersion, atom.getVersion())
isUpdateAvailable() {
let availableVersion = window.localStorage.getItem(AvailableUpdateVersion);
return availableVersion && semver.gt(availableVersion, atom.getVersion());
},
showStatusBarIfNeeded () {
showStatusBarIfNeeded() {
if (this.isUpdateAvailable() && this.statusBar) {
let statusBarView = new StatusBarView()
let statusBarView = new StatusBarView();
if (this.statusBarTile) {
this.statusBarTile.destroy()
this.statusBarTile.destroy();
}
this.statusBarTile = this.statusBar.addRightTile({
item: statusBarView,
priority: -100
})
});
return this.statusBarTile
return this.statusBarTile;
}
}
}
};

View File

@ -1,46 +1,46 @@
const { Emitter, CompositeDisposable } = require('atom')
const { Emitter, CompositeDisposable } = require('atom');
const Unsupported = 'unsupported'
const Idle = 'idle'
const CheckingForUpdate = 'checking'
const DownloadingUpdate = 'downloading'
const UpdateAvailableToInstall = 'update-available'
const UpToDate = 'no-update-available'
const ErrorState = 'error'
const Unsupported = 'unsupported';
const Idle = 'idle';
const CheckingForUpdate = 'checking';
const DownloadingUpdate = 'downloading';
const UpdateAvailableToInstall = 'update-available';
const UpToDate = 'no-update-available';
const ErrorState = 'error';
let UpdateManager = class UpdateManager {
constructor () {
this.emitter = new Emitter()
this.currentVersion = atom.getVersion()
this.availableVersion = atom.getVersion()
this.resetState()
this.listenForAtomEvents()
constructor() {
this.emitter = new Emitter();
this.currentVersion = atom.getVersion();
this.availableVersion = atom.getVersion();
this.resetState();
this.listenForAtomEvents();
}
listenForAtomEvents () {
this.subscriptions = new CompositeDisposable()
listenForAtomEvents() {
this.subscriptions = new CompositeDisposable();
this.subscriptions.add(
atom.autoUpdater.onDidBeginCheckingForUpdate(() => {
this.setState(CheckingForUpdate)
this.setState(CheckingForUpdate);
}),
atom.autoUpdater.onDidBeginDownloadingUpdate(() => {
this.setState(DownloadingUpdate)
this.setState(DownloadingUpdate);
}),
atom.autoUpdater.onDidCompleteDownloadingUpdate(({ releaseVersion }) => {
this.setAvailableVersion(releaseVersion)
this.setAvailableVersion(releaseVersion);
}),
atom.autoUpdater.onUpdateNotAvailable(() => {
this.setState(UpToDate)
this.setState(UpToDate);
}),
atom.autoUpdater.onUpdateError(() => {
this.setState(ErrorState)
this.setState(ErrorState);
}),
atom.config.observe('core.automaticallyUpdate', value => {
this.autoUpdatesEnabled = value
this.emitDidChange()
this.autoUpdatesEnabled = value;
this.emitDidChange();
})
)
);
// TODO: When https://github.com/atom/electron/issues/4587 is closed we can add this support.
// atom.autoUpdater.onUpdateAvailable =>
@ -48,95 +48,95 @@ let UpdateManager = class UpdateManager {
// @updateAvailable.addClass('is-shown')
}
dispose () {
this.subscriptions.dispose()
dispose() {
this.subscriptions.dispose();
}
onDidChange (callback) {
return this.emitter.on('did-change', callback)
onDidChange(callback) {
return this.emitter.on('did-change', callback);
}
emitDidChange () {
this.emitter.emit('did-change')
emitDidChange() {
this.emitter.emit('did-change');
}
getAutoUpdatesEnabled () {
getAutoUpdatesEnabled() {
return (
this.autoUpdatesEnabled && this.state !== UpdateManager.State.Unsupported
)
);
}
setAutoUpdatesEnabled (enabled) {
return atom.config.set('core.automaticallyUpdate', enabled)
setAutoUpdatesEnabled(enabled) {
return atom.config.set('core.automaticallyUpdate', enabled);
}
getErrorMessage () {
return atom.autoUpdater.getErrorMessage()
getErrorMessage() {
return atom.autoUpdater.getErrorMessage();
}
getState () {
return this.state
getState() {
return this.state;
}
setState (state) {
this.state = state
this.emitDidChange()
setState(state) {
this.state = state;
this.emitDidChange();
}
resetState () {
resetState() {
this.state = atom.autoUpdater.platformSupportsUpdates()
? atom.autoUpdater.getState()
: Unsupported
this.emitDidChange()
: Unsupported;
this.emitDidChange();
}
getAvailableVersion () {
return this.availableVersion
getAvailableVersion() {
return this.availableVersion;
}
setAvailableVersion (version) {
this.availableVersion = version
setAvailableVersion(version) {
this.availableVersion = version;
if (this.availableVersion !== this.currentVersion) {
this.state = UpdateAvailableToInstall
this.state = UpdateAvailableToInstall;
} else {
this.state = UpToDate
this.state = UpToDate;
}
this.emitDidChange()
this.emitDidChange();
}
checkForUpdate () {
atom.autoUpdater.checkForUpdate()
checkForUpdate() {
atom.autoUpdater.checkForUpdate();
}
restartAndInstallUpdate () {
atom.autoUpdater.restartAndInstallUpdate()
restartAndInstallUpdate() {
atom.autoUpdater.restartAndInstallUpdate();
}
getReleaseNotesURLForCurrentVersion () {
return this.getReleaseNotesURLForVersion(this.currentVersion)
getReleaseNotesURLForCurrentVersion() {
return this.getReleaseNotesURLForVersion(this.currentVersion);
}
getReleaseNotesURLForAvailableVersion () {
return this.getReleaseNotesURLForVersion(this.availableVersion)
getReleaseNotesURLForAvailableVersion() {
return this.getReleaseNotesURLForVersion(this.availableVersion);
}
getReleaseNotesURLForVersion (appVersion) {
getReleaseNotesURLForVersion(appVersion) {
// Dev versions will not have a releases page
if (appVersion.indexOf('dev') > -1) {
return 'https://atom.io/releases'
return 'https://atom.io/releases';
}
if (!appVersion.startsWith('v')) {
appVersion = `v${appVersion}`
appVersion = `v${appVersion}`;
}
const releaseRepo =
appVersion.indexOf('nightly') > -1 ? 'atom-nightly-releases' : 'atom'
return `https://github.com/atom/${releaseRepo}/releases/tag/${appVersion}`
appVersion.indexOf('nightly') > -1 ? 'atom-nightly-releases' : 'atom';
return `https://github.com/atom/${releaseRepo}/releases/tag/${appVersion}`;
}
}
};
UpdateManager.State = {
Unsupported: Unsupported,
@ -146,6 +146,6 @@ UpdateManager.State = {
UpdateAvailableToInstall: UpdateAvailableToInstall,
UpToDate: UpToDate,
Error: ErrorState
}
};
module.exports = UpdateManager
module.exports = UpdateManager;

View File

@ -1,28 +1,28 @@
describe('About', () => {
let workspaceElement
let workspaceElement;
beforeEach(async () => {
let storage = {}
let storage = {};
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value
})
storage[key] = value;
});
spyOn(window.localStorage, 'getItem').andCallFake(key => {
return storage[key]
})
return storage[key];
});
workspaceElement = atom.views.getView(atom.workspace)
await atom.packages.activatePackage('about')
})
workspaceElement = atom.views.getView(atom.workspace);
await atom.packages.activatePackage('about');
});
it('deserializes correctly', () => {
let deserializedAboutView = atom.deserializers.deserialize({
deserializer: 'AboutView',
uri: 'atom://about'
})
});
expect(deserializedAboutView).toBeTruthy()
})
expect(deserializedAboutView).toBeTruthy();
});
describe('when the about:about-atom command is triggered', () => {
it('shows the About Atom view', async () => {
@ -30,70 +30,70 @@ describe('About', () => {
// `toBeVisible()` matchers to work. Anything testing visibility or focus
// requires that the workspaceElement is on the DOM. Tests that attach the
// workspaceElement to the DOM are generally slower than those off DOM.
jasmine.attachToDOM(workspaceElement)
jasmine.attachToDOM(workspaceElement);
expect(workspaceElement.querySelector('.about')).not.toExist()
await atom.workspace.open('atom://about')
expect(workspaceElement.querySelector('.about')).not.toExist();
await atom.workspace.open('atom://about');
let aboutElement = workspaceElement.querySelector('.about')
expect(aboutElement).toBeVisible()
})
})
let aboutElement = workspaceElement.querySelector('.about');
expect(aboutElement).toBeVisible();
});
});
describe('when the Atom version number is clicked', () => {
it('copies the version number to the clipboard', async () => {
await atom.workspace.open('atom://about')
await atom.workspace.open('atom://about');
let aboutElement = workspaceElement.querySelector('.about')
let versionContainer = aboutElement.querySelector('.atom')
versionContainer.click()
expect(atom.clipboard.read()).toBe(atom.getVersion())
})
})
let aboutElement = workspaceElement.querySelector('.about');
let versionContainer = aboutElement.querySelector('.atom');
versionContainer.click();
expect(atom.clipboard.read()).toBe(atom.getVersion());
});
});
describe('when the show more link is clicked', () => {
it('expands to show additional version numbers', async () => {
await atom.workspace.open('atom://about')
jasmine.attachToDOM(workspaceElement)
await atom.workspace.open('atom://about');
jasmine.attachToDOM(workspaceElement);
let aboutElement = workspaceElement.querySelector('.about')
let showMoreElement = aboutElement.querySelector('.show-more-expand')
let moreInfoElement = workspaceElement.querySelector('.show-more')
showMoreElement.click()
expect(moreInfoElement).toBeVisible()
})
})
let aboutElement = workspaceElement.querySelector('.about');
let showMoreElement = aboutElement.querySelector('.show-more-expand');
let moreInfoElement = workspaceElement.querySelector('.show-more');
showMoreElement.click();
expect(moreInfoElement).toBeVisible();
});
});
describe('when the Electron version number is clicked', () => {
it('copies the version number to the clipboard', async () => {
await atom.workspace.open('atom://about')
await atom.workspace.open('atom://about');
let aboutElement = workspaceElement.querySelector('.about')
let versionContainer = aboutElement.querySelector('.electron')
versionContainer.click()
expect(atom.clipboard.read()).toBe(process.versions.electron)
})
})
let aboutElement = workspaceElement.querySelector('.about');
let versionContainer = aboutElement.querySelector('.electron');
versionContainer.click();
expect(atom.clipboard.read()).toBe(process.versions.electron);
});
});
describe('when the Chrome version number is clicked', () => {
it('copies the version number to the clipboard', async () => {
await atom.workspace.open('atom://about')
await atom.workspace.open('atom://about');
let aboutElement = workspaceElement.querySelector('.about')
let versionContainer = aboutElement.querySelector('.chrome')
versionContainer.click()
expect(atom.clipboard.read()).toBe(process.versions.chrome)
})
})
let aboutElement = workspaceElement.querySelector('.about');
let versionContainer = aboutElement.querySelector('.chrome');
versionContainer.click();
expect(atom.clipboard.read()).toBe(process.versions.chrome);
});
});
describe('when the Node version number is clicked', () => {
it('copies the version number to the clipboard', async () => {
await atom.workspace.open('atom://about')
await atom.workspace.open('atom://about');
let aboutElement = workspaceElement.querySelector('.about')
let versionContainer = aboutElement.querySelector('.node')
versionContainer.click()
expect(atom.clipboard.read()).toBe(process.version)
})
})
})
let aboutElement = workspaceElement.querySelector('.about');
let versionContainer = aboutElement.querySelector('.node');
versionContainer.click();
expect(atom.clipboard.read()).toBe(process.version);
});
});
});

View File

@ -1,179 +1,183 @@
const { conditionPromise } = require('./helpers/async-spec-helpers')
const MockUpdater = require('./mocks/updater')
const { conditionPromise } = require('./helpers/async-spec-helpers');
const MockUpdater = require('./mocks/updater');
describe('the status bar', () => {
let atomVersion
let workspaceElement
let atomVersion;
let workspaceElement;
beforeEach(async () => {
let storage = {}
let storage = {};
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value
})
storage[key] = value;
});
spyOn(window.localStorage, 'getItem').andCallFake(key => {
return storage[key]
})
return storage[key];
});
spyOn(atom, 'getVersion').andCallFake(() => {
return atomVersion
})
return atomVersion;
});
workspaceElement = atom.views.getView(atom.workspace)
workspaceElement = atom.views.getView(atom.workspace);
await atom.packages.activatePackage('status-bar')
await atom.workspace.open('sample.js')
})
await atom.packages.activatePackage('status-bar');
await atom.workspace.open('sample.js');
});
afterEach(async () => {
await atom.packages.deactivatePackage('about')
await atom.packages.deactivatePackage('status-bar')
})
await atom.packages.deactivatePackage('about');
await atom.packages.deactivatePackage('status-bar');
});
describe('on a stable version', function () {
describe('on a stable version', function() {
beforeEach(async () => {
atomVersion = '1.2.3'
atomVersion = '1.2.3';
await atom.packages.activatePackage('about')
})
await atom.packages.activatePackage('about');
});
describe('with no update', () => {
it('does not show the view', () => {
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
describe('with an update', () => {
it('shows the view when the update finishes downloading', () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
expect(workspaceElement).toContain('.about-release-notes')
})
MockUpdater.finishDownloadingUpdate('42.0.0');
expect(workspaceElement).toContain('.about-release-notes');
});
describe('clicking on the status', () => {
it('opens the about page', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
workspaceElement.querySelector('.about-release-notes').click()
await conditionPromise(() => workspaceElement.querySelector('.about'))
expect(workspaceElement.querySelector('.about')).toExist()
})
})
MockUpdater.finishDownloadingUpdate('42.0.0');
workspaceElement.querySelector('.about-release-notes').click();
await conditionPromise(() =>
workspaceElement.querySelector('.about')
);
expect(workspaceElement.querySelector('.about')).toExist();
});
});
it('continues to show the squirrel until Atom is updated to the new version', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
expect(workspaceElement).toContain('.about-release-notes')
MockUpdater.finishDownloadingUpdate('42.0.0');
expect(workspaceElement).toContain('.about-release-notes');
await atom.packages.deactivatePackage('about')
expect(workspaceElement).not.toContain('.about-release-notes')
await atom.packages.deactivatePackage('about');
expect(workspaceElement).not.toContain('.about-release-notes');
await atom.packages.activatePackage('about')
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).toContain('.about-release-notes')
await atom.packages.activatePackage('about');
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).toContain('.about-release-notes');
await atom.packages.deactivatePackage('about')
expect(workspaceElement).not.toContain('.about-release-notes')
await atom.packages.deactivatePackage('about');
expect(workspaceElement).not.toContain('.about-release-notes');
atomVersion = '42.0.0'
await atom.packages.activatePackage('about')
atomVersion = '42.0.0';
await atom.packages.activatePackage('about');
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes')
})
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes');
});
it('does not show the view if Atom is updated to a newer version than notified', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
MockUpdater.finishDownloadingUpdate('42.0.0');
await atom.packages.deactivatePackage('about')
await atom.packages.deactivatePackage('about');
atomVersion = '43.0.0'
await atom.packages.activatePackage('about')
atomVersion = '43.0.0';
await atom.packages.activatePackage('about');
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
})
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
});
describe('on a beta version', function () {
describe('on a beta version', function() {
beforeEach(async () => {
atomVersion = '1.2.3-beta4'
atomVersion = '1.2.3-beta4';
await atom.packages.activatePackage('about')
})
await atom.packages.activatePackage('about');
});
describe('with no update', () => {
it('does not show the view', () => {
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
describe('with an update', () => {
it('shows the view when the update finishes downloading', () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
expect(workspaceElement).toContain('.about-release-notes')
})
MockUpdater.finishDownloadingUpdate('42.0.0');
expect(workspaceElement).toContain('.about-release-notes');
});
describe('clicking on the status', () => {
it('opens the about page', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
workspaceElement.querySelector('.about-release-notes').click()
await conditionPromise(() => workspaceElement.querySelector('.about'))
expect(workspaceElement.querySelector('.about')).toExist()
})
})
MockUpdater.finishDownloadingUpdate('42.0.0');
workspaceElement.querySelector('.about-release-notes').click();
await conditionPromise(() =>
workspaceElement.querySelector('.about')
);
expect(workspaceElement.querySelector('.about')).toExist();
});
});
it('continues to show the squirrel until Atom is updated to the new version', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
expect(workspaceElement).toContain('.about-release-notes')
MockUpdater.finishDownloadingUpdate('42.0.0');
expect(workspaceElement).toContain('.about-release-notes');
await atom.packages.deactivatePackage('about')
expect(workspaceElement).not.toContain('.about-release-notes')
await atom.packages.deactivatePackage('about');
expect(workspaceElement).not.toContain('.about-release-notes');
await atom.packages.activatePackage('about')
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).toContain('.about-release-notes')
await atom.packages.activatePackage('about');
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).toContain('.about-release-notes');
await atom.packages.deactivatePackage('about')
expect(workspaceElement).not.toContain('.about-release-notes')
await atom.packages.deactivatePackage('about');
expect(workspaceElement).not.toContain('.about-release-notes');
atomVersion = '42.0.0'
await atom.packages.activatePackage('about')
atomVersion = '42.0.0';
await atom.packages.activatePackage('about');
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes')
})
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes');
});
it('does not show the view if Atom is updated to a newer version than notified', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
MockUpdater.finishDownloadingUpdate('42.0.0');
await atom.packages.deactivatePackage('about')
await atom.packages.deactivatePackage('about');
atomVersion = '43.0.0'
await atom.packages.activatePackage('about')
atomVersion = '43.0.0';
await atom.packages.activatePackage('about');
await Promise.resolve() // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
})
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
});
describe('on a development version', function () {
describe('on a development version', function() {
beforeEach(async () => {
atomVersion = '1.2.3-dev-0123abcd'
atomVersion = '1.2.3-dev-0123abcd';
await atom.packages.activatePackage('about')
})
await atom.packages.activatePackage('about');
});
describe('with no update', () => {
it('does not show the view', () => {
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
describe('with a previously downloaded update', () => {
it('does not show the view', () => {
window.localStorage.setItem('about:version-available', '42.0.0')
window.localStorage.setItem('about:version-available', '42.0.0');
expect(workspaceElement).not.toContain('.about-release-notes')
})
})
})
})
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
});
});

View File

@ -1,26 +1,26 @@
/** @babel */
const { now } = Date
const { setTimeout } = global
const { now } = Date;
const { setTimeout } = global;
export async function conditionPromise (condition) {
const startTime = now()
export async function conditionPromise(condition) {
const startTime = now();
while (true) {
await timeoutPromise(100)
await timeoutPromise(100);
if (await condition()) {
return
return;
}
if (now() - startTime > 5000) {
throw new Error('Timed out waiting on condition')
throw new Error('Timed out waiting on condition');
}
}
}
export function timeoutPromise (timeout) {
return new Promise(function (resolve) {
setTimeout(resolve, timeout)
})
export function timeoutPromise(timeout) {
return new Promise(function(resolve) {
setTimeout(resolve, timeout);
});
}

View File

@ -1,23 +1,23 @@
module.exports = {
updateError () {
atom.autoUpdater.emitter.emit('update-error')
updateError() {
atom.autoUpdater.emitter.emit('update-error');
},
checkForUpdate () {
atom.autoUpdater.emitter.emit('did-begin-checking-for-update')
checkForUpdate() {
atom.autoUpdater.emitter.emit('did-begin-checking-for-update');
},
updateNotAvailable () {
atom.autoUpdater.emitter.emit('update-not-available')
updateNotAvailable() {
atom.autoUpdater.emitter.emit('update-not-available');
},
downloadUpdate () {
atom.autoUpdater.emitter.emit('did-begin-downloading-update')
downloadUpdate() {
atom.autoUpdater.emitter.emit('did-begin-downloading-update');
},
finishDownloadingUpdate (releaseVersion) {
finishDownloadingUpdate(releaseVersion) {
atom.autoUpdater.emitter.emit('did-complete-downloading-update', {
releaseVersion
})
});
}
}
};

View File

@ -1,32 +1,32 @@
const UpdateManager = require('../lib/update-manager')
const UpdateManager = require('../lib/update-manager');
describe('UpdateManager', () => {
let updateManager
let updateManager;
beforeEach(() => {
updateManager = new UpdateManager()
})
updateManager = new UpdateManager();
});
describe('::getReleaseNotesURLForVersion', () => {
it('returns atom.io releases when dev version', () => {
expect(
updateManager.getReleaseNotesURLForVersion('1.7.0-dev-e44b57d')
).toContain('atom.io/releases')
})
).toContain('atom.io/releases');
});
it('returns the page for the release when not a dev version', () => {
expect(updateManager.getReleaseNotesURLForVersion('1.7.0')).toContain(
'atom/atom/releases/tag/v1.7.0'
)
);
expect(updateManager.getReleaseNotesURLForVersion('v1.7.0')).toContain(
'atom/atom/releases/tag/v1.7.0'
)
);
expect(
updateManager.getReleaseNotesURLForVersion('1.7.0-beta10')
).toContain('atom/atom/releases/tag/v1.7.0-beta10')
).toContain('atom/atom/releases/tag/v1.7.0-beta10');
expect(
updateManager.getReleaseNotesURLForVersion('1.7.0-nightly10')
).toContain('atom/atom-nightly-releases/releases/tag/v1.7.0-nightly10')
})
})
})
).toContain('atom/atom-nightly-releases/releases/tag/v1.7.0-nightly10');
});
});
});

View File

@ -1,385 +1,387 @@
const { shell } = require('electron')
const main = require('../lib/main')
const AboutView = require('../lib/components/about-view')
const UpdateView = require('../lib/components/update-view')
const MockUpdater = require('./mocks/updater')
const { shell } = require('electron');
const main = require('../lib/main');
const AboutView = require('../lib/components/about-view');
const UpdateView = require('../lib/components/update-view');
const MockUpdater = require('./mocks/updater');
describe('UpdateView', () => {
let aboutElement
let updateManager
let workspaceElement
let scheduler
let aboutElement;
let updateManager;
let workspaceElement;
let scheduler;
beforeEach(async () => {
let storage = {}
let storage = {};
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value
})
storage[key] = value;
});
spyOn(window.localStorage, 'getItem').andCallFake(key => {
return storage[key]
})
return storage[key];
});
workspaceElement = atom.views.getView(atom.workspace)
await atom.packages.activatePackage('about')
spyOn(atom.autoUpdater, 'getState').andReturn('idle')
spyOn(atom.autoUpdater, 'checkForUpdate')
spyOn(atom.autoUpdater, 'platformSupportsUpdates').andReturn(true)
})
workspaceElement = atom.views.getView(atom.workspace);
await atom.packages.activatePackage('about');
spyOn(atom.autoUpdater, 'getState').andReturn('idle');
spyOn(atom.autoUpdater, 'checkForUpdate');
spyOn(atom.autoUpdater, 'platformSupportsUpdates').andReturn(true);
});
describe('when the About page is open', () => {
beforeEach(async () => {
jasmine.attachToDOM(workspaceElement)
await atom.workspace.open('atom://about')
aboutElement = workspaceElement.querySelector('.about')
updateManager = main.model.state.updateManager
scheduler = AboutView.getScheduler()
})
jasmine.attachToDOM(workspaceElement);
await atom.workspace.open('atom://about');
aboutElement = workspaceElement.querySelector('.about');
updateManager = main.model.state.updateManager;
scheduler = AboutView.getScheduler();
});
describe('when the updates are not supported by the platform', () => {
beforeEach(async () => {
atom.autoUpdater.platformSupportsUpdates.andReturn(false)
updateManager.resetState()
await scheduler.getNextUpdatePromise()
})
atom.autoUpdater.platformSupportsUpdates.andReturn(false);
updateManager.resetState();
await scheduler.getNextUpdatePromise();
});
it('hides the auto update UI and shows the update instructions link', async () => {
expect(
aboutElement.querySelector('.about-update-action-button')
).not.toBeVisible()
).not.toBeVisible();
expect(
aboutElement.querySelector('.about-auto-updates')
).not.toBeVisible()
})
).not.toBeVisible();
});
it('opens the update instructions page when the instructions link is clicked', async () => {
spyOn(shell, 'openExternal')
spyOn(shell, 'openExternal');
let link = aboutElement.querySelector(
'.app-unsupported .about-updates-instructions'
)
link.click()
);
link.click();
let args = shell.openExternal.mostRecentCall.args
expect(shell.openExternal).toHaveBeenCalled()
expect(args[0]).toContain('installing-atom')
})
})
let args = shell.openExternal.mostRecentCall.args;
expect(shell.openExternal).toHaveBeenCalled();
expect(args[0]).toContain('installing-atom');
});
});
describe('when updates are supported by the platform', () => {
beforeEach(async () => {
atom.autoUpdater.platformSupportsUpdates.andReturn(true)
updateManager.resetState()
await scheduler.getNextUpdatePromise()
})
atom.autoUpdater.platformSupportsUpdates.andReturn(true);
updateManager.resetState();
await scheduler.getNextUpdatePromise();
});
it('shows the auto update UI', () => {
expect(aboutElement.querySelector('.about-updates')).toBeVisible()
})
expect(aboutElement.querySelector('.about-updates')).toBeVisible();
});
it('shows the correct panels when the app checks for updates and there is no update available', async () => {
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
).toBeVisible();
MockUpdater.checkForUpdate()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
MockUpdater.checkForUpdate();
await scheduler.getNextUpdatePromise();
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).toBeVisible()
).toBeVisible();
MockUpdater.updateNotAvailable()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible()
MockUpdater.updateNotAvailable();
await scheduler.getNextUpdatePromise();
expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible()
})
).not.toBeVisible();
});
it('shows the correct panels when the app checks for updates and encounters an error', async () => {
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
).toBeVisible();
MockUpdater.checkForUpdate()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
MockUpdater.checkForUpdate();
await scheduler.getNextUpdatePromise();
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).toBeVisible()
).toBeVisible();
spyOn(atom.autoUpdater, 'getErrorMessage').andReturn('an error message')
MockUpdater.updateError()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-update-error')).toBeVisible()
spyOn(atom.autoUpdater, 'getErrorMessage').andReturn(
'an error message'
);
MockUpdater.updateError();
await scheduler.getNextUpdatePromise();
expect(aboutElement.querySelector('.app-update-error')).toBeVisible();
expect(
aboutElement.querySelector('.app-error-message').textContent
).toBe('an error message')
).toBe('an error message');
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible()
).not.toBeVisible();
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false)
).toBe(false);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now')
})
).toBe('Check now');
});
it('shows the correct panels and button states when the app checks for updates and an update is downloaded', async () => {
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
).toBeVisible();
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false)
).toBe(false);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now')
).toBe('Check now');
MockUpdater.checkForUpdate()
await scheduler.getNextUpdatePromise()
MockUpdater.checkForUpdate();
await scheduler.getNextUpdatePromise();
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).toBeVisible()
).toBeVisible();
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(true)
).toBe(true);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now')
).toBe('Check now');
MockUpdater.downloadUpdate()
await scheduler.getNextUpdatePromise()
MockUpdater.downloadUpdate();
await scheduler.getNextUpdatePromise();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible()
).not.toBeVisible();
expect(
aboutElement.querySelector('.app-downloading-update')
).toBeVisible()
).toBeVisible();
// TODO: at some point it would be nice to be able to cancel an update download, and then this would be a cancel button
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(true)
).toBe(true);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now')
).toBe('Check now');
MockUpdater.finishDownloadingUpdate('42.0.0')
await scheduler.getNextUpdatePromise()
MockUpdater.finishDownloadingUpdate('42.0.0');
await scheduler.getNextUpdatePromise();
expect(
aboutElement.querySelector('.app-downloading-update')
).not.toBeVisible()
).not.toBeVisible();
expect(
aboutElement.querySelector('.app-update-available-to-install')
).toBeVisible()
).toBeVisible();
expect(
aboutElement.querySelector(
'.app-update-available-to-install .about-updates-version'
).textContent
).toBe('42.0.0')
).toBe('42.0.0');
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false)
).toBe(false);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Restart and install')
})
).toBe('Restart and install');
});
it('opens the release notes for the downloaded release when the release notes link are clicked', async () => {
MockUpdater.finishDownloadingUpdate('1.2.3')
await scheduler.getNextUpdatePromise()
MockUpdater.finishDownloadingUpdate('1.2.3');
await scheduler.getNextUpdatePromise();
spyOn(shell, 'openExternal')
spyOn(shell, 'openExternal');
let link = aboutElement.querySelector(
'.app-update-available-to-install .about-updates-release-notes'
)
link.click()
);
link.click();
let args = shell.openExternal.mostRecentCall.args
expect(shell.openExternal).toHaveBeenCalled()
expect(args[0]).toContain('/v1.2.3')
})
let args = shell.openExternal.mostRecentCall.args;
expect(shell.openExternal).toHaveBeenCalled();
expect(args[0]).toContain('/v1.2.3');
});
it('executes checkForUpdate() when the check for update button is clicked', () => {
let button = aboutElement.querySelector('.about-update-action-button')
button.click()
expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled()
})
let button = aboutElement.querySelector('.about-update-action-button');
button.click();
expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled();
});
it('executes restartAndInstallUpdate() when the restart and install button is clicked', async () => {
spyOn(atom.autoUpdater, 'restartAndInstallUpdate')
MockUpdater.finishDownloadingUpdate('42.0.0')
await scheduler.getNextUpdatePromise()
spyOn(atom.autoUpdater, 'restartAndInstallUpdate');
MockUpdater.finishDownloadingUpdate('42.0.0');
await scheduler.getNextUpdatePromise();
let button = aboutElement.querySelector('.about-update-action-button')
button.click()
expect(atom.autoUpdater.restartAndInstallUpdate).toHaveBeenCalled()
})
let button = aboutElement.querySelector('.about-update-action-button');
button.click();
expect(atom.autoUpdater.restartAndInstallUpdate).toHaveBeenCalled();
});
it("starts in the same state as atom's AutoUpdateManager", async () => {
atom.autoUpdater.getState.andReturn('downloading')
updateManager.resetState()
atom.autoUpdater.getState.andReturn('downloading');
updateManager.resetState();
await scheduler.getNextUpdatePromise()
await scheduler.getNextUpdatePromise();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible()
).not.toBeVisible();
expect(
aboutElement.querySelector('.app-downloading-update')
).toBeVisible()
).toBeVisible();
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(true)
).toBe(true);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now')
})
).toBe('Check now');
});
describe('when core.automaticallyUpdate is toggled', () => {
beforeEach(async () => {
expect(atom.config.get('core.automaticallyUpdate')).toBe(true)
atom.autoUpdater.checkForUpdate.reset()
})
expect(atom.config.get('core.automaticallyUpdate')).toBe(true);
atom.autoUpdater.checkForUpdate.reset();
});
it('shows the auto update UI', async () => {
expect(
aboutElement.querySelector('.about-auto-updates input').checked
).toBe(true)
).toBe(true);
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
).toBeVisible();
expect(
aboutElement.querySelector('.about-default-update-message')
.textContent
).toBe('Atom will check for updates automatically')
).toBe('Atom will check for updates automatically');
atom.config.set('core.automaticallyUpdate', false)
await scheduler.getNextUpdatePromise()
atom.config.set('core.automaticallyUpdate', false);
await scheduler.getNextUpdatePromise();
expect(
aboutElement.querySelector('.about-auto-updates input').checked
).toBe(false)
).toBe(false);
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
).toBeVisible();
expect(
aboutElement.querySelector('.about-default-update-message')
.textContent
).toBe('Automatic updates are disabled please check manually')
})
).toBe('Automatic updates are disabled please check manually');
});
it('updates config and the UI when the checkbox is used to toggle', async () => {
expect(
aboutElement.querySelector('.about-auto-updates input').checked
).toBe(true)
).toBe(true);
aboutElement.querySelector('.about-auto-updates input').click()
await scheduler.getNextUpdatePromise()
aboutElement.querySelector('.about-auto-updates input').click();
await scheduler.getNextUpdatePromise();
expect(atom.config.get('core.automaticallyUpdate')).toBe(false)
expect(atom.config.get('core.automaticallyUpdate')).toBe(false);
expect(
aboutElement.querySelector('.about-auto-updates input').checked
).toBe(false)
).toBe(false);
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
).toBeVisible();
expect(
aboutElement.querySelector('.about-default-update-message')
.textContent
).toBe('Automatic updates are disabled please check manually')
).toBe('Automatic updates are disabled please check manually');
aboutElement.querySelector('.about-auto-updates input').click()
await scheduler.getNextUpdatePromise()
aboutElement.querySelector('.about-auto-updates input').click();
await scheduler.getNextUpdatePromise();
expect(atom.config.get('core.automaticallyUpdate')).toBe(true)
expect(atom.config.get('core.automaticallyUpdate')).toBe(true);
expect(
aboutElement.querySelector('.about-auto-updates input').checked
).toBe(true)
).toBe(true);
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
).toBeVisible();
expect(
aboutElement.querySelector('.about-default-update-message')
.textContent
).toBe('Atom will check for updates automatically')
})
).toBe('Atom will check for updates automatically');
});
describe('checking for updates', function () {
describe('checking for updates', function() {
afterEach(() => {
this.updateView = null
})
this.updateView = null;
});
it('checks for update when the about page is shown', () => {
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled()
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled();
this.updateView = new UpdateView({
updateManager: updateManager,
availableVersion: '9999.0.0',
viewUpdateReleaseNotes: () => {}
})
});
expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled()
})
expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled();
});
it('does not check for update when the about page is shown and the update manager is not in the idle state', () => {
atom.autoUpdater.getState.andReturn('downloading')
updateManager.resetState()
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled()
atom.autoUpdater.getState.andReturn('downloading');
updateManager.resetState();
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled();
this.updateView = new UpdateView({
updateManager: updateManager,
availableVersion: '9999.0.0',
viewUpdateReleaseNotes: () => {}
})
});
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled()
})
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled();
});
it('does not check for update when the about page is shown and auto updates are turned off', () => {
atom.config.set('core.automaticallyUpdate', false)
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled()
atom.config.set('core.automaticallyUpdate', false);
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled();
this.updateView = new UpdateView({
updateManager: updateManager,
availableVersion: '9999.0.0',
viewUpdateReleaseNotes: () => {}
})
});
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled()
})
})
})
})
})
expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled();
});
});
});
});
});
describe('when the About page is not open and an update is downloaded', () => {
it('should display the new version when it is opened', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0')
MockUpdater.finishDownloadingUpdate('42.0.0');
jasmine.attachToDOM(workspaceElement)
await atom.workspace.open('atom://about')
aboutElement = workspaceElement.querySelector('.about')
updateManager = main.model.state.updateManager
scheduler = AboutView.getScheduler()
jasmine.attachToDOM(workspaceElement);
await atom.workspace.open('atom://about');
aboutElement = workspaceElement.querySelector('.about');
updateManager = main.model.state.updateManager;
scheduler = AboutView.getScheduler();
expect(
aboutElement.querySelector('.app-update-available-to-install')
).toBeVisible()
).toBeVisible();
expect(
aboutElement.querySelector(
'.app-update-available-to-install .about-updates-version'
).textContent
).toBe('42.0.0')
).toBe('42.0.0');
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false)
).toBe(false);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Restart and install')
})
})
})
).toBe('Restart and install');
});
});
});

View File

@ -1,56 +1,56 @@
/** @babel */
const fs = require('fs')
const path = require('path')
const fs = require('fs');
const path = require('path');
module.exports = {
async enumerate () {
async enumerate() {
if (atom.inDevMode()) {
return []
return [];
}
const duplicatePackages = []
const names = atom.packages.getAvailablePackageNames()
const duplicatePackages = [];
const names = atom.packages.getAvailablePackageNames();
for (let name of names) {
if (atom.packages.isBundledPackage(name)) {
const isDuplicatedPackage = await this.isInstalledAsCommunityPackage(
name
)
);
if (isDuplicatedPackage) {
duplicatePackages.push(name)
duplicatePackages.push(name);
}
}
}
return duplicatePackages
return duplicatePackages;
},
async isInstalledAsCommunityPackage (name) {
const availablePackagePaths = atom.packages.getPackageDirPaths()
async isInstalledAsCommunityPackage(name) {
const availablePackagePaths = atom.packages.getPackageDirPaths();
for (let packagePath of availablePackagePaths) {
const candidate = path.join(packagePath, name)
const candidate = path.join(packagePath, name);
if (fs.existsSync(candidate)) {
const realPath = await this.realpath(candidate)
const realPath = await this.realpath(candidate);
if (realPath === candidate) {
return true
return true;
}
}
}
return false
return false;
},
realpath (path) {
realpath(path) {
return new Promise((resolve, reject) => {
fs.realpath(path, function (error, realpath) {
fs.realpath(path, function(error, realpath) {
if (error) {
reject(error)
reject(error);
} else {
resolve(realpath)
resolve(realpath);
}
})
})
});
});
}
}
};

View File

@ -1,19 +1,19 @@
/** @babel */
const dalek = require('./dalek')
const Grim = require('grim')
const dalek = require('./dalek');
const Grim = require('grim');
module.exports = {
activate () {
activate() {
atom.packages.onDidActivateInitialPackages(async () => {
const duplicates = await dalek.enumerate()
const duplicates = await dalek.enumerate();
for (let i = 0; i < duplicates.length; i++) {
const duplicate = duplicates[i]
const duplicate = duplicates[i];
Grim.deprecate(
`You have the core package "${duplicate}" installed as a community package. See https://github.com/atom/atom/blob/master/packages/dalek/README.md for how this causes problems and instructions on how to correct the situation.`,
{ packageName: duplicate }
)
);
}
})
});
}
}
};

View File

@ -1,21 +1,21 @@
/** @babel */
const assert = require('assert')
const fs = require('fs')
const sinon = require('sinon')
const path = require('path')
const assert = require('assert');
const fs = require('fs');
const sinon = require('sinon');
const path = require('path');
const dalek = require('../lib/dalek')
const dalek = require('../lib/dalek');
describe('dalek', function () {
describe('enumerate', function () {
let availablePackages = {}
let realPaths = {}
let bundledPackages = []
let packageDirPaths = []
let sandbox = null
describe('dalek', function() {
describe('enumerate', function() {
let availablePackages = {};
let realPaths = {};
let bundledPackages = [];
let packageDirPaths = [];
let sandbox = null;
beforeEach(function () {
beforeEach(function() {
availablePackages = {
'an-unduplicated-installed-package': path.join(
'Users',
@ -36,66 +36,68 @@ describe('dalek', function () {
'node_modules',
'unduplicated-package'
)
}
};
atom.devMode = false
bundledPackages = ['duplicated-package', 'unduplicated-package']
packageDirPaths = [path.join('Users', 'username', '.atom', 'packages')]
sandbox = sinon.sandbox.create()
atom.devMode = false;
bundledPackages = ['duplicated-package', 'unduplicated-package'];
packageDirPaths = [path.join('Users', 'username', '.atom', 'packages')];
sandbox = sinon.sandbox.create();
sandbox
.stub(dalek, 'realpath')
.callsFake(filePath => Promise.resolve(realPaths[filePath] || filePath))
.callsFake(filePath =>
Promise.resolve(realPaths[filePath] || filePath)
);
sandbox.stub(atom.packages, 'isBundledPackage').callsFake(packageName => {
return bundledPackages.includes(packageName)
})
return bundledPackages.includes(packageName);
});
sandbox
.stub(atom.packages, 'getAvailablePackageNames')
.callsFake(() => Object.keys(availablePackages))
.callsFake(() => Object.keys(availablePackages));
sandbox.stub(atom.packages, 'getPackageDirPaths').callsFake(() => {
return packageDirPaths
})
return packageDirPaths;
});
sandbox.stub(fs, 'existsSync').callsFake(candidate => {
return (
Object.values(availablePackages).includes(candidate) &&
!candidate.includes(atom.getLoadSettings().resourcePath)
)
})
})
);
});
});
afterEach(function () {
sandbox.restore()
})
afterEach(function() {
sandbox.restore();
});
it('returns a list of duplicate names', async function () {
assert.deepEqual(await dalek.enumerate(), ['duplicated-package'])
})
it('returns a list of duplicate names', async function() {
assert.deepEqual(await dalek.enumerate(), ['duplicated-package']);
});
describe('when in dev mode', function () {
beforeEach(function () {
atom.devMode = true
})
describe('when in dev mode', function() {
beforeEach(function() {
atom.devMode = true;
});
it('always returns an empty list', async function () {
assert.deepEqual(await dalek.enumerate(), [])
})
})
it('always returns an empty list', async function() {
assert.deepEqual(await dalek.enumerate(), []);
});
});
describe('when a package is symlinked into the package directory', async function () {
beforeEach(function () {
const realPath = path.join('Users', 'username', 'duplicated-package')
describe('when a package is symlinked into the package directory', async function() {
beforeEach(function() {
const realPath = path.join('Users', 'username', 'duplicated-package');
const packagePath = path.join(
'Users',
'username',
'.atom',
'packages',
'duplicated-package'
)
realPaths[packagePath] = realPath
})
);
realPaths[packagePath] = realPath;
});
it('is not included in the list of duplicate names', async function () {
assert.deepEqual(await dalek.enumerate(), [])
})
})
})
})
it('is not included in the list of duplicate names', async function() {
assert.deepEqual(await dalek.enumerate(), []);
});
});
});
});

View File

@ -1,2 +1,2 @@
const createRunner = require('atom-mocha-test-runner').createRunner
module.exports = createRunner({ testSuffixes: ['test.js'] })
const createRunner = require('atom-mocha-test-runner').createRunner;
module.exports = createRunner({ testSuffixes: ['test.js'] });

View File

@ -1,88 +1,88 @@
/** @babel */
/** @jsx etch.dom */
import _ from 'underscore-plus'
import { CompositeDisposable } from 'atom'
import etch from 'etch'
import fs from 'fs-plus'
import Grim from 'grim'
import marked from 'marked'
import path from 'path'
import shell from 'shell'
import _ from 'underscore-plus';
import { CompositeDisposable } from 'atom';
import etch from 'etch';
import fs from 'fs-plus';
import Grim from 'grim';
import marked from 'marked';
import path from 'path';
import shell from 'shell';
export default class DeprecationCopView {
constructor ({ uri }) {
this.uri = uri
this.subscriptions = new CompositeDisposable()
constructor({ uri }) {
this.uri = uri;
this.subscriptions = new CompositeDisposable();
this.subscriptions.add(
Grim.on('updated', () => {
etch.update(this)
etch.update(this);
})
)
);
// TODO: Remove conditional when the new StyleManager deprecation APIs reach stable.
if (atom.styles.onDidUpdateDeprecations) {
this.subscriptions.add(
atom.styles.onDidUpdateDeprecations(() => {
etch.update(this)
etch.update(this);
})
)
);
}
etch.initialize(this)
etch.initialize(this);
this.subscriptions.add(
atom.commands.add(this.element, {
'core:move-up': () => {
this.scrollUp()
this.scrollUp();
},
'core:move-down': () => {
this.scrollDown()
this.scrollDown();
},
'core:page-up': () => {
this.pageUp()
this.pageUp();
},
'core:page-down': () => {
this.pageDown()
this.pageDown();
},
'core:move-to-top': () => {
this.scrollToTop()
this.scrollToTop();
},
'core:move-to-bottom': () => {
this.scrollToBottom()
this.scrollToBottom();
}
})
)
);
}
serialize () {
serialize() {
return {
deserializer: this.constructor.name,
uri: this.getURI(),
version: 1
}
};
}
destroy () {
this.subscriptions.dispose()
return etch.destroy(this)
destroy() {
this.subscriptions.dispose();
return etch.destroy(this);
}
update () {
return etch.update(this)
update() {
return etch.update(this);
}
render () {
render() {
return (
<div
className='deprecation-cop pane-item native-key-bindings'
tabIndex='-1'
className="deprecation-cop pane-item native-key-bindings"
tabIndex="-1"
>
<div className='panel'>
<div className='padded deprecation-overview'>
<div className='pull-right btn-group'>
<div className="panel">
<div className="padded deprecation-overview">
<div className="pull-right btn-group">
<button
className='btn btn-primary check-for-update'
className="btn btn-primary check-for-update"
onclick={event => {
event.preventDefault()
this.checkForUpdates()
event.preventDefault();
this.checkForUpdates();
}}
>
Check for Updates
@ -90,53 +90,53 @@ export default class DeprecationCopView {
</div>
</div>
<div className='panel-heading'>
<div className="panel-heading">
<span>Deprecated calls</span>
</div>
<ul className='list-tree has-collapsable-children'>
<ul className="list-tree has-collapsable-children">
{this.renderDeprecatedCalls()}
</ul>
<div className='panel-heading'>
<div className="panel-heading">
<span>Deprecated selectors</span>
</div>
<ul className='selectors list-tree has-collapsable-children'>
<ul className="selectors list-tree has-collapsable-children">
{this.renderDeprecatedSelectors()}
</ul>
</div>
</div>
)
);
}
renderDeprecatedCalls () {
const deprecationsByPackageName = this.getDeprecatedCallsByPackageName()
const packageNames = Object.keys(deprecationsByPackageName)
renderDeprecatedCalls() {
const deprecationsByPackageName = this.getDeprecatedCallsByPackageName();
const packageNames = Object.keys(deprecationsByPackageName);
if (packageNames.length === 0) {
return <li className='list-item'>No deprecated calls</li>
return <li className="list-item">No deprecated calls</li>;
} else {
return packageNames.sort().map(packageName => (
<li className='deprecation list-nested-item collapsed'>
<li className="deprecation list-nested-item collapsed">
<div
className='deprecation-info list-item'
className="deprecation-info list-item"
onclick={event =>
event.target.parentElement.classList.toggle('collapsed')
}
>
<span className='text-highlight'>{packageName || 'atom core'}</span>
<span className="text-highlight">{packageName || 'atom core'}</span>
<span>{` (${_.pluralize(
deprecationsByPackageName[packageName].length,
'deprecation'
)})`}</span>
</div>
<ul className='list'>
<ul className="list">
{this.renderPackageActionsIfNeeded(packageName)}
{deprecationsByPackageName[packageName].map(
({ deprecation, stack }) => (
<li className='list-item deprecation-detail'>
<span className='text-warning icon icon-alert' />
<li className="list-item deprecation-detail">
<span className="text-warning icon icon-alert" />
<div
className='list-item deprecation-message'
className="list-item deprecation-message"
innerHTML={marked(deprecation.getMessage())}
/>
{this.renderIssueURLIfNeeded(
@ -144,17 +144,17 @@ export default class DeprecationCopView {
deprecation,
this.buildIssueURL(packageName, deprecation, stack)
)}
<div className='stack-trace'>
<div className="stack-trace">
{stack.map(({ functionName, location }) => (
<div className='stack-line'>
<div className="stack-line">
<span>{functionName}</span>
<span> - </span>
<a
className='stack-line-location'
className="stack-line-location"
href={location}
onclick={event => {
event.preventDefault()
this.openLocation(location)
event.preventDefault();
this.openLocation(location);
}}
>
{location}
@ -167,56 +167,56 @@ export default class DeprecationCopView {
)}
</ul>
</li>
))
));
}
}
renderDeprecatedSelectors () {
const deprecationsByPackageName = this.getDeprecatedSelectorsByPackageName()
const packageNames = Object.keys(deprecationsByPackageName)
renderDeprecatedSelectors() {
const deprecationsByPackageName = this.getDeprecatedSelectorsByPackageName();
const packageNames = Object.keys(deprecationsByPackageName);
if (packageNames.length === 0) {
return <li className='list-item'>No deprecated selectors</li>
return <li className="list-item">No deprecated selectors</li>;
} else {
return packageNames.map(packageName => (
<li className='deprecation list-nested-item collapsed'>
<li className="deprecation list-nested-item collapsed">
<div
className='deprecation-info list-item'
className="deprecation-info list-item"
onclick={event =>
event.target.parentElement.classList.toggle('collapsed')
}
>
<span className='text-highlight'>{packageName}</span>
<span className="text-highlight">{packageName}</span>
</div>
<ul className='list'>
<ul className="list">
{this.renderPackageActionsIfNeeded(packageName)}
{deprecationsByPackageName[packageName].map(
({ packagePath, sourcePath, deprecation }) => {
const relativeSourcePath = path.relative(
packagePath,
sourcePath
)
const issueTitle = `Deprecated selector in \`${relativeSourcePath}\``
);
const issueTitle = `Deprecated selector in \`${relativeSourcePath}\``;
const issueBody = `In \`${relativeSourcePath}\`: \n\n${
deprecation.message
}`
}`;
return (
<li className='list-item source-file'>
<li className="list-item source-file">
<a
className='source-url'
className="source-url"
href={sourcePath}
onclick={event => {
event.preventDefault()
this.openLocation(sourcePath)
event.preventDefault();
this.openLocation(sourcePath);
}}
>
{relativeSourcePath}
</a>
<ul className='list'>
<li className='list-item deprecation-detail'>
<span className='text-warning icon icon-alert' />
<ul className="list">
<li className="list-item deprecation-detail">
<span className="text-warning icon icon-alert" />
<div
className='list-item deprecation-message'
className="list-item deprecation-message"
innerHTML={marked(deprecation.message)}
/>
{this.renderSelectorIssueURLIfNeeded(
@ -227,138 +227,138 @@ export default class DeprecationCopView {
</li>
</ul>
</li>
)
);
}
)}
</ul>
</li>
))
));
}
}
renderPackageActionsIfNeeded (packageName) {
renderPackageActionsIfNeeded(packageName) {
if (packageName && atom.packages.getLoadedPackage(packageName)) {
return (
<div className='padded'>
<div className='btn-group'>
<div className="padded">
<div className="btn-group">
<button
className='btn check-for-update'
className="btn check-for-update"
onclick={event => {
event.preventDefault()
this.checkForUpdates()
event.preventDefault();
this.checkForUpdates();
}}
>
Check for Update
</button>
<button
className='btn disable-package'
className="btn disable-package"
data-package-name={packageName}
onclick={event => {
event.preventDefault()
this.disablePackage(packageName)
event.preventDefault();
this.disablePackage(packageName);
}}
>
Disable Package
</button>
</div>
</div>
)
);
} else {
return ''
return '';
}
}
encodeURI (str) {
encodeURI(str) {
return encodeURI(str)
.replace(/#/g, '%23')
.replace(/;/g, '%3B')
.replace(/%20/g, '+')
.replace(/%20/g, '+');
}
renderSelectorIssueURLIfNeeded (packageName, issueTitle, issueBody) {
const repoURL = this.getRepoURL(packageName)
renderSelectorIssueURLIfNeeded(packageName, issueTitle, issueBody) {
const repoURL = this.getRepoURL(packageName);
if (repoURL) {
const issueURL = `${repoURL}/issues/new?title=${this.encodeURI(
issueTitle
)}&body=${this.encodeURI(issueBody)}`
)}&body=${this.encodeURI(issueBody)}`;
return (
<div className='btn-toolbar'>
<div className="btn-toolbar">
<button
className='btn issue-url'
className="btn issue-url"
data-issue-title={issueTitle}
data-repo-url={repoURL}
data-issue-url={issueURL}
onclick={event => {
event.preventDefault()
this.openIssueURL(repoURL, issueURL, issueTitle)
event.preventDefault();
this.openIssueURL(repoURL, issueURL, issueTitle);
}}
>
Report Issue
</button>
</div>
)
);
} else {
return ''
return '';
}
}
renderIssueURLIfNeeded (packageName, deprecation, issueURL) {
renderIssueURLIfNeeded(packageName, deprecation, issueURL) {
if (packageName && issueURL) {
const repoURL = this.getRepoURL(packageName)
const issueTitle = `${deprecation.getOriginName()} is deprecated.`
const repoURL = this.getRepoURL(packageName);
const issueTitle = `${deprecation.getOriginName()} is deprecated.`;
return (
<div className='btn-toolbar'>
<div className="btn-toolbar">
<button
className='btn issue-url'
className="btn issue-url"
data-issue-title={issueTitle}
data-repo-url={repoURL}
data-issue-url={issueURL}
onclick={event => {
event.preventDefault()
this.openIssueURL(repoURL, issueURL, issueTitle)
event.preventDefault();
this.openIssueURL(repoURL, issueURL, issueTitle);
}}
>
Report Issue
</button>
</div>
)
);
} else {
return ''
return '';
}
}
buildIssueURL (packageName, deprecation, stack) {
const repoURL = this.getRepoURL(packageName)
buildIssueURL(packageName, deprecation, stack) {
const repoURL = this.getRepoURL(packageName);
if (repoURL) {
const title = `${deprecation.getOriginName()} is deprecated.`
const title = `${deprecation.getOriginName()} is deprecated.`;
const stacktrace = stack
.map(({ functionName, location }) => `${functionName} (${location})`)
.join('\n')
const body = `${deprecation.getMessage()}\n\`\`\`\n${stacktrace}\n\`\`\``
.join('\n');
const body = `${deprecation.getMessage()}\n\`\`\`\n${stacktrace}\n\`\`\``;
return `${repoURL}/issues/new?title=${encodeURI(title)}&body=${encodeURI(
body
)}`
)}`;
} else {
return null
return null;
}
}
async openIssueURL (repoURL, issueURL, issueTitle) {
const issue = await this.findSimilarIssue(repoURL, issueTitle)
async openIssueURL(repoURL, issueURL, issueTitle) {
const issue = await this.findSimilarIssue(repoURL, issueTitle);
if (issue) {
shell.openExternal(issue.html_url)
shell.openExternal(issue.html_url);
} else if (process.platform === 'win32') {
// Windows will not launch URLs greater than ~2000 bytes so we need to shrink it
shell.openExternal((await this.shortenURL(issueURL)) || issueURL)
shell.openExternal((await this.shortenURL(issueURL)) || issueURL);
} else {
shell.openExternal(issueURL)
shell.openExternal(issueURL);
}
}
async findSimilarIssue (repoURL, issueTitle) {
const url = 'https://api.github.com/search/issues'
const repo = repoURL.replace(/http(s)?:\/\/(\d+\.)?github.com\//gi, '')
const query = `${issueTitle} repo:${repo}`
async findSimilarIssue(repoURL, issueTitle) {
const url = 'https://api.github.com/search/issues';
const repo = repoURL.replace(/http(s)?:\/\/(\d+\.)?github.com\//gi, '');
const query = `${issueTitle} repo:${repo}`;
const response = await window.fetch(
`${url}?q=${encodeURI(query)}&sort=created`,
{
@ -368,45 +368,45 @@ export default class DeprecationCopView {
'Content-Type': 'application/json'
}
}
)
);
if (response.ok) {
const data = await response.json()
const data = await response.json();
if (data.items) {
const issues = {}
const issues = {};
for (const issue of data.items) {
if (issue.title.includes(issueTitle) && !issues[issue.state]) {
issues[issue.state] = issue
issues[issue.state] = issue;
}
}
return issues.open || issues.closed
return issues.open || issues.closed;
}
}
}
async shortenURL (url) {
let encodedUrl = encodeURIComponent(url).substr(0, 5000) // is.gd has 5000 char limit
async shortenURL(url) {
let encodedUrl = encodeURIComponent(url).substr(0, 5000); // is.gd has 5000 char limit
let incompletePercentEncoding = encodedUrl.indexOf(
'%',
encodedUrl.length - 2
)
);
if (incompletePercentEncoding >= 0) {
// Handle an incomplete % encoding cut-off
encodedUrl = encodedUrl.substr(0, incompletePercentEncoding)
encodedUrl = encodedUrl.substr(0, incompletePercentEncoding);
}
let result = await fetch('https://is.gd/create.php?format=simple', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `url=${encodedUrl}`
})
});
return result.text()
return result.text();
}
getRepoURL (packageName) {
const loadedPackage = atom.packages.getLoadedPackage(packageName)
getRepoURL(packageName) {
const loadedPackage = atom.packages.getLoadedPackage(packageName);
if (
loadedPackage &&
loadedPackage.metadata &&
@ -414,171 +414,171 @@ export default class DeprecationCopView {
) {
const url =
loadedPackage.metadata.repository.url ||
loadedPackage.metadata.repository
return url.replace(/\.git$/, '')
loadedPackage.metadata.repository;
return url.replace(/\.git$/, '');
} else {
return null
return null;
}
}
getDeprecatedCallsByPackageName () {
const deprecatedCalls = Grim.getDeprecations()
deprecatedCalls.sort((a, b) => b.getCallCount() - a.getCallCount())
const deprecatedCallsByPackageName = {}
getDeprecatedCallsByPackageName() {
const deprecatedCalls = Grim.getDeprecations();
deprecatedCalls.sort((a, b) => b.getCallCount() - a.getCallCount());
const deprecatedCallsByPackageName = {};
for (const deprecation of deprecatedCalls) {
const stacks = deprecation.getStacks()
stacks.sort((a, b) => b.callCount - a.callCount)
const stacks = deprecation.getStacks();
stacks.sort((a, b) => b.callCount - a.callCount);
for (const stack of stacks) {
let packageName = null
let packageName = null;
if (stack.metadata && stack.metadata.packageName) {
packageName = stack.metadata.packageName
packageName = stack.metadata.packageName;
} else {
packageName = (this.getPackageName(stack) || '').toLowerCase()
packageName = (this.getPackageName(stack) || '').toLowerCase();
}
deprecatedCallsByPackageName[packageName] =
deprecatedCallsByPackageName[packageName] || []
deprecatedCallsByPackageName[packageName].push({ deprecation, stack })
deprecatedCallsByPackageName[packageName] || [];
deprecatedCallsByPackageName[packageName].push({ deprecation, stack });
}
}
return deprecatedCallsByPackageName
return deprecatedCallsByPackageName;
}
getDeprecatedSelectorsByPackageName () {
const deprecatedSelectorsByPackageName = {}
getDeprecatedSelectorsByPackageName() {
const deprecatedSelectorsByPackageName = {};
if (atom.styles.getDeprecations) {
const deprecatedSelectorsBySourcePath = atom.styles.getDeprecations()
const deprecatedSelectorsBySourcePath = atom.styles.getDeprecations();
for (const sourcePath of Object.keys(deprecatedSelectorsBySourcePath)) {
const deprecation = deprecatedSelectorsBySourcePath[sourcePath]
const components = sourcePath.split(path.sep)
const packagesComponentIndex = components.indexOf('packages')
let packageName = null
let packagePath = null
const deprecation = deprecatedSelectorsBySourcePath[sourcePath];
const components = sourcePath.split(path.sep);
const packagesComponentIndex = components.indexOf('packages');
let packageName = null;
let packagePath = null;
if (packagesComponentIndex === -1) {
packageName = 'Other' // could be Atom Core or the personal style sheet
packagePath = ''
packageName = 'Other'; // could be Atom Core or the personal style sheet
packagePath = '';
} else {
packageName = components[packagesComponentIndex + 1]
packageName = components[packagesComponentIndex + 1];
packagePath = components
.slice(0, packagesComponentIndex + 1)
.join(path.sep)
.join(path.sep);
}
deprecatedSelectorsByPackageName[packageName] =
deprecatedSelectorsByPackageName[packageName] || []
deprecatedSelectorsByPackageName[packageName] || [];
deprecatedSelectorsByPackageName[packageName].push({
packagePath,
sourcePath,
deprecation
})
});
}
}
return deprecatedSelectorsByPackageName
return deprecatedSelectorsByPackageName;
}
getPackageName (stack) {
const packagePaths = this.getPackagePathsByPackageName()
getPackageName(stack) {
const packagePaths = this.getPackagePathsByPackageName();
for (const [packageName, packagePath] of packagePaths) {
if (
packagePath.includes('.atom/dev/packages') ||
packagePath.includes('.atom/packages')
) {
packagePaths.set(packageName, fs.absolute(packagePath))
packagePaths.set(packageName, fs.absolute(packagePath));
}
}
for (let i = 1; i < stack.length; i++) {
const { fileName } = stack[i]
const { fileName } = stack[i];
// Empty when it was run from the dev console
if (!fileName) {
return null
return null;
}
// Continue to next stack entry if call is in node_modules
if (fileName.includes(`${path.sep}node_modules${path.sep}`)) {
continue
continue;
}
for (const [packageName, packagePath] of packagePaths) {
const relativePath = path.relative(packagePath, fileName)
const relativePath = path.relative(packagePath, fileName);
if (!/^\.\./.test(relativePath)) {
return packageName
return packageName;
}
}
if (atom.getUserInitScriptPath() === fileName) {
return `Your local ${path.basename(fileName)} file`
return `Your local ${path.basename(fileName)} file`;
}
}
return null
return null;
}
getPackagePathsByPackageName () {
getPackagePathsByPackageName() {
if (this.packagePathsByPackageName) {
return this.packagePathsByPackageName
return this.packagePathsByPackageName;
} else {
this.packagePathsByPackageName = new Map()
this.packagePathsByPackageName = new Map();
for (const pack of atom.packages.getLoadedPackages()) {
this.packagePathsByPackageName.set(pack.name, pack.path)
this.packagePathsByPackageName.set(pack.name, pack.path);
}
return this.packagePathsByPackageName
return this.packagePathsByPackageName;
}
}
checkForUpdates () {
atom.workspace.open('atom://config/updates')
checkForUpdates() {
atom.workspace.open('atom://config/updates');
}
disablePackage (packageName) {
disablePackage(packageName) {
if (packageName) {
atom.packages.disablePackage(packageName)
atom.packages.disablePackage(packageName);
}
}
openLocation (location) {
let pathToOpen = location.replace('file://', '')
openLocation(location) {
let pathToOpen = location.replace('file://', '');
if (process.platform === 'win32') {
pathToOpen = pathToOpen.replace(/^\//, '')
pathToOpen = pathToOpen.replace(/^\//, '');
}
atom.open({ pathsToOpen: [pathToOpen] })
atom.open({ pathsToOpen: [pathToOpen] });
}
getURI () {
return this.uri
getURI() {
return this.uri;
}
getTitle () {
return 'Deprecation Cop'
getTitle() {
return 'Deprecation Cop';
}
getIconName () {
return 'alert'
getIconName() {
return 'alert';
}
scrollUp () {
this.element.scrollTop -= document.body.offsetHeight / 20
scrollUp() {
this.element.scrollTop -= document.body.offsetHeight / 20;
}
scrollDown () {
this.element.scrollTop += document.body.offsetHeight / 20
scrollDown() {
this.element.scrollTop += document.body.offsetHeight / 20;
}
pageUp () {
this.element.scrollTop -= this.element.offsetHeight
pageUp() {
this.element.scrollTop -= this.element.offsetHeight;
}
pageDown () {
this.element.scrollTop += this.element.offsetHeight
pageDown() {
this.element.scrollTop += this.element.offsetHeight;
}
scrollToTop () {
this.element.scrollTop = 0
scrollToTop() {
this.element.scrollTop = 0;
}
scrollToBottom () {
this.element.scrollTop = this.element.scrollHeight
scrollToBottom() {
this.element.scrollTop = this.element.scrollHeight;
}
}

View File

@ -1,54 +1,54 @@
const { Disposable, CompositeDisposable } = require('atom')
const DeprecationCopView = require('./deprecation-cop-view')
const DeprecationCopStatusBarView = require('./deprecation-cop-status-bar-view')
const ViewURI = 'atom://deprecation-cop'
const { Disposable, CompositeDisposable } = require('atom');
const DeprecationCopView = require('./deprecation-cop-view');
const DeprecationCopStatusBarView = require('./deprecation-cop-status-bar-view');
const ViewURI = 'atom://deprecation-cop';
class DeprecationCopPackage {
activate () {
this.disposables = new CompositeDisposable()
activate() {
this.disposables = new CompositeDisposable();
this.disposables.add(
atom.workspace.addOpener(uri => {
if (uri === ViewURI) {
return this.deserializeDeprecationCopView({ uri })
return this.deserializeDeprecationCopView({ uri });
}
})
)
);
this.disposables.add(
atom.commands.add('atom-workspace', 'deprecation-cop:view', () => {
atom.workspace.open(ViewURI)
atom.workspace.open(ViewURI);
})
)
);
}
deactivate () {
this.disposables.dispose()
const pane = atom.workspace.paneForURI(ViewURI)
deactivate() {
this.disposables.dispose();
const pane = atom.workspace.paneForURI(ViewURI);
if (pane) {
pane.destroyItem(pane.itemForURI(ViewURI))
pane.destroyItem(pane.itemForURI(ViewURI));
}
}
deserializeDeprecationCopView (state) {
return new DeprecationCopView(state)
deserializeDeprecationCopView(state) {
return new DeprecationCopView(state);
}
consumeStatusBar (statusBar) {
const statusBarView = new DeprecationCopStatusBarView()
consumeStatusBar(statusBar) {
const statusBarView = new DeprecationCopStatusBarView();
const statusBarTile = statusBar.addRightTile({
item: statusBarView,
priority: 150
})
});
this.disposables.add(
new Disposable(() => {
statusBarView.destroy()
statusBarView.destroy();
})
)
);
this.disposables.add(
new Disposable(() => {
statusBarTile.destroy()
statusBarTile.destroy();
})
)
);
}
}
module.exports = new DeprecationCopPackage()
module.exports = new DeprecationCopPackage();

View File

@ -1,31 +1,31 @@
const fs = require('fs-plus')
const path = require('path')
const Watcher = require('./watcher')
const fs = require('fs-plus');
const path = require('path');
const Watcher = require('./watcher');
module.exports = class BaseThemeWatcher extends Watcher {
constructor () {
super()
constructor() {
super();
this.stylesheetsPath = path.dirname(
atom.themes.resolveStylesheet('../static/atom.less')
)
this.watch()
);
this.watch();
}
watch () {
watch() {
const filePaths = fs
.readdirSync(this.stylesheetsPath)
.filter(filePath => path.extname(filePath).includes('less'))
.filter(filePath => path.extname(filePath).includes('less'));
for (const filePath of filePaths) {
this.watchFile(path.join(this.stylesheetsPath, filePath))
this.watchFile(path.join(this.stylesheetsPath, filePath));
}
}
loadStylesheet () {
this.loadAllStylesheets()
loadStylesheet() {
this.loadAllStylesheets();
}
loadAllStylesheets () {
atom.themes.reloadBaseStylesheets()
loadAllStylesheets() {
atom.themes.reloadBaseStylesheets();
}
}
};

View File

@ -1,30 +1,30 @@
module.exports = {
activate (state) {
if (!atom.inDevMode() || atom.inSpecMode()) return
activate(state) {
if (!atom.inDevMode() || atom.inSpecMode()) return;
if (atom.packages.hasActivatedInitialPackages()) {
this.startWatching()
this.startWatching();
} else {
this.activatedDisposable = atom.packages.onDidActivateInitialPackages(
() => this.startWatching()
)
);
}
},
deactivate () {
if (this.activatedDisposable) this.activatedDisposable.dispose()
if (this.commandDisposable) this.commandDisposable.dispose()
if (this.uiWatcher) this.uiWatcher.destroy()
deactivate() {
if (this.activatedDisposable) this.activatedDisposable.dispose();
if (this.commandDisposable) this.commandDisposable.dispose();
if (this.uiWatcher) this.uiWatcher.destroy();
},
startWatching () {
const UIWatcher = require('./ui-watcher')
this.uiWatcher = new UIWatcher({ themeManager: atom.themes })
startWatching() {
const UIWatcher = require('./ui-watcher');
this.uiWatcher = new UIWatcher({ themeManager: atom.themes });
this.commandDisposable = atom.commands.add(
'atom-workspace',
'dev-live-reload:reload-all',
() => this.uiWatcher.reloadAll()
)
if (this.activatedDisposable) this.activatedDisposable.dispose()
);
if (this.activatedDisposable) this.activatedDisposable.dispose();
}
}
};

View File

@ -1,49 +1,50 @@
const fs = require('fs-plus')
const fs = require('fs-plus');
const Watcher = require('./watcher')
const Watcher = require('./watcher');
module.exports = class PackageWatcher extends Watcher {
static supportsPackage (pack, type) {
if (pack.getType() === type && pack.getStylesheetPaths().length) return true
return false
static supportsPackage(pack, type) {
if (pack.getType() === type && pack.getStylesheetPaths().length)
return true;
return false;
}
constructor (pack) {
super()
this.pack = pack
this.watch()
constructor(pack) {
super();
this.pack = pack;
this.watch();
}
watch () {
const watchedPaths = []
watch() {
const watchedPaths = [];
const watchPath = stylesheet => {
if (!watchedPaths.includes(stylesheet)) this.watchFile(stylesheet)
watchedPaths.push(stylesheet)
}
if (!watchedPaths.includes(stylesheet)) this.watchFile(stylesheet);
watchedPaths.push(stylesheet);
};
const stylesheetsPath = this.pack.getStylesheetsPath()
const stylesheetsPath = this.pack.getStylesheetsPath();
if (fs.isDirectorySync(stylesheetsPath)) {
this.watchDirectory(stylesheetsPath)
this.watchDirectory(stylesheetsPath);
}
const stylesheetPaths = new Set(this.pack.getStylesheetPaths())
const onFile = stylesheetPath => stylesheetPaths.add(stylesheetPath)
const onFolder = () => true
fs.traverseTreeSync(stylesheetsPath, onFile, onFolder)
const stylesheetPaths = new Set(this.pack.getStylesheetPaths());
const onFile = stylesheetPath => stylesheetPaths.add(stylesheetPath);
const onFolder = () => true;
fs.traverseTreeSync(stylesheetsPath, onFile, onFolder);
for (let stylesheet of stylesheetPaths) {
watchPath(stylesheet)
watchPath(stylesheet);
}
}
loadStylesheet (pathName) {
if (pathName.includes('variables')) this.emitGlobalsChanged()
this.loadAllStylesheets()
loadStylesheet(pathName) {
if (pathName.includes('variables')) this.emitGlobalsChanged();
this.loadAllStylesheets();
}
loadAllStylesheets () {
console.log('Reloading package', this.pack.name)
this.pack.reloadStylesheets()
loadAllStylesheets() {
console.log('Reloading package', this.pack.name);
this.pack.reloadStylesheets();
}
}
};

View File

@ -1,114 +1,114 @@
const { CompositeDisposable } = require('atom')
const { CompositeDisposable } = require('atom');
const BaseThemeWatcher = require('./base-theme-watcher')
const PackageWatcher = require('./package-watcher')
const BaseThemeWatcher = require('./base-theme-watcher');
const PackageWatcher = require('./package-watcher');
module.exports = class UIWatcher {
constructor () {
this.subscriptions = new CompositeDisposable()
this.reloadAll = this.reloadAll.bind(this)
this.watchers = []
this.baseTheme = this.createWatcher(new BaseThemeWatcher())
this.watchPackages()
constructor() {
this.subscriptions = new CompositeDisposable();
this.reloadAll = this.reloadAll.bind(this);
this.watchers = [];
this.baseTheme = this.createWatcher(new BaseThemeWatcher());
this.watchPackages();
}
watchPackages () {
this.watchedThemes = new Map()
this.watchedPackages = new Map()
watchPackages() {
this.watchedThemes = new Map();
this.watchedPackages = new Map();
for (const theme of atom.themes.getActiveThemes()) {
this.watchTheme(theme)
this.watchTheme(theme);
}
for (const pack of atom.packages.getActivePackages()) {
this.watchPackage(pack)
this.watchPackage(pack);
}
this.watchForPackageChanges()
this.watchForPackageChanges();
}
watchForPackageChanges () {
watchForPackageChanges() {
this.subscriptions.add(
atom.themes.onDidChangeActiveThemes(() => {
// We need to destroy all theme watchers as all theme packages are destroyed
// when a theme changes.
for (const theme of this.watchedThemes.values()) {
theme.destroy()
theme.destroy();
}
this.watchedThemes.clear()
this.watchedThemes.clear();
// Rewatch everything!
for (const theme of atom.themes.getActiveThemes()) {
this.watchTheme(theme)
this.watchTheme(theme);
}
})
)
);
this.subscriptions.add(
atom.packages.onDidActivatePackage(pack => this.watchPackage(pack))
)
);
this.subscriptions.add(
atom.packages.onDidDeactivatePackage(pack => {
// This only handles packages - onDidChangeActiveThemes handles themes
const watcher = this.watchedPackages.get(pack.name)
if (watcher) watcher.destroy()
this.watchedPackages.delete(pack.name)
const watcher = this.watchedPackages.get(pack.name);
if (watcher) watcher.destroy();
this.watchedPackages.delete(pack.name);
})
)
);
}
watchTheme (theme) {
watchTheme(theme) {
if (PackageWatcher.supportsPackage(theme, 'theme')) {
this.watchedThemes.set(
theme.name,
this.createWatcher(new PackageWatcher(theme))
)
);
}
}
watchPackage (pack) {
watchPackage(pack) {
if (PackageWatcher.supportsPackage(pack, 'atom')) {
this.watchedPackages.set(
pack.name,
this.createWatcher(new PackageWatcher(pack))
)
);
}
}
createWatcher (watcher) {
createWatcher(watcher) {
watcher.onDidChangeGlobals(() => {
console.log('Global changed, reloading all styles')
this.reloadAll()
})
console.log('Global changed, reloading all styles');
this.reloadAll();
});
watcher.onDidDestroy(() =>
this.watchers.splice(this.watchers.indexOf(watcher), 1)
)
this.watchers.push(watcher)
return watcher
);
this.watchers.push(watcher);
return watcher;
}
reloadAll () {
this.baseTheme.loadAllStylesheets()
reloadAll() {
this.baseTheme.loadAllStylesheets();
for (const pack of atom.packages.getActivePackages()) {
if (PackageWatcher.supportsPackage(pack, 'atom')) {
pack.reloadStylesheets()
pack.reloadStylesheets();
}
}
for (const theme of atom.themes.getActiveThemes()) {
if (PackageWatcher.supportsPackage(theme, 'theme')) {
theme.reloadStylesheets()
theme.reloadStylesheets();
}
}
}
destroy () {
this.subscriptions.dispose()
this.baseTheme.destroy()
destroy() {
this.subscriptions.dispose();
this.baseTheme.destroy();
for (const pack of this.watchedPackages.values()) {
pack.destroy()
pack.destroy();
}
for (const theme of this.watchedThemes.values()) {
theme.destroy()
theme.destroy();
}
}
}
};

View File

@ -1,74 +1,74 @@
const { CompositeDisposable, File, Directory, Emitter } = require('atom')
const path = require('path')
const { CompositeDisposable, File, Directory, Emitter } = require('atom');
const path = require('path');
module.exports = class Watcher {
constructor () {
this.destroy = this.destroy.bind(this)
this.emitter = new Emitter()
this.disposables = new CompositeDisposable()
this.entities = [] // Used for specs
constructor() {
this.destroy = this.destroy.bind(this);
this.emitter = new Emitter();
this.disposables = new CompositeDisposable();
this.entities = []; // Used for specs
}
onDidDestroy (callback) {
this.emitter.on('did-destroy', callback)
onDidDestroy(callback) {
this.emitter.on('did-destroy', callback);
}
onDidChangeGlobals (callback) {
this.emitter.on('did-change-globals', callback)
onDidChangeGlobals(callback) {
this.emitter.on('did-change-globals', callback);
}
destroy () {
this.disposables.dispose()
this.entities = null
this.emitter.emit('did-destroy')
this.emitter.dispose()
destroy() {
this.disposables.dispose();
this.entities = null;
this.emitter.emit('did-destroy');
this.emitter.dispose();
}
watch () {
watch() {
// override me
}
loadStylesheet (stylesheetPath) {
loadStylesheet(stylesheetPath) {
// override me
}
loadAllStylesheets () {
loadAllStylesheets() {
// override me
}
emitGlobalsChanged () {
this.emitter.emit('did-change-globals')
emitGlobalsChanged() {
this.emitter.emit('did-change-globals');
}
watchDirectory (directoryPath) {
if (this.isInAsarArchive(directoryPath)) return
const entity = new Directory(directoryPath)
this.disposables.add(entity.onDidChange(() => this.loadAllStylesheets()))
this.entities.push(entity)
watchDirectory(directoryPath) {
if (this.isInAsarArchive(directoryPath)) return;
const entity = new Directory(directoryPath);
this.disposables.add(entity.onDidChange(() => this.loadAllStylesheets()));
this.entities.push(entity);
}
watchGlobalFile (filePath) {
const entity = new File(filePath)
this.disposables.add(entity.onDidChange(() => this.emitGlobalsChanged()))
this.entities.push(entity)
watchGlobalFile(filePath) {
const entity = new File(filePath);
this.disposables.add(entity.onDidChange(() => this.emitGlobalsChanged()));
this.entities.push(entity);
}
watchFile (filePath) {
if (this.isInAsarArchive(filePath)) return
const reloadFn = () => this.loadStylesheet(entity.getPath())
watchFile(filePath) {
if (this.isInAsarArchive(filePath)) return;
const reloadFn = () => this.loadStylesheet(entity.getPath());
const entity = new File(filePath)
this.disposables.add(entity.onDidChange(reloadFn))
this.disposables.add(entity.onDidDelete(reloadFn))
this.disposables.add(entity.onDidRename(reloadFn))
this.entities.push(entity)
const entity = new File(filePath);
this.disposables.add(entity.onDidChange(reloadFn));
this.disposables.add(entity.onDidDelete(reloadFn));
this.disposables.add(entity.onDidRename(reloadFn));
this.entities.push(entity);
}
isInAsarArchive (pathToCheck) {
const { resourcePath } = atom.getLoadSettings()
isInAsarArchive(pathToCheck) {
const { resourcePath } = atom.getLoadSettings();
return (
pathToCheck.startsWith(`${resourcePath}${path.sep}`) &&
path.extname(resourcePath) === '.asar'
)
);
}
}
};

View File

@ -1,26 +1,26 @@
/** @babel */
export async function conditionPromise (
export async function conditionPromise(
condition,
description = 'anonymous condition'
) {
const startTime = Date.now()
const startTime = Date.now();
while (true) {
await timeoutPromise(100)
await timeoutPromise(100);
if (await condition()) {
return
return;
}
if (Date.now() - startTime > 5000) {
throw new Error('Timed out waiting on ' + description)
throw new Error('Timed out waiting on ' + description);
}
}
}
export function timeoutPromise (timeout) {
return new Promise(function (resolve) {
global.setTimeout(resolve, timeout)
})
export function timeoutPromise(timeout) {
return new Promise(function(resolve) {
global.setTimeout(resolve, timeout);
});
}

View File

@ -1,128 +1,128 @@
describe('Dev Live Reload', () => {
describe('package activation', () => {
let [pack, mainModule] = []
let [pack, mainModule] = [];
beforeEach(() => {
pack = atom.packages.loadPackage('dev-live-reload')
pack.requireMainModule()
mainModule = pack.mainModule
spyOn(mainModule, 'startWatching')
})
pack = atom.packages.loadPackage('dev-live-reload');
pack.requireMainModule();
mainModule = pack.mainModule;
spyOn(mainModule, 'startWatching');
});
describe('when the window is not in dev mode', () => {
beforeEach(() => spyOn(atom, 'inDevMode').andReturn(false))
beforeEach(() => spyOn(atom, 'inDevMode').andReturn(false));
it('does not watch files', async () => {
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true);
await atom.packages.activatePackage('dev-live-reload')
expect(mainModule.startWatching).not.toHaveBeenCalled()
})
})
await atom.packages.activatePackage('dev-live-reload');
expect(mainModule.startWatching).not.toHaveBeenCalled();
});
});
describe('when the window is in spec mode', () => {
beforeEach(() => spyOn(atom, 'inSpecMode').andReturn(true))
beforeEach(() => spyOn(atom, 'inSpecMode').andReturn(true));
it('does not watch files', async () => {
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true);
await atom.packages.activatePackage('dev-live-reload')
expect(mainModule.startWatching).not.toHaveBeenCalled()
})
})
await atom.packages.activatePackage('dev-live-reload');
expect(mainModule.startWatching).not.toHaveBeenCalled();
});
});
describe('when the window is in dev mode', () => {
beforeEach(() => {
spyOn(atom, 'inDevMode').andReturn(true)
spyOn(atom, 'inSpecMode').andReturn(false)
})
spyOn(atom, 'inDevMode').andReturn(true);
spyOn(atom, 'inSpecMode').andReturn(false);
});
it('watches files', async () => {
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true);
await atom.packages.activatePackage('dev-live-reload')
expect(mainModule.startWatching).toHaveBeenCalled()
})
})
await atom.packages.activatePackage('dev-live-reload');
expect(mainModule.startWatching).toHaveBeenCalled();
});
});
describe('when the window is in both dev mode and spec mode', () => {
beforeEach(() => {
spyOn(atom, 'inDevMode').andReturn(true)
spyOn(atom, 'inSpecMode').andReturn(true)
})
spyOn(atom, 'inDevMode').andReturn(true);
spyOn(atom, 'inSpecMode').andReturn(true);
});
it('does not watch files', async () => {
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true);
await atom.packages.activatePackage('dev-live-reload')
expect(mainModule.startWatching).not.toHaveBeenCalled()
})
})
await atom.packages.activatePackage('dev-live-reload');
expect(mainModule.startWatching).not.toHaveBeenCalled();
});
});
describe('when the package is activated before initial packages have been activated', () => {
beforeEach(() => {
spyOn(atom, 'inDevMode').andReturn(true)
spyOn(atom, 'inSpecMode').andReturn(false)
})
spyOn(atom, 'inDevMode').andReturn(true);
spyOn(atom, 'inSpecMode').andReturn(false);
});
it('waits until all initial packages have been activated before watching files', async () => {
await atom.packages.activatePackage('dev-live-reload')
expect(mainModule.startWatching).not.toHaveBeenCalled()
await atom.packages.activatePackage('dev-live-reload');
expect(mainModule.startWatching).not.toHaveBeenCalled();
atom.packages.emitter.emit('did-activate-initial-packages')
expect(mainModule.startWatching).toHaveBeenCalled()
})
})
})
atom.packages.emitter.emit('did-activate-initial-packages');
expect(mainModule.startWatching).toHaveBeenCalled();
});
});
});
describe('package deactivation', () => {
beforeEach(() => {
spyOn(atom, 'inDevMode').andReturn(true)
spyOn(atom, 'inSpecMode').andReturn(false)
})
spyOn(atom, 'inDevMode').andReturn(true);
spyOn(atom, 'inSpecMode').andReturn(false);
});
it('stops watching all files', async () => {
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true);
const { mainModule } = await atom.packages.activatePackage(
'dev-live-reload'
)
expect(mainModule.uiWatcher).not.toBeNull()
);
expect(mainModule.uiWatcher).not.toBeNull();
spyOn(mainModule.uiWatcher, 'destroy')
spyOn(mainModule.uiWatcher, 'destroy');
await atom.packages.deactivatePackage('dev-live-reload')
expect(mainModule.uiWatcher.destroy).toHaveBeenCalled()
})
await atom.packages.deactivatePackage('dev-live-reload');
expect(mainModule.uiWatcher.destroy).toHaveBeenCalled();
});
it('unsubscribes from the onDidActivateInitialPackages subscription if it is disabled before all initial packages are activated', async () => {
const { mainModule } = await atom.packages.activatePackage(
'dev-live-reload'
)
expect(mainModule.activatedDisposable.disposed).toBe(false)
);
expect(mainModule.activatedDisposable.disposed).toBe(false);
await atom.packages.deactivatePackage('dev-live-reload')
expect(mainModule.activatedDisposable.disposed).toBe(true)
await atom.packages.deactivatePackage('dev-live-reload');
expect(mainModule.activatedDisposable.disposed).toBe(true);
spyOn(mainModule, 'startWatching')
atom.packages.emitter.emit('did-activate-initial-packages')
expect(mainModule.startWatching).not.toHaveBeenCalled()
})
spyOn(mainModule, 'startWatching');
atom.packages.emitter.emit('did-activate-initial-packages');
expect(mainModule.startWatching).not.toHaveBeenCalled();
});
it('removes its commands', async () => {
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
await atom.packages.activatePackage('dev-live-reload')
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true);
await atom.packages.activatePackage('dev-live-reload');
expect(
atom.commands
.findCommands({ target: atom.views.getView(atom.workspace) })
.filter(command => command.name.startsWith('dev-live-reload')).length
).toBeGreaterThan(0)
).toBeGreaterThan(0);
await atom.packages.deactivatePackage('dev-live-reload')
await atom.packages.deactivatePackage('dev-live-reload');
expect(
atom.commands
.findCommands({ target: atom.views.getView(atom.workspace) })
.filter(command => command.name.startsWith('dev-live-reload')).length
).toBe(0)
})
})
})
).toBe(0);
});
});
});

View File

@ -1,260 +1,262 @@
const path = require('path')
const path = require('path');
const UIWatcher = require('../lib/ui-watcher')
const UIWatcher = require('../lib/ui-watcher');
const { conditionPromise } = require('./async-spec-helpers')
const { conditionPromise } = require('./async-spec-helpers');
describe('UIWatcher', () => {
let uiWatcher = null
let uiWatcher = null;
beforeEach(() =>
atom.packages.packageDirPaths.push(path.join(__dirname, 'fixtures'))
)
);
afterEach(() => uiWatcher && uiWatcher.destroy())
afterEach(() => uiWatcher && uiWatcher.destroy());
describe("when a base theme's file changes", () => {
beforeEach(() => {
spyOn(atom.themes, 'resolveStylesheet').andReturn(
path.join(__dirname, 'fixtures', 'static', 'atom.less')
)
uiWatcher = new UIWatcher()
})
);
uiWatcher = new UIWatcher();
});
it('reloads all the base styles', () => {
spyOn(atom.themes, 'reloadBaseStylesheets')
spyOn(atom.themes, 'reloadBaseStylesheets');
expect(uiWatcher.baseTheme.entities[0].getPath()).toContain(
`${path.sep}static${path.sep}`
)
);
uiWatcher.baseTheme.entities[0].emitter.emit('did-change')
expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled()
})
})
uiWatcher.baseTheme.entities[0].emitter.emit('did-change');
expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled();
});
});
it("watches all the style sheets in the theme's styles folder", async () => {
const packagePath = path.join(
__dirname,
'fixtures',
'package-with-styles-folder'
)
);
await atom.packages.activatePackage(packagePath)
uiWatcher = new UIWatcher()
await atom.packages.activatePackage(packagePath);
uiWatcher = new UIWatcher();
const lastWatcher = uiWatcher.watchers[uiWatcher.watchers.length - 1]
const lastWatcher = uiWatcher.watchers[uiWatcher.watchers.length - 1];
expect(lastWatcher.entities.length).toBe(4)
expect(lastWatcher.entities.length).toBe(4);
expect(lastWatcher.entities[0].getPath()).toBe(
path.join(packagePath, 'styles')
)
);
expect(lastWatcher.entities[1].getPath()).toBe(
path.join(packagePath, 'styles', '3.css')
)
);
expect(lastWatcher.entities[2].getPath()).toBe(
path.join(packagePath, 'styles', 'sub', '1.css')
)
);
expect(lastWatcher.entities[3].getPath()).toBe(
path.join(packagePath, 'styles', 'sub', '2.less')
)
})
);
});
describe('when a package stylesheet file changes', async () => {
beforeEach(async () => {
await atom.packages.activatePackage(
path.join(__dirname, 'fixtures', 'package-with-styles-manifest')
)
uiWatcher = new UIWatcher()
})
);
uiWatcher = new UIWatcher();
});
it('reloads all package styles', () => {
const pack = atom.packages.getActivePackages()[0]
spyOn(pack, 'reloadStylesheets')
const pack = atom.packages.getActivePackages()[0];
spyOn(pack, 'reloadStylesheets');
uiWatcher.watchers[uiWatcher.watchers.length - 1].entities[1].emitter.emit('did-change')
uiWatcher.watchers[
uiWatcher.watchers.length - 1
].entities[1].emitter.emit('did-change');
expect(pack.reloadStylesheets).toHaveBeenCalled()
})
})
expect(pack.reloadStylesheets).toHaveBeenCalled();
});
});
describe('when a package does not have a stylesheet', () => {
beforeEach(async () => {
await atom.packages.activatePackage('package-with-index')
uiWatcher = new UIWatcher()
})
await atom.packages.activatePackage('package-with-index');
uiWatcher = new UIWatcher();
});
it('does not create a PackageWatcher', () => {
expect(uiWatcher.watchedPackages['package-with-index']).toBeUndefined()
})
})
expect(uiWatcher.watchedPackages['package-with-index']).toBeUndefined();
});
});
describe('when a package global file changes', () => {
beforeEach(async () => {
atom.config.set('core.themes', [
'theme-with-ui-variables',
'theme-with-multiple-imported-files'
])
]);
await atom.themes.activateThemes()
uiWatcher = new UIWatcher()
})
await atom.themes.activateThemes();
uiWatcher = new UIWatcher();
});
afterEach(() => atom.themes.deactivateThemes())
afterEach(() => atom.themes.deactivateThemes());
it('reloads every package when the variables file changes', () => {
let varEntity
let varEntity;
for (const theme of atom.themes.getActiveThemes()) {
spyOn(theme, 'reloadStylesheets')
spyOn(theme, 'reloadStylesheets');
}
for (const entity of uiWatcher.watchedThemes.get(
'theme-with-multiple-imported-files'
).entities) {
if (entity.getPath().indexOf('variables') > -1) varEntity = entity
if (entity.getPath().indexOf('variables') > -1) varEntity = entity;
}
varEntity.emitter.emit('did-change')
varEntity.emitter.emit('did-change');
for (const theme of atom.themes.getActiveThemes()) {
expect(theme.reloadStylesheets).toHaveBeenCalled()
expect(theme.reloadStylesheets).toHaveBeenCalled();
}
})
})
});
});
describe('watcher lifecycle', () => {
it('starts watching a package if it is activated after initial startup', async () => {
uiWatcher = new UIWatcher()
expect(uiWatcher.watchedPackages.size).toBe(0)
uiWatcher = new UIWatcher();
expect(uiWatcher.watchedPackages.size).toBe(0);
await atom.packages.activatePackage(
path.join(__dirname, 'fixtures', 'package-with-styles-folder')
)
);
expect(
uiWatcher.watchedPackages.get('package-with-styles-folder')
).not.toBeUndefined()
})
).not.toBeUndefined();
});
it('unwatches a package after it is deactivated', async () => {
await atom.packages.activatePackage(
path.join(__dirname, 'fixtures', 'package-with-styles-folder')
)
uiWatcher = new UIWatcher()
);
uiWatcher = new UIWatcher();
const watcher = uiWatcher.watchedPackages.get(
'package-with-styles-folder'
)
expect(watcher).not.toBeUndefined()
);
expect(watcher).not.toBeUndefined();
const watcherDestructionSpy = jasmine.createSpy('watcher-on-did-destroy')
watcher.onDidDestroy(watcherDestructionSpy)
const watcherDestructionSpy = jasmine.createSpy('watcher-on-did-destroy');
watcher.onDidDestroy(watcherDestructionSpy);
await atom.packages.deactivatePackage('package-with-styles-folder')
await atom.packages.deactivatePackage('package-with-styles-folder');
expect(
uiWatcher.watchedPackages.get('package-with-styles-folder')
).toBeUndefined()
expect(uiWatcher.watchedPackages.size).toBe(0)
expect(watcherDestructionSpy).toHaveBeenCalled()
})
).toBeUndefined();
expect(uiWatcher.watchedPackages.size).toBe(0);
expect(watcherDestructionSpy).toHaveBeenCalled();
});
it('does not watch activated packages after the UI watcher has been destroyed', async () => {
uiWatcher = new UIWatcher()
uiWatcher.destroy()
uiWatcher = new UIWatcher();
uiWatcher.destroy();
await atom.packages.activatePackage(
path.join(__dirname, 'fixtures', 'package-with-styles-folder')
)
expect(uiWatcher.watchedPackages.size).toBe(0)
})
})
);
expect(uiWatcher.watchedPackages.size).toBe(0);
});
});
describe('minimal theme packages', () => {
let pack = null
let pack = null;
beforeEach(async () => {
atom.config.set('core.themes', [
'theme-with-syntax-variables',
'theme-with-index-less'
])
await atom.themes.activateThemes()
uiWatcher = new UIWatcher()
pack = atom.themes.getActiveThemes()[0]
})
]);
await atom.themes.activateThemes();
uiWatcher = new UIWatcher();
pack = atom.themes.getActiveThemes()[0];
});
afterEach(() => atom.themes.deactivateThemes())
afterEach(() => atom.themes.deactivateThemes());
it('watches themes without a styles directory', () => {
spyOn(pack, 'reloadStylesheets')
spyOn(atom.themes, 'reloadBaseStylesheets')
spyOn(pack, 'reloadStylesheets');
spyOn(atom.themes, 'reloadBaseStylesheets');
const watcher = uiWatcher.watchedThemes.get('theme-with-index-less')
const watcher = uiWatcher.watchedThemes.get('theme-with-index-less');
expect(watcher.entities.length).toBe(1)
expect(watcher.entities.length).toBe(1);
watcher.entities[0].emitter.emit('did-change')
expect(pack.reloadStylesheets).toHaveBeenCalled()
expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled()
})
})
watcher.entities[0].emitter.emit('did-change');
expect(pack.reloadStylesheets).toHaveBeenCalled();
expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled();
});
});
describe('theme packages', () => {
let pack = null
let pack = null;
beforeEach(async () => {
atom.config.set('core.themes', [
'theme-with-syntax-variables',
'theme-with-multiple-imported-files'
])
]);
await atom.themes.activateThemes()
uiWatcher = new UIWatcher()
pack = atom.themes.getActiveThemes()[0]
})
await atom.themes.activateThemes();
uiWatcher = new UIWatcher();
pack = atom.themes.getActiveThemes()[0];
});
afterEach(() => atom.themes.deactivateThemes())
afterEach(() => atom.themes.deactivateThemes());
it('reloads the theme when anything within the theme changes', () => {
spyOn(pack, 'reloadStylesheets')
spyOn(atom.themes, 'reloadBaseStylesheets')
spyOn(pack, 'reloadStylesheets');
spyOn(atom.themes, 'reloadBaseStylesheets');
const watcher = uiWatcher.watchedThemes.get(
'theme-with-multiple-imported-files'
)
);
expect(watcher.entities.length).toBe(6)
expect(watcher.entities.length).toBe(6);
watcher.entities[2].emitter.emit('did-change')
expect(pack.reloadStylesheets).toHaveBeenCalled()
expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled()
watcher.entities[2].emitter.emit('did-change');
expect(pack.reloadStylesheets).toHaveBeenCalled();
expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled();
watcher.entities[watcher.entities.length - 1].emitter.emit('did-change')
expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled()
})
watcher.entities[watcher.entities.length - 1].emitter.emit('did-change');
expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled();
});
it('unwatches when a theme is deactivated', async () => {
jasmine.useRealClock()
jasmine.useRealClock();
atom.config.set('core.themes', [])
atom.config.set('core.themes', []);
await conditionPromise(
() => !uiWatcher.watchedThemes['theme-with-multiple-imported-files']
)
})
);
});
it('watches a new theme when it is deactivated', async () => {
jasmine.useRealClock()
jasmine.useRealClock();
atom.config.set('core.themes', [
'theme-with-syntax-variables',
'theme-with-package-file'
])
]);
await conditionPromise(() =>
uiWatcher.watchedThemes.get('theme-with-package-file')
)
);
pack = atom.themes.getActiveThemes()[0]
spyOn(pack, 'reloadStylesheets')
pack = atom.themes.getActiveThemes()[0];
spyOn(pack, 'reloadStylesheets');
expect(pack.name).toBe('theme-with-package-file')
expect(pack.name).toBe('theme-with-package-file');
const watcher = uiWatcher.watchedThemes.get('theme-with-package-file')
watcher.entities[2].emitter.emit('did-change')
expect(pack.reloadStylesheets).toHaveBeenCalled()
})
})
})
const watcher = uiWatcher.watchedThemes.get('theme-with-package-file');
watcher.entities[2].emitter.emit('did-change');
expect(pack.reloadStylesheets).toHaveBeenCalled();
});
});
});

View File

@ -1,57 +1,57 @@
/** @babel */
import { CompositeDisposable } from 'atom'
import { CompositeDisposable } from 'atom';
let reporter
let reporter;
function getReporter () {
function getReporter() {
if (!reporter) {
const Reporter = require('./reporter')
reporter = new Reporter()
const Reporter = require('./reporter');
reporter = new Reporter();
}
return reporter
return reporter;
}
export default {
activate () {
this.subscriptions = new CompositeDisposable()
activate() {
this.subscriptions = new CompositeDisposable();
if (!atom.config.get('exception-reporting.userId')) {
atom.config.set('exception-reporting.userId', require('node-uuid').v4())
atom.config.set('exception-reporting.userId', require('node-uuid').v4());
}
this.subscriptions.add(
atom.onDidThrowError(({ message, url, line, column, originalError }) => {
try {
getReporter().reportUncaughtException(originalError)
getReporter().reportUncaughtException(originalError);
} catch (secondaryException) {
try {
console.error(
'Error reporting uncaught exception',
secondaryException
)
getReporter().reportUncaughtException(secondaryException)
);
getReporter().reportUncaughtException(secondaryException);
} catch (error) {}
}
})
)
);
if (atom.onDidFailAssertion != null) {
this.subscriptions.add(
atom.onDidFailAssertion(error => {
try {
getReporter().reportFailedAssertion(error)
getReporter().reportFailedAssertion(error);
} catch (secondaryException) {
try {
console.error(
'Error reporting assertion failure',
secondaryException
)
getReporter().reportUncaughtException(secondaryException)
);
getReporter().reportUncaughtException(secondaryException);
} catch (error) {}
}
})
)
);
}
}
}
};

View File

@ -1,31 +1,31 @@
/** @babel */
import os from 'os'
import stackTrace from 'stack-trace'
import fs from 'fs-plus'
import path from 'path'
import os from 'os';
import stackTrace from 'stack-trace';
import fs from 'fs-plus';
import path from 'path';
const API_KEY = '7ddca14cb60cbd1cd12d1b252473b076'
const LIB_VERSION = require('../package.json')['version']
const StackTraceCache = new WeakMap()
const API_KEY = '7ddca14cb60cbd1cd12d1b252473b076';
const LIB_VERSION = require('../package.json')['version'];
const StackTraceCache = new WeakMap();
export default class Reporter {
constructor (params = {}) {
this.request = params.request || window.fetch
constructor(params = {}) {
this.request = params.request || window.fetch;
this.alwaysReport = params.hasOwnProperty('alwaysReport')
? params.alwaysReport
: false
: false;
this.reportPreviousErrors = params.hasOwnProperty('reportPreviousErrors')
? params.reportPreviousErrors
: true
: true;
this.resourcePath = this.normalizePath(
params.resourcePath || process.resourcesPath
)
this.reportedErrors = []
this.reportedAssertionFailures = []
);
this.reportedErrors = [];
this.reportedAssertionFailures = [];
}
buildNotificationJSON (error, params) {
buildNotificationJSON(error, params) {
return {
apiKey: API_KEY,
notifier: {
@ -51,18 +51,18 @@ export default class Reporter {
metaData: error.metadata
}
]
}
};
}
buildExceptionJSON (error, projectRoot) {
buildExceptionJSON(error, projectRoot) {
return {
errorClass: error.constructor.name,
message: error.message,
stacktrace: this.buildStackTraceJSON(error, projectRoot)
}
};
}
buildStackTraceJSON (error, projectRoot) {
buildStackTraceJSON(error, projectRoot) {
return this.parseStackTrace(error).map(callSite => {
return {
file: this.scrubPath(callSite.getFileName()),
@ -71,110 +71,110 @@ export default class Reporter {
lineNumber: callSite.getLineNumber(),
columnNumber: callSite.getColumnNumber(),
inProject: !/node_modules/.test(callSite.getFileName())
}
})
};
});
}
normalizePath (pathToNormalize) {
normalizePath(pathToNormalize) {
return pathToNormalize
.replace('file:///', '') // Sometimes it's a uri
.replace(/\\/g, '/') // Unify path separators across Win/macOS/Linux
.replace(/\\/g, '/'); // Unify path separators across Win/macOS/Linux
}
scrubPath (pathToScrub) {
const absolutePath = this.normalizePath(pathToScrub)
scrubPath(pathToScrub) {
const absolutePath = this.normalizePath(pathToScrub);
if (this.isBundledFile(absolutePath)) {
return this.normalizePath(path.relative(this.resourcePath, absolutePath))
return this.normalizePath(path.relative(this.resourcePath, absolutePath));
} else {
return absolutePath
.replace(this.normalizePath(fs.getHomeDirectory()), '~') // Remove users home dir
.replace(/.*(\/packages\/.*)/, '$1') // Remove everything before app.asar or packages
.replace(/.*(\/packages\/.*)/, '$1'); // Remove everything before app.asar or packages
}
}
getDefaultNotificationParams () {
getDefaultNotificationParams() {
return {
userId: atom.config.get('exception-reporting.userId'),
appVersion: atom.getVersion(),
releaseStage: this.getReleaseChannel(atom.getVersion()),
projectRoot: atom.getLoadSettings().resourcePath,
osVersion: `${os.platform()}-${os.arch()}-${os.release()}`
}
};
}
getReleaseChannel (version) {
getReleaseChannel(version) {
return version.indexOf('beta') > -1
? 'beta'
: version.indexOf('dev') > -1
? 'dev'
: 'stable'
: 'stable';
}
performRequest (json) {
performRequest(json) {
this.request.call(null, 'https://notify.bugsnag.com', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(json)
})
});
}
shouldReport (error) {
if (this.alwaysReport) return true // Used in specs
if (atom.config.get('core.telemetryConsent') !== 'limited') return false
if (atom.inDevMode()) return false
shouldReport(error) {
if (this.alwaysReport) return true; // Used in specs
if (atom.config.get('core.telemetryConsent') !== 'limited') return false;
if (atom.inDevMode()) return false;
const topFrame = this.parseStackTrace(error)[0]
const fileName = topFrame ? topFrame.getFileName() : null
const topFrame = this.parseStackTrace(error)[0];
const fileName = topFrame ? topFrame.getFileName() : null;
return (
fileName &&
(this.isBundledFile(fileName) || this.isTeletypeFile(fileName))
)
);
}
parseStackTrace (error) {
let callSites = StackTraceCache.get(error)
parseStackTrace(error) {
let callSites = StackTraceCache.get(error);
if (callSites) {
return callSites
return callSites;
} else {
callSites = stackTrace.parse(error)
StackTraceCache.set(error, callSites)
return callSites
callSites = stackTrace.parse(error);
StackTraceCache.set(error, callSites);
return callSites;
}
}
requestPrivateMetadataConsent (error, message, reportFn) {
let notification, dismissSubscription
requestPrivateMetadataConsent(error, message, reportFn) {
let notification, dismissSubscription;
function reportWithoutPrivateMetadata () {
function reportWithoutPrivateMetadata() {
if (dismissSubscription) {
dismissSubscription.dispose()
dismissSubscription.dispose();
}
delete error.privateMetadata
delete error.privateMetadataDescription
reportFn(error)
delete error.privateMetadata;
delete error.privateMetadataDescription;
reportFn(error);
if (notification) {
notification.dismiss()
notification.dismiss();
}
}
function reportWithPrivateMetadata () {
function reportWithPrivateMetadata() {
if (error.metadata == null) {
error.metadata = {}
error.metadata = {};
}
for (let key in error.privateMetadata) {
let value = error.privateMetadata[key]
error.metadata[key] = value
let value = error.privateMetadata[key];
error.metadata[key] = value;
}
reportWithoutPrivateMetadata()
reportWithoutPrivateMetadata();
}
const name = error.privateMetadataRequestName
const name = error.privateMetadataRequestName;
if (name != null) {
if (localStorage.getItem(`private-metadata-request:${name}`)) {
return reportWithoutPrivateMetadata(error)
return reportWithoutPrivateMetadata(error);
} else {
localStorage.setItem(`private-metadata-request:${name}`, true)
localStorage.setItem(`private-metadata-request:${name}`, true);
}
}
@ -193,51 +193,51 @@ export default class Reporter {
onDidClick: reportWithPrivateMetadata
}
]
})
});
dismissSubscription = notification.onDidDismiss(
reportWithoutPrivateMetadata
)
);
}
addPackageMetadata (error) {
let activePackages = atom.packages.getActivePackages()
const availablePackagePaths = atom.packages.getPackageDirPaths()
addPackageMetadata(error) {
let activePackages = atom.packages.getActivePackages();
const availablePackagePaths = atom.packages.getPackageDirPaths();
if (activePackages.length > 0) {
let userPackages = {}
let bundledPackages = {}
let userPackages = {};
let bundledPackages = {};
for (let pack of atom.packages.getActivePackages()) {
if (availablePackagePaths.includes(path.dirname(pack.path))) {
userPackages[pack.name] = pack.metadata.version
userPackages[pack.name] = pack.metadata.version;
} else {
bundledPackages[pack.name] = pack.metadata.version
bundledPackages[pack.name] = pack.metadata.version;
}
}
if (error.metadata == null) {
error.metadata = {}
error.metadata = {};
}
error.metadata.bundledPackages = bundledPackages
error.metadata.userPackages = userPackages
error.metadata.bundledPackages = bundledPackages;
error.metadata.userPackages = userPackages;
}
}
addPreviousErrorsMetadata (error) {
if (!this.reportPreviousErrors) return
if (!error.metadata) error.metadata = {}
addPreviousErrorsMetadata(error) {
if (!this.reportPreviousErrors) return;
if (!error.metadata) error.metadata = {};
error.metadata.previousErrors = this.reportedErrors.map(
error => error.message
)
);
error.metadata.previousAssertionFailures = this.reportedAssertionFailures.map(
error => error.message
)
);
}
reportUncaughtException (error) {
if (!this.shouldReport(error)) return
reportUncaughtException(error) {
if (!this.shouldReport(error)) return;
this.addPackageMetadata(error)
this.addPreviousErrorsMetadata(error)
this.addPackageMetadata(error);
this.addPreviousErrorsMetadata(error);
if (
error.privateMetadata != null &&
@ -247,21 +247,21 @@ export default class Reporter {
error,
'The Atom team would like to collect the following information to resolve this error:',
error => this.reportUncaughtException(error)
)
return
);
return;
}
let params = this.getDefaultNotificationParams()
params.severity = 'error'
this.performRequest(this.buildNotificationJSON(error, params))
this.reportedErrors.push(error)
let params = this.getDefaultNotificationParams();
params.severity = 'error';
this.performRequest(this.buildNotificationJSON(error, params));
this.reportedErrors.push(error);
}
reportFailedAssertion (error) {
if (!this.shouldReport(error)) return
reportFailedAssertion(error) {
if (!this.shouldReport(error)) return;
this.addPackageMetadata(error)
this.addPreviousErrorsMetadata(error)
this.addPackageMetadata(error);
this.addPreviousErrorsMetadata(error);
if (
error.privateMetadata != null &&
@ -271,32 +271,32 @@ export default class Reporter {
error,
'The Atom team would like to collect some information to resolve an unexpected condition:',
error => this.reportFailedAssertion(error)
)
return
);
return;
}
let params = this.getDefaultNotificationParams()
params.severity = 'warning'
this.performRequest(this.buildNotificationJSON(error, params))
this.reportedAssertionFailures.push(error)
let params = this.getDefaultNotificationParams();
params.severity = 'warning';
this.performRequest(this.buildNotificationJSON(error, params));
this.reportedAssertionFailures.push(error);
}
// Used in specs
setRequestFunction (requestFunction) {
this.request = requestFunction
setRequestFunction(requestFunction) {
this.request = requestFunction;
}
isBundledFile (fileName) {
return this.normalizePath(fileName).indexOf(this.resourcePath) === 0
isBundledFile(fileName) {
return this.normalizePath(fileName).indexOf(this.resourcePath) === 0;
}
isTeletypeFile (fileName) {
const teletypePath = atom.packages.resolvePackagePath('teletype')
isTeletypeFile(fileName) {
const teletypePath = atom.packages.resolvePackagePath('teletype');
return (
teletypePath && this.normalizePath(fileName).indexOf(teletypePath) === 0
)
);
}
}
Reporter.API_KEY = API_KEY
Reporter.LIB_VERSION = LIB_VERSION
Reporter.API_KEY = API_KEY;
Reporter.LIB_VERSION = LIB_VERSION;

View File

@ -1,76 +1,76 @@
const Reporter = require('../lib/reporter')
const semver = require('semver')
const os = require('os')
const path = require('path')
const fs = require('fs-plus')
let osVersion = `${os.platform()}-${os.arch()}-${os.release()}`
const Reporter = require('../lib/reporter');
const semver = require('semver');
const os = require('os');
const path = require('path');
const fs = require('fs-plus');
let osVersion = `${os.platform()}-${os.arch()}-${os.release()}`;
let getReleaseChannel = version => {
return version.indexOf('beta') > -1
? 'beta'
: version.indexOf('dev') > -1
? 'dev'
: 'stable'
}
: 'stable';
};
describe('Reporter', () => {
let reporter,
requests,
initialStackTraceLimit,
initialFsGetHomeDirectory,
mockActivePackages
mockActivePackages;
beforeEach(() => {
reporter = new Reporter({
request: (url, options) => requests.push(Object.assign({ url }, options)),
alwaysReport: true,
reportPreviousErrors: false
})
requests = []
mockActivePackages = []
});
requests = [];
mockActivePackages = [];
spyOn(atom.packages, 'getActivePackages').andCallFake(
() => mockActivePackages
)
);
initialStackTraceLimit = Error.stackTraceLimit
Error.stackTraceLimit = 1
initialStackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 1;
initialFsGetHomeDirectory = fs.getHomeDirectory
})
initialFsGetHomeDirectory = fs.getHomeDirectory;
});
afterEach(() => {
fs.getHomeDirectory = initialFsGetHomeDirectory
Error.stackTraceLimit = initialStackTraceLimit
})
fs.getHomeDirectory = initialFsGetHomeDirectory;
Error.stackTraceLimit = initialStackTraceLimit;
});
describe('.reportUncaughtException(error)', () => {
it('posts errors originated inside Atom Core to BugSnag', () => {
const repositoryRootPath = path.join(__dirname, '..')
const repositoryRootPath = path.join(__dirname, '..');
reporter = new Reporter({
request: (url, options) =>
requests.push(Object.assign({ url }, options)),
alwaysReport: true,
reportPreviousErrors: false,
resourcePath: repositoryRootPath
})
});
let error = new Error()
Error.captureStackTrace(error)
reporter.reportUncaughtException(error)
let error = new Error();
Error.captureStackTrace(error);
reporter.reportUncaughtException(error);
let [lineNumber, columnNumber] = error.stack
.match(/.js:(\d+):(\d+)/)
.slice(1)
.map(s => parseInt(s))
.map(s => parseInt(s));
expect(requests.length).toBe(1)
let [request] = requests
expect(request.method).toBe('POST')
expect(request.url).toBe('https://notify.bugsnag.com')
expect(request.headers.get('Content-Type')).toBe('application/json')
let body = JSON.parse(request.body)
expect(requests.length).toBe(1);
let [request] = requests;
expect(request.method).toBe('POST');
expect(request.url).toBe('https://notify.bugsnag.com');
expect(request.headers.get('Content-Type')).toBe('application/json');
let body = JSON.parse(request.body);
// Delete `inProject` field because tests may fail when run as part of Atom core
// (i.e. when this test file will be located under `node_modules/exception-reporting/spec`)
delete body.events[0].exceptions[0].stacktrace[0].inProject
delete body.events[0].exceptions[0].stacktrace[0].inProject;
expect(body).toEqual({
apiKey: Reporter.API_KEY,
@ -109,29 +109,29 @@ describe('Reporter', () => {
}
}
]
})
})
});
});
it('posts errors originated outside Atom Core to BugSnag', () => {
fs.getHomeDirectory = () => path.join(__dirname, '..', '..')
fs.getHomeDirectory = () => path.join(__dirname, '..', '..');
let error = new Error()
Error.captureStackTrace(error)
reporter.reportUncaughtException(error)
let error = new Error();
Error.captureStackTrace(error);
reporter.reportUncaughtException(error);
let [lineNumber, columnNumber] = error.stack
.match(/.js:(\d+):(\d+)/)
.slice(1)
.map(s => parseInt(s))
.map(s => parseInt(s));
expect(requests.length).toBe(1)
let [request] = requests
expect(request.method).toBe('POST')
expect(request.url).toBe('https://notify.bugsnag.com')
expect(request.headers.get('Content-Type')).toBe('application/json')
let body = JSON.parse(request.body)
expect(requests.length).toBe(1);
let [request] = requests;
expect(request.method).toBe('POST');
expect(request.url).toBe('https://notify.bugsnag.com');
expect(request.headers.get('Content-Type')).toBe('application/json');
let body = JSON.parse(request.body);
// Delete `inProject` field because tests may fail when run as part of Atom core
// (i.e. when this test file will be located under `node_modules/exception-reporting/spec`)
delete body.events[0].exceptions[0].stacktrace[0].inProject
delete body.events[0].exceptions[0].stacktrace[0].inProject;
expect(body).toEqual({
apiKey: Reporter.API_KEY,
@ -170,84 +170,84 @@ describe('Reporter', () => {
}
}
]
})
})
});
});
describe('when the error object has `privateMetadata` and `privateMetadataDescription` fields', () => {
let [error, notification] = []
let [error, notification] = [];
beforeEach(() => {
atom.notifications.clear()
spyOn(atom.notifications, 'addInfo').andCallThrough()
atom.notifications.clear();
spyOn(atom.notifications, 'addInfo').andCallThrough();
error = new Error()
Error.captureStackTrace(error)
error = new Error();
Error.captureStackTrace(error);
error.metadata = { foo: 'bar' }
error.privateMetadata = { baz: 'quux' }
error.privateMetadataDescription = 'The contents of baz'
})
error.metadata = { foo: 'bar' };
error.privateMetadata = { baz: 'quux' };
error.privateMetadataDescription = 'The contents of baz';
});
it('posts a notification asking for consent', () => {
reporter.reportUncaughtException(error)
expect(atom.notifications.addInfo).toHaveBeenCalled()
})
reporter.reportUncaughtException(error);
expect(atom.notifications.addInfo).toHaveBeenCalled();
});
it('submits the error with the private metadata if the user consents', () => {
spyOn(reporter, 'reportUncaughtException').andCallThrough()
reporter.reportUncaughtException(error)
reporter.reportUncaughtException.reset()
spyOn(reporter, 'reportUncaughtException').andCallThrough();
reporter.reportUncaughtException(error);
reporter.reportUncaughtException.reset();
notification = atom.notifications.getNotifications()[0]
notification = atom.notifications.getNotifications()[0];
let notificationOptions = atom.notifications.addInfo.argsForCall[0][1]
expect(notificationOptions.buttons[1].text).toMatch(/Yes/)
let notificationOptions = atom.notifications.addInfo.argsForCall[0][1];
expect(notificationOptions.buttons[1].text).toMatch(/Yes/);
notificationOptions.buttons[1].onDidClick()
expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error)
expect(reporter.reportUncaughtException.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' })
notificationOptions.buttons[1].onDidClick();
expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error);
expect(reporter.reportUncaughtException.callCount).toBe(1);
expect(error.privateMetadata).toBeUndefined();
expect(error.privateMetadataDescription).toBeUndefined();
expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' });
expect(notification.isDismissed()).toBe(true)
})
expect(notification.isDismissed()).toBe(true);
});
it('submits the error without the private metadata if the user does not consent', () => {
spyOn(reporter, 'reportUncaughtException').andCallThrough()
reporter.reportUncaughtException(error)
reporter.reportUncaughtException.reset()
spyOn(reporter, 'reportUncaughtException').andCallThrough();
reporter.reportUncaughtException(error);
reporter.reportUncaughtException.reset();
notification = atom.notifications.getNotifications()[0]
notification = atom.notifications.getNotifications()[0];
let notificationOptions = atom.notifications.addInfo.argsForCall[0][1]
expect(notificationOptions.buttons[0].text).toMatch(/No/)
let notificationOptions = atom.notifications.addInfo.argsForCall[0][1];
expect(notificationOptions.buttons[0].text).toMatch(/No/);
notificationOptions.buttons[0].onDidClick()
expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error)
expect(reporter.reportUncaughtException.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({ foo: 'bar' })
notificationOptions.buttons[0].onDidClick();
expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error);
expect(reporter.reportUncaughtException.callCount).toBe(1);
expect(error.privateMetadata).toBeUndefined();
expect(error.privateMetadataDescription).toBeUndefined();
expect(error.metadata).toEqual({ foo: 'bar' });
expect(notification.isDismissed()).toBe(true)
})
expect(notification.isDismissed()).toBe(true);
});
it('submits the error without the private metadata if the user dismisses the notification', () => {
spyOn(reporter, 'reportUncaughtException').andCallThrough()
reporter.reportUncaughtException(error)
reporter.reportUncaughtException.reset()
spyOn(reporter, 'reportUncaughtException').andCallThrough();
reporter.reportUncaughtException(error);
reporter.reportUncaughtException.reset();
notification = atom.notifications.getNotifications()[0]
notification.dismiss()
notification = atom.notifications.getNotifications()[0];
notification.dismiss();
expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error)
expect(reporter.reportUncaughtException.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({ foo: 'bar' })
})
})
expect(reporter.reportUncaughtException).toHaveBeenCalledWith(error);
expect(reporter.reportUncaughtException.callCount).toBe(1);
expect(error.privateMetadata).toBeUndefined();
expect(error.privateMetadataDescription).toBeUndefined();
expect(error.metadata).toEqual({ foo: 'bar' });
});
});
it('treats packages located in atom.packages.getPackageDirPaths as user packages', () => {
mockActivePackages = [
@ -273,71 +273,71 @@ describe('Reporter', () => {
'/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2',
metadata: { version: '1.2.0' }
}
]
];
const packageDirPaths = ['/Users/user/.atom/packages']
const packageDirPaths = ['/Users/user/.atom/packages'];
spyOn(atom.packages, 'getPackageDirPaths').andReturn(packageDirPaths)
spyOn(atom.packages, 'getPackageDirPaths').andReturn(packageDirPaths);
let error = new Error()
Error.captureStackTrace(error)
reporter.reportUncaughtException(error)
let error = new Error();
Error.captureStackTrace(error);
reporter.reportUncaughtException(error);
expect(error.metadata.userPackages).toEqual({
'user-1': '1.0.0',
'user-2': '1.2.0'
})
});
expect(error.metadata.bundledPackages).toEqual({
'bundled-1': '1.0.0',
'bundled-2': '1.2.0'
})
})
});
});
it('adds previous error messages and assertion failures to the reported metadata', () => {
reporter.reportPreviousErrors = true
reporter.reportPreviousErrors = true;
reporter.reportUncaughtException(new Error('A'))
reporter.reportUncaughtException(new Error('B'))
reporter.reportFailedAssertion(new Error('X'))
reporter.reportFailedAssertion(new Error('Y'))
reporter.reportUncaughtException(new Error('A'));
reporter.reportUncaughtException(new Error('B'));
reporter.reportFailedAssertion(new Error('X'));
reporter.reportFailedAssertion(new Error('Y'));
reporter.reportUncaughtException(new Error('C'))
reporter.reportUncaughtException(new Error('C'));
expect(requests.length).toBe(5)
expect(requests.length).toBe(5);
const lastRequest = requests[requests.length - 1]
const body = JSON.parse(lastRequest.body)
const lastRequest = requests[requests.length - 1];
const body = JSON.parse(lastRequest.body);
console.log(body)
expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B'])
console.log(body);
expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B']);
expect(body.events[0].metaData.previousAssertionFailures).toEqual([
'X',
'Y'
])
})
})
]);
});
});
describe('.reportFailedAssertion(error)', () => {
it('posts warnings to bugsnag', () => {
fs.getHomeDirectory = () => path.join(__dirname, '..', '..')
fs.getHomeDirectory = () => path.join(__dirname, '..', '..');
let error = new Error()
Error.captureStackTrace(error)
reporter.reportFailedAssertion(error)
let error = new Error();
Error.captureStackTrace(error);
reporter.reportFailedAssertion(error);
let [lineNumber, columnNumber] = error.stack
.match(/.js:(\d+):(\d+)/)
.slice(1)
.map(s => parseInt(s))
.map(s => parseInt(s));
expect(requests.length).toBe(1)
let [request] = requests
expect(request.method).toBe('POST')
expect(request.url).toBe('https://notify.bugsnag.com')
expect(request.headers.get('Content-Type')).toBe('application/json')
let body = JSON.parse(request.body)
expect(requests.length).toBe(1);
let [request] = requests;
expect(request.method).toBe('POST');
expect(request.url).toBe('https://notify.bugsnag.com');
expect(request.headers.get('Content-Type')).toBe('application/json');
let body = JSON.parse(request.body);
// Delete `inProject` field because tests may fail when run as part of Atom core
// (i.e. when this test file will be located under `node_modules/exception-reporting/spec`)
delete body.events[0].exceptions[0].stacktrace[0].inProject
delete body.events[0].exceptions[0].stacktrace[0].inProject;
expect(body).toEqual({
apiKey: Reporter.API_KEY,
@ -376,112 +376,112 @@ describe('Reporter', () => {
}
}
]
})
})
});
});
describe('when the error object has `privateMetadata` and `privateMetadataDescription` fields', () => {
let [error, notification] = []
let [error, notification] = [];
beforeEach(() => {
atom.notifications.clear()
spyOn(atom.notifications, 'addInfo').andCallThrough()
atom.notifications.clear();
spyOn(atom.notifications, 'addInfo').andCallThrough();
error = new Error()
Error.captureStackTrace(error)
error = new Error();
Error.captureStackTrace(error);
error.metadata = { foo: 'bar' }
error.privateMetadata = { baz: 'quux' }
error.privateMetadataDescription = 'The contents of baz'
})
error.metadata = { foo: 'bar' };
error.privateMetadata = { baz: 'quux' };
error.privateMetadataDescription = 'The contents of baz';
});
it('posts a notification asking for consent', () => {
reporter.reportFailedAssertion(error)
expect(atom.notifications.addInfo).toHaveBeenCalled()
})
reporter.reportFailedAssertion(error);
expect(atom.notifications.addInfo).toHaveBeenCalled();
});
it('submits the error with the private metadata if the user consents', () => {
spyOn(reporter, 'reportFailedAssertion').andCallThrough()
reporter.reportFailedAssertion(error)
reporter.reportFailedAssertion.reset()
spyOn(reporter, 'reportFailedAssertion').andCallThrough();
reporter.reportFailedAssertion(error);
reporter.reportFailedAssertion.reset();
notification = atom.notifications.getNotifications()[0]
notification = atom.notifications.getNotifications()[0];
let notificationOptions = atom.notifications.addInfo.argsForCall[0][1]
expect(notificationOptions.buttons[1].text).toMatch(/Yes/)
let notificationOptions = atom.notifications.addInfo.argsForCall[0][1];
expect(notificationOptions.buttons[1].text).toMatch(/Yes/);
notificationOptions.buttons[1].onDidClick()
expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error)
expect(reporter.reportFailedAssertion.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' })
notificationOptions.buttons[1].onDidClick();
expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error);
expect(reporter.reportFailedAssertion.callCount).toBe(1);
expect(error.privateMetadata).toBeUndefined();
expect(error.privateMetadataDescription).toBeUndefined();
expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' });
expect(notification.isDismissed()).toBe(true)
})
expect(notification.isDismissed()).toBe(true);
});
it('submits the error without the private metadata if the user does not consent', () => {
spyOn(reporter, 'reportFailedAssertion').andCallThrough()
reporter.reportFailedAssertion(error)
reporter.reportFailedAssertion.reset()
spyOn(reporter, 'reportFailedAssertion').andCallThrough();
reporter.reportFailedAssertion(error);
reporter.reportFailedAssertion.reset();
notification = atom.notifications.getNotifications()[0]
notification = atom.notifications.getNotifications()[0];
let notificationOptions = atom.notifications.addInfo.argsForCall[0][1]
expect(notificationOptions.buttons[0].text).toMatch(/No/)
let notificationOptions = atom.notifications.addInfo.argsForCall[0][1];
expect(notificationOptions.buttons[0].text).toMatch(/No/);
notificationOptions.buttons[0].onDidClick()
expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error)
expect(reporter.reportFailedAssertion.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({ foo: 'bar' })
notificationOptions.buttons[0].onDidClick();
expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error);
expect(reporter.reportFailedAssertion.callCount).toBe(1);
expect(error.privateMetadata).toBeUndefined();
expect(error.privateMetadataDescription).toBeUndefined();
expect(error.metadata).toEqual({ foo: 'bar' });
expect(notification.isDismissed()).toBe(true)
})
expect(notification.isDismissed()).toBe(true);
});
it('submits the error without the private metadata if the user dismisses the notification', () => {
spyOn(reporter, 'reportFailedAssertion').andCallThrough()
reporter.reportFailedAssertion(error)
reporter.reportFailedAssertion.reset()
spyOn(reporter, 'reportFailedAssertion').andCallThrough();
reporter.reportFailedAssertion(error);
reporter.reportFailedAssertion.reset();
notification = atom.notifications.getNotifications()[0]
notification.dismiss()
notification = atom.notifications.getNotifications()[0];
notification.dismiss();
expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error)
expect(reporter.reportFailedAssertion.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({ foo: 'bar' })
})
expect(reporter.reportFailedAssertion).toHaveBeenCalledWith(error);
expect(reporter.reportFailedAssertion.callCount).toBe(1);
expect(error.privateMetadata).toBeUndefined();
expect(error.privateMetadataDescription).toBeUndefined();
expect(error.metadata).toEqual({ foo: 'bar' });
});
it("only notifies the user once for a given 'privateMetadataRequestName'", () => {
let fakeStorage = {}
let fakeStorage = {};
spyOn(global.localStorage, 'setItem').andCallFake(
(key, value) => (fakeStorage[key] = value)
)
);
spyOn(global.localStorage, 'getItem').andCallFake(
key => fakeStorage[key]
)
);
error.privateMetadataRequestName = 'foo'
error.privateMetadataRequestName = 'foo';
reporter.reportFailedAssertion(error)
expect(atom.notifications.addInfo).toHaveBeenCalled()
atom.notifications.addInfo.reset()
reporter.reportFailedAssertion(error);
expect(atom.notifications.addInfo).toHaveBeenCalled();
atom.notifications.addInfo.reset();
reporter.reportFailedAssertion(error)
expect(atom.notifications.addInfo).not.toHaveBeenCalled()
reporter.reportFailedAssertion(error);
expect(atom.notifications.addInfo).not.toHaveBeenCalled();
let error2 = new Error()
Error.captureStackTrace(error2)
error2.privateMetadataDescription = 'Something about you'
error2.privateMetadata = { baz: 'quux' }
error2.privateMetadataRequestName = 'bar'
let error2 = new Error();
Error.captureStackTrace(error2);
error2.privateMetadataDescription = 'Something about you';
error2.privateMetadata = { baz: 'quux' };
error2.privateMetadataRequestName = 'bar';
reporter.reportFailedAssertion(error2)
expect(atom.notifications.addInfo).toHaveBeenCalled()
})
})
reporter.reportFailedAssertion(error2);
expect(atom.notifications.addInfo).toHaveBeenCalled();
});
});
it('treats packages located in atom.packages.getPackageDirPaths as user packages', () => {
mockActivePackages = [
@ -507,46 +507,46 @@ describe('Reporter', () => {
'/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2',
metadata: { version: '1.2.0' }
}
]
];
const packageDirPaths = ['/Users/user/.atom/packages']
const packageDirPaths = ['/Users/user/.atom/packages'];
spyOn(atom.packages, 'getPackageDirPaths').andReturn(packageDirPaths)
spyOn(atom.packages, 'getPackageDirPaths').andReturn(packageDirPaths);
let error = new Error()
Error.captureStackTrace(error)
reporter.reportFailedAssertion(error)
let error = new Error();
Error.captureStackTrace(error);
reporter.reportFailedAssertion(error);
expect(error.metadata.userPackages).toEqual({
'user-1': '1.0.0',
'user-2': '1.2.0'
})
});
expect(error.metadata.bundledPackages).toEqual({
'bundled-1': '1.0.0',
'bundled-2': '1.2.0'
})
})
});
});
it('adds previous error messages and assertion failures to the reported metadata', () => {
reporter.reportPreviousErrors = true
reporter.reportPreviousErrors = true;
reporter.reportUncaughtException(new Error('A'))
reporter.reportUncaughtException(new Error('B'))
reporter.reportFailedAssertion(new Error('X'))
reporter.reportFailedAssertion(new Error('Y'))
reporter.reportUncaughtException(new Error('A'));
reporter.reportUncaughtException(new Error('B'));
reporter.reportFailedAssertion(new Error('X'));
reporter.reportFailedAssertion(new Error('Y'));
reporter.reportFailedAssertion(new Error('C'))
reporter.reportFailedAssertion(new Error('C'));
expect(requests.length).toBe(5)
expect(requests.length).toBe(5);
const lastRequest = requests[requests.length - 1]
const body = JSON.parse(lastRequest.body)
const lastRequest = requests[requests.length - 1];
const body = JSON.parse(lastRequest.body);
expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B'])
expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B']);
expect(body.events[0].metaData.previousAssertionFailures).toEqual([
'X',
'Y'
])
})
})
})
]);
});
});
});

View File

@ -1,89 +1,89 @@
const SelectListView = require('atom-select-list')
const { repositoryForPath } = require('./helpers')
const SelectListView = require('atom-select-list');
const { repositoryForPath } = require('./helpers');
module.exports = class DiffListView {
constructor () {
constructor() {
this.selectListView = new SelectListView({
emptyMessage: 'No diffs in file',
items: [],
filterKeyForItem: diff => diff.lineText,
elementForItem: diff => {
const li = document.createElement('li')
li.classList.add('two-lines')
const li = document.createElement('li');
li.classList.add('two-lines');
const primaryLine = document.createElement('div')
primaryLine.classList.add('primary-line')
primaryLine.textContent = diff.lineText
li.appendChild(primaryLine)
const primaryLine = document.createElement('div');
primaryLine.classList.add('primary-line');
primaryLine.textContent = diff.lineText;
li.appendChild(primaryLine);
const secondaryLine = document.createElement('div')
secondaryLine.classList.add('secondary-line')
const secondaryLine = document.createElement('div');
secondaryLine.classList.add('secondary-line');
secondaryLine.textContent = `-${diff.oldStart},${diff.oldLines} +${
diff.newStart
},${diff.newLines}`
li.appendChild(secondaryLine)
},${diff.newLines}`;
li.appendChild(secondaryLine);
return li
return li;
},
didConfirmSelection: diff => {
this.cancel()
const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart
this.cancel();
const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart;
this.editor.setCursorBufferPosition([bufferRow, 0], {
autoscroll: true
})
this.editor.moveToFirstCharacterOfLine()
});
this.editor.moveToFirstCharacterOfLine();
},
didCancelSelection: () => {
this.cancel()
this.cancel();
}
})
this.selectListView.element.classList.add('diff-list-view')
});
this.selectListView.element.classList.add('diff-list-view');
this.panel = atom.workspace.addModalPanel({
item: this.selectListView,
visible: false
})
});
}
attach () {
this.previouslyFocusedElement = document.activeElement
this.selectListView.reset()
this.panel.show()
this.selectListView.focus()
attach() {
this.previouslyFocusedElement = document.activeElement;
this.selectListView.reset();
this.panel.show();
this.selectListView.focus();
}
cancel () {
this.panel.hide()
cancel() {
this.panel.hide();
if (this.previouslyFocusedElement) {
this.previouslyFocusedElement.focus()
this.previouslyFocusedElement = null
this.previouslyFocusedElement.focus();
this.previouslyFocusedElement = null;
}
}
destroy () {
this.cancel()
this.panel.destroy()
return this.selectListView.destroy()
destroy() {
this.cancel();
this.panel.destroy();
return this.selectListView.destroy();
}
async toggle () {
const editor = atom.workspace.getActiveTextEditor()
async toggle() {
const editor = atom.workspace.getActiveTextEditor();
if (this.panel.isVisible()) {
this.cancel()
this.cancel();
} else if (editor) {
this.editor = editor
const repository = repositoryForPath(this.editor.getPath())
this.editor = editor;
const repository = repositoryForPath(this.editor.getPath());
let diffs = repository
? repository.getLineDiffs(this.editor.getPath(), this.editor.getText())
: []
if (!diffs) diffs = []
: [];
if (!diffs) diffs = [];
for (let diff of diffs) {
const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart
const lineText = this.editor.lineTextForBufferRow(bufferRow)
diff.lineText = lineText ? lineText.trim() : ''
const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart;
const lineText = this.editor.lineTextForBufferRow(bufferRow);
diff.lineText = lineText ? lineText.trim() : '';
}
await this.selectListView.update({ items: diffs })
this.attach()
await this.selectListView.update({ items: diffs });
this.attach();
}
}
}
};

View File

@ -1,21 +1,21 @@
const { CompositeDisposable } = require('atom')
const { repositoryForPath } = require('./helpers')
const { CompositeDisposable } = require('atom');
const { repositoryForPath } = require('./helpers');
const MAX_BUFFER_LENGTH_TO_DIFF = 2 * 1024 * 1024
const MAX_BUFFER_LENGTH_TO_DIFF = 2 * 1024 * 1024;
module.exports = class GitDiffView {
constructor (editor) {
this.updateDiffs = this.updateDiffs.bind(this)
this.editor = editor
this.subscriptions = new CompositeDisposable()
this.decorations = {}
this.markers = []
constructor(editor) {
this.updateDiffs = this.updateDiffs.bind(this);
this.editor = editor;
this.subscriptions = new CompositeDisposable();
this.decorations = {};
this.markers = [];
}
start () {
const editorElement = this.editor.getElement()
start() {
const editorElement = this.editor.getElement();
this.subscribeToRepository()
this.subscribeToRepository();
this.subscriptions.add(
this.editor.onDidStopChanging(this.updateDiffs),
@ -35,29 +35,29 @@ module.exports = class GitDiffView {
),
editorElement.onDidAttach(() => this.updateIconDecoration()),
this.editor.onDidDestroy(() => {
this.cancelUpdate()
this.removeDecorations()
this.subscriptions.dispose()
this.cancelUpdate();
this.removeDecorations();
this.subscriptions.dispose();
})
)
);
this.updateIconDecoration()
this.scheduleUpdate()
this.updateIconDecoration();
this.scheduleUpdate();
}
moveToNextDiff () {
const cursorLineNumber = this.editor.getCursorBufferPosition().row + 1
let nextDiffLineNumber = null
let firstDiffLineNumber = null
moveToNextDiff() {
const cursorLineNumber = this.editor.getCursorBufferPosition().row + 1;
let nextDiffLineNumber = null;
let firstDiffLineNumber = null;
if (this.diffs) {
for (const { newStart } of this.diffs) {
if (newStart > cursorLineNumber) {
if (nextDiffLineNumber == null) nextDiffLineNumber = newStart - 1
nextDiffLineNumber = Math.min(newStart - 1, nextDiffLineNumber)
if (nextDiffLineNumber == null) nextDiffLineNumber = newStart - 1;
nextDiffLineNumber = Math.min(newStart - 1, nextDiffLineNumber);
}
if (firstDiffLineNumber == null) firstDiffLineNumber = newStart - 1
firstDiffLineNumber = Math.min(newStart - 1, firstDiffLineNumber)
if (firstDiffLineNumber == null) firstDiffLineNumber = newStart - 1;
firstDiffLineNumber = Math.min(newStart - 1, firstDiffLineNumber);
}
}
@ -66,39 +66,39 @@ module.exports = class GitDiffView {
atom.config.get('git-diff.wrapAroundOnMoveToDiff') &&
nextDiffLineNumber == null
) {
nextDiffLineNumber = firstDiffLineNumber
nextDiffLineNumber = firstDiffLineNumber;
}
this.moveToLineNumber(nextDiffLineNumber)
this.moveToLineNumber(nextDiffLineNumber);
}
updateIconDecoration () {
const gutter = this.editor.getElement().querySelector('.gutter')
updateIconDecoration() {
const gutter = this.editor.getElement().querySelector('.gutter');
if (gutter) {
if (
atom.config.get('editor.showLineNumbers') &&
atom.config.get('git-diff.showIconsInEditorGutter')
) {
gutter.classList.add('git-diff-icon')
gutter.classList.add('git-diff-icon');
} else {
gutter.classList.remove('git-diff-icon')
gutter.classList.remove('git-diff-icon');
}
}
}
moveToPreviousDiff () {
const cursorLineNumber = this.editor.getCursorBufferPosition().row + 1
let previousDiffLineNumber = -1
let lastDiffLineNumber = -1
moveToPreviousDiff() {
const cursorLineNumber = this.editor.getCursorBufferPosition().row + 1;
let previousDiffLineNumber = -1;
let lastDiffLineNumber = -1;
if (this.diffs) {
for (const { newStart } of this.diffs) {
if (newStart < cursorLineNumber) {
previousDiffLineNumber = Math.max(
newStart - 1,
previousDiffLineNumber
)
);
}
lastDiffLineNumber = Math.max(newStart - 1, lastDiffLineNumber)
lastDiffLineNumber = Math.max(newStart - 1, lastDiffLineNumber);
}
}
@ -107,87 +107,87 @@ module.exports = class GitDiffView {
atom.config.get('git-diff.wrapAroundOnMoveToDiff') &&
previousDiffLineNumber === -1
) {
previousDiffLineNumber = lastDiffLineNumber
previousDiffLineNumber = lastDiffLineNumber;
}
this.moveToLineNumber(previousDiffLineNumber)
this.moveToLineNumber(previousDiffLineNumber);
}
moveToLineNumber (lineNumber) {
moveToLineNumber(lineNumber) {
if (lineNumber != null && lineNumber >= 0) {
this.editor.setCursorBufferPosition([lineNumber, 0])
this.editor.moveToFirstCharacterOfLine()
this.editor.setCursorBufferPosition([lineNumber, 0]);
this.editor.moveToFirstCharacterOfLine();
}
}
subscribeToRepository () {
this.repository = repositoryForPath(this.editor.getPath())
subscribeToRepository() {
this.repository = repositoryForPath(this.editor.getPath());
if (this.repository) {
this.subscriptions.add(
this.repository.onDidChangeStatuses(() => {
this.scheduleUpdate()
this.scheduleUpdate();
})
)
);
this.subscriptions.add(
this.repository.onDidChangeStatus(changedPath => {
if (changedPath === this.editor.getPath()) this.scheduleUpdate()
if (changedPath === this.editor.getPath()) this.scheduleUpdate();
})
)
);
}
}
cancelUpdate () {
clearImmediate(this.immediateId)
cancelUpdate() {
clearImmediate(this.immediateId);
}
scheduleUpdate () {
this.cancelUpdate()
this.immediateId = setImmediate(this.updateDiffs)
scheduleUpdate() {
this.cancelUpdate();
this.immediateId = setImmediate(this.updateDiffs);
}
updateDiffs () {
if (this.editor.isDestroyed()) return
this.removeDecorations()
const path = this.editor && this.editor.getPath()
updateDiffs() {
if (this.editor.isDestroyed()) return;
this.removeDecorations();
const path = this.editor && this.editor.getPath();
if (
path &&
this.editor.getBuffer().getLength() < MAX_BUFFER_LENGTH_TO_DIFF
) {
this.diffs =
this.repository &&
this.repository.getLineDiffs(path, this.editor.getText())
if (this.diffs) this.addDecorations(this.diffs)
this.repository.getLineDiffs(path, this.editor.getText());
if (this.diffs) this.addDecorations(this.diffs);
}
}
addDecorations (diffs) {
addDecorations(diffs) {
for (const { newStart, oldLines, newLines } of diffs) {
const startRow = newStart - 1
const endRow = newStart + newLines - 1
const startRow = newStart - 1;
const endRow = newStart + newLines - 1;
if (oldLines === 0 && newLines > 0) {
this.markRange(startRow, endRow, 'git-line-added')
this.markRange(startRow, endRow, 'git-line-added');
} else if (newLines === 0 && oldLines > 0) {
if (startRow < 0) {
this.markRange(0, 0, 'git-previous-line-removed')
this.markRange(0, 0, 'git-previous-line-removed');
} else {
this.markRange(startRow, startRow, 'git-line-removed')
this.markRange(startRow, startRow, 'git-line-removed');
}
} else {
this.markRange(startRow, endRow, 'git-line-modified')
this.markRange(startRow, endRow, 'git-line-modified');
}
}
}
removeDecorations () {
for (let marker of this.markers) marker.destroy()
this.markers = []
removeDecorations() {
for (let marker of this.markers) marker.destroy();
this.markers = [];
}
markRange (startRow, endRow, klass) {
markRange(startRow, endRow, klass) {
const marker = this.editor.markBufferRange([[startRow, 0], [endRow, 0]], {
invalidate: 'never'
})
this.editor.decorateMarker(marker, { type: 'line-number', class: klass })
this.markers.push(marker)
});
this.editor.decorateMarker(marker, { type: 'line-number', class: klass });
this.markers.push(marker);
}
}
};

View File

@ -1,11 +1,11 @@
exports.repositoryForPath = function (goalPath) {
const directories = atom.project.getDirectories()
const repositories = atom.project.getRepositories()
exports.repositoryForPath = function(goalPath) {
const directories = atom.project.getDirectories();
const repositories = atom.project.getRepositories();
for (let i = 0; i < directories.length; i++) {
const directory = directories[i]
const directory = directories[i];
if (goalPath === directory.getPath() || directory.contains(goalPath)) {
return repositories[i]
return repositories[i];
}
}
return null
}
return null;
};

View File

@ -1,32 +1,32 @@
const GitDiffView = require('./git-diff-view')
const DiffListView = require('./diff-list-view')
const GitDiffView = require('./git-diff-view');
const DiffListView = require('./diff-list-view');
let diffListView = null
let diffListView = null;
module.exports = {
activate () {
const watchedEditors = new WeakSet()
activate() {
const watchedEditors = new WeakSet();
atom.workspace.observeTextEditors(editor => {
if (watchedEditors.has(editor)) return
if (watchedEditors.has(editor)) return;
new GitDiffView(editor).start()
new GitDiffView(editor).start();
atom.commands.add(
atom.views.getView(editor),
'git-diff:toggle-diff-list',
() => {
if (diffListView == null) diffListView = new DiffListView()
diffListView.toggle()
if (diffListView == null) diffListView = new DiffListView();
diffListView.toggle();
}
)
);
watchedEditors.add(editor)
editor.onDidDestroy(() => watchedEditors.delete(editor))
})
watchedEditors.add(editor);
editor.onDidDestroy(() => watchedEditors.delete(editor));
});
},
deactivate () {
if (diffListView) diffListView.destroy()
diffListView = null
deactivate() {
if (diffListView) diffListView.destroy();
diffListView = null;
}
}
};

View File

@ -1,49 +1,51 @@
const path = require('path')
const fs = require('fs-plus')
const temp = require('temp')
const path = require('path');
const fs = require('fs-plus');
const temp = require('temp');
describe('git-diff:toggle-diff-list', () => {
let diffListView, editor
let diffListView, editor;
beforeEach(() => {
const projectPath = temp.mkdirSync('git-diff-spec-')
fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath)
const projectPath = temp.mkdirSync('git-diff-spec-');
fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath);
fs.moveSync(
path.join(projectPath, 'git.git'),
path.join(projectPath, '.git')
)
atom.project.setPaths([projectPath])
);
atom.project.setPaths([projectPath]);
jasmine.attachToDOM(atom.workspace.getElement())
jasmine.attachToDOM(atom.workspace.getElement());
waitsForPromise(() => atom.packages.activatePackage('git-diff'))
waitsForPromise(() => atom.packages.activatePackage('git-diff'));
waitsForPromise(() => atom.workspace.open('sample.js'))
waitsForPromise(() => atom.workspace.open('sample.js'));
runs(() => {
editor = atom.workspace.getActiveTextEditor()
editor.setCursorBufferPosition([8, 30])
editor.insertText('a')
atom.commands.dispatch(editor.getElement(), 'git-diff:toggle-diff-list')
})
editor = atom.workspace.getActiveTextEditor();
editor.setCursorBufferPosition([8, 30]);
editor.insertText('a');
atom.commands.dispatch(editor.getElement(), 'git-diff:toggle-diff-list');
});
waitsFor(() => {
diffListView = document.querySelector('.diff-list-view')
return diffListView && diffListView.querySelectorAll('li').length > 0
})
})
diffListView = document.querySelector('.diff-list-view');
return diffListView && diffListView.querySelectorAll('li').length > 0;
});
});
it('shows a list of all diff hunks', () => {
diffListView = document.querySelector('.diff-list-view ol')
expect(diffListView.textContent).toBe('while (items.length > 0) {a-9,1 +9,1')
})
diffListView = document.querySelector('.diff-list-view ol');
expect(diffListView.textContent).toBe(
'while (items.length > 0) {a-9,1 +9,1'
);
});
it('moves the cursor to the selected hunk', () => {
editor.setCursorBufferPosition([0, 0])
editor.setCursorBufferPosition([0, 0]);
atom.commands.dispatch(
document.querySelector('.diff-list-view'),
'core:confirm'
)
expect(editor.getCursorBufferPosition()).toEqual([8, 4])
})
})
);
expect(editor.getCursorBufferPosition()).toEqual([8, 4]);
});
});

View File

@ -1,244 +1,246 @@
const path = require('path')
const fs = require('fs-plus')
const temp = require('temp')
const path = require('path');
const fs = require('fs-plus');
const temp = require('temp');
describe('GitDiff package', () => {
let editor, editorElement, projectPath
let editor, editorElement, projectPath;
beforeEach(() => {
spyOn(window, 'setImmediate').andCallFake(fn => fn())
spyOn(window, 'setImmediate').andCallFake(fn => fn());
projectPath = temp.mkdirSync('git-diff-spec-')
const otherPath = temp.mkdirSync('some-other-path-')
projectPath = temp.mkdirSync('git-diff-spec-');
const otherPath = temp.mkdirSync('some-other-path-');
fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath)
fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath);
fs.moveSync(
path.join(projectPath, 'git.git'),
path.join(projectPath, '.git')
)
atom.project.setPaths([otherPath, projectPath])
);
atom.project.setPaths([otherPath, projectPath]);
jasmine.attachToDOM(atom.workspace.getElement())
jasmine.attachToDOM(atom.workspace.getElement());
waitsForPromise(() =>
atom.workspace.open(path.join(projectPath, 'sample.js'))
)
);
runs(() => {
editor = atom.workspace.getActiveTextEditor()
editorElement = editor.getElement()
})
editor = atom.workspace.getActiveTextEditor();
editorElement = editor.getElement();
});
waitsForPromise(() => atom.packages.activatePackage('git-diff'))
})
waitsForPromise(() => atom.packages.activatePackage('git-diff'));
});
describe('when the editor has modified lines', () => {
it('highlights the modified lines', () => {
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
0
)
editor.insertText('a')
advanceClock(editor.getBuffer().stoppedChangingDelay)
);
editor.insertText('a');
advanceClock(editor.getBuffer().stoppedChangingDelay);
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
1
)
);
expect(editorElement.querySelector('.git-line-modified')).toHaveData(
'buffer-row',
0
)
})
})
);
});
});
describe('when the editor has added lines', () => {
it('highlights the added lines', () => {
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0)
editor.moveToEndOfLine()
editor.insertNewline()
editor.insertText('a')
advanceClock(editor.getBuffer().stoppedChangingDelay)
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(1)
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0);
editor.moveToEndOfLine();
editor.insertNewline();
editor.insertText('a');
advanceClock(editor.getBuffer().stoppedChangingDelay);
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(1);
expect(editorElement.querySelector('.git-line-added')).toHaveData(
'buffer-row',
1
)
})
})
);
});
});
describe('when the editor has removed lines', () => {
it('highlights the line preceeding the deleted lines', () => {
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0)
editor.setCursorBufferPosition([5])
editor.deleteLine()
advanceClock(editor.getBuffer().stoppedChangingDelay)
expect(editorElement.querySelectorAll('.git-line-removed').length).toBe(1)
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0);
editor.setCursorBufferPosition([5]);
editor.deleteLine();
advanceClock(editor.getBuffer().stoppedChangingDelay);
expect(editorElement.querySelectorAll('.git-line-removed').length).toBe(
1
);
expect(editorElement.querySelector('.git-line-removed')).toHaveData(
'buffer-row',
4
)
})
})
);
});
});
describe('when the editor has removed the first line', () => {
it('highlights the line preceeding the deleted lines', () => {
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0)
editor.setCursorBufferPosition([0, 0])
editor.deleteLine()
advanceClock(editor.getBuffer().stoppedChangingDelay)
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(0);
editor.setCursorBufferPosition([0, 0]);
editor.deleteLine();
advanceClock(editor.getBuffer().stoppedChangingDelay);
expect(
editorElement.querySelectorAll('.git-previous-line-removed').length
).toBe(1)
).toBe(1);
expect(
editorElement.querySelector('.git-previous-line-removed')
).toHaveData('buffer-row', 0)
})
})
).toHaveData('buffer-row', 0);
});
});
describe('when a modified line is restored to the HEAD version contents', () => {
it('removes the diff highlight', () => {
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
0
)
editor.insertText('a')
advanceClock(editor.getBuffer().stoppedChangingDelay)
);
editor.insertText('a');
advanceClock(editor.getBuffer().stoppedChangingDelay);
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
1
)
editor.backspace()
advanceClock(editor.getBuffer().stoppedChangingDelay)
);
editor.backspace();
advanceClock(editor.getBuffer().stoppedChangingDelay);
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
0
)
})
})
);
});
});
describe('when a modified file is opened', () => {
it('highlights the changed lines', () => {
fs.writeFileSync(
path.join(projectPath, 'sample.txt'),
'Some different text.'
)
let nextTick = false
);
let nextTick = false;
waitsForPromise(() =>
atom.workspace.open(path.join(projectPath, 'sample.txt'))
)
);
runs(() => {
editorElement = atom.workspace.getActiveTextEditor().getElement()
})
editorElement = atom.workspace.getActiveTextEditor().getElement();
});
setImmediate(() => {
nextTick = true
})
nextTick = true;
});
waitsFor(() => nextTick)
waitsFor(() => nextTick);
runs(() => {
expect(
editorElement.querySelectorAll('.git-line-modified').length
).toBe(1)
).toBe(1);
expect(editorElement.querySelector('.git-line-modified')).toHaveData(
'buffer-row',
0
)
})
})
})
);
});
});
});
describe('when the project paths change', () => {
it("doesn't try to use the destroyed git repository", () => {
editor.deleteLine()
atom.project.setPaths([temp.mkdirSync('no-repository')])
advanceClock(editor.getBuffer().stoppedChangingDelay)
})
})
editor.deleteLine();
atom.project.setPaths([temp.mkdirSync('no-repository')]);
advanceClock(editor.getBuffer().stoppedChangingDelay);
});
});
describe('move-to-next-diff/move-to-previous-diff events', () => {
it('moves the cursor to first character of the next/previous diff line', () => {
editor.insertText('a')
editor.setCursorBufferPosition([5])
editor.deleteLine()
advanceClock(editor.getBuffer().stoppedChangingDelay)
editor.insertText('a');
editor.setCursorBufferPosition([5]);
editor.deleteLine();
advanceClock(editor.getBuffer().stoppedChangingDelay);
editor.setCursorBufferPosition([0])
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff')
expect(editor.getCursorBufferPosition()).toEqual([4, 4])
editor.setCursorBufferPosition([0]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff');
expect(editor.getCursorBufferPosition()).toEqual([4, 4]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff')
expect(editor.getCursorBufferPosition()).toEqual([0, 0])
})
atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff');
expect(editor.getCursorBufferPosition()).toEqual([0, 0]);
});
it('wraps around to the first/last diff in the file', () => {
editor.insertText('a')
editor.setCursorBufferPosition([5])
editor.deleteLine()
advanceClock(editor.getBuffer().stoppedChangingDelay)
editor.insertText('a');
editor.setCursorBufferPosition([5]);
editor.deleteLine();
advanceClock(editor.getBuffer().stoppedChangingDelay);
editor.setCursorBufferPosition([0])
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff')
expect(editor.getCursorBufferPosition()).toEqual([4, 4])
editor.setCursorBufferPosition([0]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff');
expect(editor.getCursorBufferPosition()).toEqual([4, 4]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff')
expect(editor.getCursorBufferPosition()).toEqual([0, 0])
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff');
expect(editor.getCursorBufferPosition()).toEqual([0, 0]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff')
expect(editor.getCursorBufferPosition()).toEqual([4, 4])
})
atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff');
expect(editor.getCursorBufferPosition()).toEqual([4, 4]);
});
describe('when the wrapAroundOnMoveToDiff config option is false', () => {
beforeEach(() =>
atom.config.set('git-diff.wrapAroundOnMoveToDiff', false)
)
);
it('does not wraps around to the first/last diff in the file', () => {
editor.insertText('a')
editor.setCursorBufferPosition([5])
editor.deleteLine()
advanceClock(editor.getBuffer().stoppedChangingDelay)
editor.insertText('a');
editor.setCursorBufferPosition([5]);
editor.deleteLine();
advanceClock(editor.getBuffer().stoppedChangingDelay);
editor.setCursorBufferPosition([0])
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff')
expect(editor.getCursorBufferPosition()).toEqual([4, 4])
editor.setCursorBufferPosition([0]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff');
expect(editor.getCursorBufferPosition()).toEqual([4, 4]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff')
expect(editor.getCursorBufferPosition()).toEqual([4, 4])
atom.commands.dispatch(editorElement, 'git-diff:move-to-next-diff');
expect(editor.getCursorBufferPosition()).toEqual([4, 4]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff')
expect(editor.getCursorBufferPosition()).toEqual([0, 0])
atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff');
expect(editor.getCursorBufferPosition()).toEqual([0, 0]);
atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff')
expect(editor.getCursorBufferPosition()).toEqual([0, 0])
})
})
})
atom.commands.dispatch(editorElement, 'git-diff:move-to-previous-diff');
expect(editor.getCursorBufferPosition()).toEqual([0, 0]);
});
});
});
describe('when the showIconsInEditorGutter config option is true', () => {
beforeEach(() => {
atom.config.set('git-diff.showIconsInEditorGutter', true)
})
atom.config.set('git-diff.showIconsInEditorGutter', true);
});
it('the gutter has a git-diff-icon class', () =>
expect(editorElement.querySelector('.gutter')).toHaveClass(
'git-diff-icon'
))
));
it('keeps the git-diff-icon class when editor.showLineNumbers is toggled', () => {
atom.config.set('editor.showLineNumbers', false)
atom.config.set('editor.showLineNumbers', false);
expect(editorElement.querySelector('.gutter')).not.toHaveClass(
'git-diff-icon'
)
);
atom.config.set('editor.showLineNumbers', true)
atom.config.set('editor.showLineNumbers', true);
expect(editorElement.querySelector('.gutter')).toHaveClass(
'git-diff-icon'
)
})
);
});
it('removes the git-diff-icon class when the showIconsInEditorGutter config option set to false', () => {
atom.config.set('git-diff.showIconsInEditorGutter', false)
atom.config.set('git-diff.showIconsInEditorGutter', false);
expect(editorElement.querySelector('.gutter')).not.toHaveClass(
'git-diff-icon'
)
})
})
})
);
});
});
});

View File

@ -1,111 +1,111 @@
'use babel'
'use babel';
import { Point, TextEditor } from 'atom'
import { Point, TextEditor } from 'atom';
class GoToLineView {
constructor () {
this.miniEditor = new TextEditor({ mini: true })
this.miniEditor.element.addEventListener('blur', this.close.bind(this))
constructor() {
this.miniEditor = new TextEditor({ mini: true });
this.miniEditor.element.addEventListener('blur', this.close.bind(this));
this.message = document.createElement('div')
this.message.classList.add('message')
this.message = document.createElement('div');
this.message.classList.add('message');
this.element = document.createElement('div')
this.element.classList.add('go-to-line')
this.element.appendChild(this.miniEditor.element)
this.element.appendChild(this.message)
this.element = document.createElement('div');
this.element.classList.add('go-to-line');
this.element.appendChild(this.miniEditor.element);
this.element.appendChild(this.message);
this.panel = atom.workspace.addModalPanel({
item: this,
visible: false
})
});
atom.commands.add('atom-text-editor', 'go-to-line:toggle', () => {
this.toggle()
return false
})
this.toggle();
return false;
});
atom.commands.add(this.miniEditor.element, 'core:confirm', () => {
this.navigate()
})
this.navigate();
});
atom.commands.add(this.miniEditor.element, 'core:cancel', () => {
this.close()
})
this.close();
});
this.miniEditor.onWillInsertText(arg => {
if (arg.text.match(/[^0-9:]/)) {
arg.cancel()
arg.cancel();
}
})
});
this.miniEditor.onDidChange(() => {
this.navigate({ keepOpen: true })
})
this.navigate({ keepOpen: true });
});
}
toggle () {
this.panel.isVisible() ? this.close() : this.open()
toggle() {
this.panel.isVisible() ? this.close() : this.open();
}
close () {
if (!this.panel.isVisible()) return
this.miniEditor.setText('')
this.panel.hide()
close() {
if (!this.panel.isVisible()) return;
this.miniEditor.setText('');
this.panel.hide();
if (this.miniEditor.element.hasFocus()) {
this.restoreFocus()
this.restoreFocus();
}
}
navigate (options = {}) {
const lineNumber = this.miniEditor.getText()
const editor = atom.workspace.getActiveTextEditor()
navigate(options = {}) {
const lineNumber = this.miniEditor.getText();
const editor = atom.workspace.getActiveTextEditor();
if (!options.keepOpen) {
this.close()
this.close();
}
if (!editor || !lineNumber.length) return
if (!editor || !lineNumber.length) return;
const currentRow = editor.getCursorBufferPosition().row
const rowLineNumber = lineNumber.split(/:+/)[0] || ''
const currentRow = editor.getCursorBufferPosition().row;
const rowLineNumber = lineNumber.split(/:+/)[0] || '';
const row =
rowLineNumber.length > 0 ? parseInt(rowLineNumber) - 1 : currentRow
const columnLineNumber = lineNumber.split(/:+/)[1] || ''
rowLineNumber.length > 0 ? parseInt(rowLineNumber) - 1 : currentRow;
const columnLineNumber = lineNumber.split(/:+/)[1] || '';
const column =
columnLineNumber.length > 0 ? parseInt(columnLineNumber) - 1 : -1
columnLineNumber.length > 0 ? parseInt(columnLineNumber) - 1 : -1;
const position = new Point(row, column)
editor.setCursorBufferPosition(position)
editor.unfoldBufferRow(row)
const position = new Point(row, column);
editor.setCursorBufferPosition(position);
editor.unfoldBufferRow(row);
if (column < 0) {
editor.moveToFirstCharacterOfLine()
editor.moveToFirstCharacterOfLine();
}
editor.scrollToBufferPosition(position, {
center: true
})
});
}
storeFocusedElement () {
this.previouslyFocusedElement = document.activeElement
return this.previouslyFocusedElement
storeFocusedElement() {
this.previouslyFocusedElement = document.activeElement;
return this.previouslyFocusedElement;
}
restoreFocus () {
restoreFocus() {
if (
this.previouslyFocusedElement &&
this.previouslyFocusedElement.parentElement
) {
return this.previouslyFocusedElement.focus()
return this.previouslyFocusedElement.focus();
}
atom.views.getView(atom.workspace).focus()
atom.views.getView(atom.workspace).focus();
}
open () {
if (this.panel.isVisible() || !atom.workspace.getActiveTextEditor()) return
this.storeFocusedElement()
this.panel.show()
open() {
if (this.panel.isVisible() || !atom.workspace.getActiveTextEditor()) return;
this.storeFocusedElement();
this.panel.show();
this.message.textContent =
'Enter a <row> or <row>:<column> to go there. Examples: "3" for row 3 or "2:7" for row 2 and column 7'
this.miniEditor.element.focus()
'Enter a <row> or <row>:<column> to go there. Examples: "3" for row 3 or "2:7" for row 2 and column 7';
this.miniEditor.element.focus();
}
}
export default {
activate () {
return new GoToLineView()
activate() {
return new GoToLineView();
}
}
};

View File

@ -1,169 +1,169 @@
'use babel'
'use babel';
/* eslint-env jasmine */
import GoToLineView from '../lib/go-to-line-view'
import GoToLineView from '../lib/go-to-line-view';
describe('GoToLine', () => {
let editor = null
let editorView = null
let goToLine = null
let editor = null;
let editorView = null;
let goToLine = null;
beforeEach(() => {
waitsForPromise(() => {
return atom.workspace.open('sample.js')
})
return atom.workspace.open('sample.js');
});
runs(() => {
const workspaceElement = atom.views.getView(atom.workspace)
workspaceElement.style.height = '200px'
workspaceElement.style.width = '1000px'
jasmine.attachToDOM(workspaceElement)
editor = atom.workspace.getActiveTextEditor()
editorView = atom.views.getView(editor)
goToLine = GoToLineView.activate()
editor.setCursorBufferPosition([1, 0])
})
})
const workspaceElement = atom.views.getView(atom.workspace);
workspaceElement.style.height = '200px';
workspaceElement.style.width = '1000px';
jasmine.attachToDOM(workspaceElement);
editor = atom.workspace.getActiveTextEditor();
editorView = atom.views.getView(editor);
goToLine = GoToLineView.activate();
editor.setCursorBufferPosition([1, 0]);
});
});
describe('when go-to-line:toggle is triggered', () => {
it('adds a modal panel', () => {
expect(goToLine.panel.isVisible()).toBeFalsy()
atom.commands.dispatch(editorView, 'go-to-line:toggle')
expect(goToLine.panel.isVisible()).toBeTruthy()
})
})
expect(goToLine.panel.isVisible()).toBeFalsy();
atom.commands.dispatch(editorView, 'go-to-line:toggle');
expect(goToLine.panel.isVisible()).toBeTruthy();
});
});
describe('when entering a line number', () => {
it('only allows 0-9 and the colon character to be entered in the mini editor', () => {
expect(goToLine.miniEditor.getText()).toBe('')
goToLine.miniEditor.insertText('a')
expect(goToLine.miniEditor.getText()).toBe('')
goToLine.miniEditor.insertText('path/file.txt:56')
expect(goToLine.miniEditor.getText()).toBe('')
goToLine.miniEditor.insertText(':')
expect(goToLine.miniEditor.getText()).toBe(':')
goToLine.miniEditor.setText('')
goToLine.miniEditor.insertText('4')
expect(goToLine.miniEditor.getText()).toBe('4')
})
})
expect(goToLine.miniEditor.getText()).toBe('');
goToLine.miniEditor.insertText('a');
expect(goToLine.miniEditor.getText()).toBe('');
goToLine.miniEditor.insertText('path/file.txt:56');
expect(goToLine.miniEditor.getText()).toBe('');
goToLine.miniEditor.insertText(':');
expect(goToLine.miniEditor.getText()).toBe(':');
goToLine.miniEditor.setText('');
goToLine.miniEditor.insertText('4');
expect(goToLine.miniEditor.getText()).toBe('4');
});
});
describe('when typing line numbers (auto-navigation)', () => {
it('automatically scrolls to the desired line', () => {
goToLine.miniEditor.insertText('19')
expect(editor.getCursorBufferPosition()).toEqual([18, 0])
})
})
goToLine.miniEditor.insertText('19');
expect(editor.getCursorBufferPosition()).toEqual([18, 0]);
});
});
describe('when typing line and column numbers (auto-navigation)', () => {
it('automatically scrolls to the desired line and column', () => {
goToLine.miniEditor.insertText('3:8')
expect(editor.getCursorBufferPosition()).toEqual([2, 7])
})
})
goToLine.miniEditor.insertText('3:8');
expect(editor.getCursorBufferPosition()).toEqual([2, 7]);
});
});
describe('when entering a line number and column number', () => {
it('moves the cursor to the column number of the line specified', () => {
expect(goToLine.miniEditor.getText()).toBe('')
goToLine.miniEditor.insertText('3:14')
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
expect(editor.getCursorBufferPosition()).toEqual([2, 13])
})
expect(goToLine.miniEditor.getText()).toBe('');
goToLine.miniEditor.insertText('3:14');
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm');
expect(editor.getCursorBufferPosition()).toEqual([2, 13]);
});
it('centers the selected line', () => {
goToLine.miniEditor.insertText('45:4')
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
const rowsPerPage = editor.getRowsPerPage()
const currentRow = editor.getCursorBufferPosition().row - 1
goToLine.miniEditor.insertText('45:4');
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm');
const rowsPerPage = editor.getRowsPerPage();
const currentRow = editor.getCursorBufferPosition().row - 1;
expect(editor.getFirstVisibleScreenRow()).toBe(
currentRow - Math.ceil(rowsPerPage / 2)
)
);
expect(editor.getLastVisibleScreenRow()).toBe(
currentRow + Math.floor(rowsPerPage / 2)
)
})
})
);
});
});
describe('when entering a line number greater than the number of rows in the buffer', () => {
it('moves the cursor position to the first character of the last line', () => {
atom.commands.dispatch(editorView, 'go-to-line:toggle')
expect(goToLine.panel.isVisible()).toBeTruthy()
expect(goToLine.miniEditor.getText()).toBe('')
goToLine.miniEditor.insertText('78')
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
expect(goToLine.panel.isVisible()).toBeFalsy()
expect(editor.getCursorBufferPosition()).toEqual([77, 0])
})
})
atom.commands.dispatch(editorView, 'go-to-line:toggle');
expect(goToLine.panel.isVisible()).toBeTruthy();
expect(goToLine.miniEditor.getText()).toBe('');
goToLine.miniEditor.insertText('78');
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm');
expect(goToLine.panel.isVisible()).toBeFalsy();
expect(editor.getCursorBufferPosition()).toEqual([77, 0]);
});
});
describe('when entering a column number greater than the number in the specified line', () => {
it('moves the cursor position to the last character of the specified line', () => {
atom.commands.dispatch(editorView, 'go-to-line:toggle')
expect(goToLine.panel.isVisible()).toBeTruthy()
expect(goToLine.miniEditor.getText()).toBe('')
goToLine.miniEditor.insertText('3:43')
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
expect(goToLine.panel.isVisible()).toBeFalsy()
expect(editor.getCursorBufferPosition()).toEqual([2, 39])
})
})
atom.commands.dispatch(editorView, 'go-to-line:toggle');
expect(goToLine.panel.isVisible()).toBeTruthy();
expect(goToLine.miniEditor.getText()).toBe('');
goToLine.miniEditor.insertText('3:43');
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm');
expect(goToLine.panel.isVisible()).toBeFalsy();
expect(editor.getCursorBufferPosition()).toEqual([2, 39]);
});
});
describe('when core:confirm is triggered', () => {
describe('when a line number has been entered', () => {
it('moves the cursor to the first character of the line', () => {
goToLine.miniEditor.insertText('3')
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
expect(editor.getCursorBufferPosition()).toEqual([2, 4])
})
})
goToLine.miniEditor.insertText('3');
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm');
expect(editor.getCursorBufferPosition()).toEqual([2, 4]);
});
});
describe('when the line number entered is nested within foldes', () => {
it('unfolds all folds containing the given row', () => {
expect(editor.indentationForBufferRow(9)).toEqual(3)
editor.foldAll()
expect(editor.screenRowForBufferRow(9)).toEqual(0)
goToLine.miniEditor.insertText('10')
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
expect(editor.getCursorBufferPosition()).toEqual([9, 6])
})
})
})
expect(editor.indentationForBufferRow(9)).toEqual(3);
editor.foldAll();
expect(editor.screenRowForBufferRow(9)).toEqual(0);
goToLine.miniEditor.insertText('10');
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm');
expect(editor.getCursorBufferPosition()).toEqual([9, 6]);
});
});
});
describe('when no line number has been entered', () => {
it('closes the view and does not update the cursor position', () => {
atom.commands.dispatch(editorView, 'go-to-line:toggle')
expect(goToLine.panel.isVisible()).toBeTruthy()
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
expect(goToLine.panel.isVisible()).toBeFalsy()
expect(editor.getCursorBufferPosition()).toEqual([1, 0])
})
})
atom.commands.dispatch(editorView, 'go-to-line:toggle');
expect(goToLine.panel.isVisible()).toBeTruthy();
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm');
expect(goToLine.panel.isVisible()).toBeFalsy();
expect(editor.getCursorBufferPosition()).toEqual([1, 0]);
});
});
describe('when no line number has been entered, but a column number has been entered', () => {
it('navigates to the column of the current line', () => {
atom.commands.dispatch(editorView, 'go-to-line:toggle')
expect(goToLine.panel.isVisible()).toBeTruthy()
goToLine.miniEditor.insertText('4:1')
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
expect(goToLine.panel.isVisible()).toBeFalsy()
expect(editor.getCursorBufferPosition()).toEqual([3, 0])
atom.commands.dispatch(editorView, 'go-to-line:toggle')
expect(goToLine.panel.isVisible()).toBeTruthy()
goToLine.miniEditor.insertText(':19')
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
expect(goToLine.panel.isVisible()).toBeFalsy()
expect(editor.getCursorBufferPosition()).toEqual([3, 18])
})
})
atom.commands.dispatch(editorView, 'go-to-line:toggle');
expect(goToLine.panel.isVisible()).toBeTruthy();
goToLine.miniEditor.insertText('4:1');
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm');
expect(goToLine.panel.isVisible()).toBeFalsy();
expect(editor.getCursorBufferPosition()).toEqual([3, 0]);
atom.commands.dispatch(editorView, 'go-to-line:toggle');
expect(goToLine.panel.isVisible()).toBeTruthy();
goToLine.miniEditor.insertText(':19');
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm');
expect(goToLine.panel.isVisible()).toBeFalsy();
expect(editor.getCursorBufferPosition()).toEqual([3, 18]);
});
});
describe('when core:cancel is triggered', () => {
it('closes the view and does not update the cursor position', () => {
atom.commands.dispatch(editorView, 'go-to-line:toggle')
expect(goToLine.panel.isVisible()).toBeTruthy()
atom.commands.dispatch(goToLine.miniEditor.element, 'core:cancel')
expect(goToLine.panel.isVisible()).toBeFalsy()
expect(editor.getCursorBufferPosition()).toEqual([1, 0])
})
})
})
atom.commands.dispatch(editorView, 'go-to-line:toggle');
expect(goToLine.panel.isVisible()).toBeTruthy();
atom.commands.dispatch(goToLine.miniEditor.element, 'core:cancel');
expect(goToLine.panel.isVisible()).toBeFalsy();
expect(editor.getCursorBufferPosition()).toEqual([1, 0]);
});
});
});

View File

@ -1,103 +1,103 @@
const SelectListView = require('atom-select-list')
const SelectListView = require('atom-select-list');
module.exports = class GrammarListView {
constructor () {
this.autoDetect = { name: 'Auto Detect' }
constructor() {
this.autoDetect = { name: 'Auto Detect' };
this.selectListView = new SelectListView({
itemsClassList: ['mark-active'],
items: [],
filterKeyForItem: grammar => grammar.name,
elementForItem: grammar => {
const grammarName = grammar.name || grammar.scopeName
const element = document.createElement('li')
const grammarName = grammar.name || grammar.scopeName;
const element = document.createElement('li');
if (grammar === this.currentGrammar) {
element.classList.add('active')
element.classList.add('active');
}
element.textContent = grammarName
element.dataset.grammar = grammarName
element.textContent = grammarName;
element.dataset.grammar = grammarName;
const div = document.createElement('div')
div.classList.add('pull-right')
const div = document.createElement('div');
div.classList.add('pull-right');
if (grammar.scopeName) {
const scopeName = document.createElement('scopeName')
scopeName.classList.add('key-binding') // It will be styled the same as the keybindings in the command palette
scopeName.textContent = grammar.scopeName
div.appendChild(scopeName)
element.appendChild(div)
const scopeName = document.createElement('scopeName');
scopeName.classList.add('key-binding'); // It will be styled the same as the keybindings in the command palette
scopeName.textContent = grammar.scopeName;
div.appendChild(scopeName);
element.appendChild(div);
}
return element
return element;
},
didConfirmSelection: grammar => {
this.cancel()
this.cancel();
if (grammar === this.autoDetect) {
atom.textEditors.clearGrammarOverride(this.editor)
atom.textEditors.clearGrammarOverride(this.editor);
} else {
atom.textEditors.setGrammarOverride(this.editor, grammar.scopeName)
atom.textEditors.setGrammarOverride(this.editor, grammar.scopeName);
}
},
didCancelSelection: () => {
this.cancel()
this.cancel();
}
})
this.selectListView.element.classList.add('grammar-selector')
});
this.selectListView.element.classList.add('grammar-selector');
}
destroy () {
this.cancel()
return this.selectListView.destroy()
destroy() {
this.cancel();
return this.selectListView.destroy();
}
cancel () {
cancel() {
if (this.panel != null) {
this.panel.destroy()
this.panel.destroy();
}
this.panel = null
this.currentGrammar = null
this.panel = null;
this.currentGrammar = null;
if (this.previouslyFocusedElement) {
this.previouslyFocusedElement.focus()
this.previouslyFocusedElement = null
this.previouslyFocusedElement.focus();
this.previouslyFocusedElement = null;
}
}
attach () {
this.previouslyFocusedElement = document.activeElement
attach() {
this.previouslyFocusedElement = document.activeElement;
if (this.panel == null) {
this.panel = atom.workspace.addModalPanel({ item: this.selectListView })
this.panel = atom.workspace.addModalPanel({ item: this.selectListView });
}
this.selectListView.focus()
this.selectListView.reset()
this.selectListView.focus();
this.selectListView.reset();
}
async toggle () {
async toggle() {
if (this.panel != null) {
this.cancel()
this.cancel();
} else if (atom.workspace.getActiveTextEditor()) {
this.editor = atom.workspace.getActiveTextEditor()
this.currentGrammar = this.editor.getGrammar()
this.editor = atom.workspace.getActiveTextEditor();
this.currentGrammar = this.editor.getGrammar();
if (this.currentGrammar === atom.grammars.nullGrammar) {
this.currentGrammar = this.autoDetect
this.currentGrammar = this.autoDetect;
}
const grammars = atom.grammars.getGrammars().filter(grammar => {
return grammar !== atom.grammars.nullGrammar && grammar.name
})
return grammar !== atom.grammars.nullGrammar && grammar.name;
});
grammars.sort((a, b) => {
if (a.scopeName === 'text.plain') {
return -1
return -1;
} else if (b.scopeName === 'text.plain') {
return 1
return 1;
} else if (a.name) {
return a.name.localeCompare(b.name)
return a.name.localeCompare(b.name);
} else if (a.scopeName) {
return a.scopeName.localeCompare(b.scopeName)
return a.scopeName.localeCompare(b.scopeName);
} else {
return 1
return 1;
}
})
grammars.unshift(this.autoDetect)
await this.selectListView.update({ items: grammars })
this.attach()
});
grammars.unshift(this.autoDetect);
await this.selectListView.update({ items: grammars });
this.attach();
}
}
}
};

View File

@ -1,114 +1,114 @@
const { Disposable } = require('atom')
const { Disposable } = require('atom');
module.exports = class GrammarStatusView {
constructor (statusBar) {
this.statusBar = statusBar
this.element = document.createElement('grammar-selector-status')
this.element.classList.add('grammar-status', 'inline-block')
this.grammarLink = document.createElement('a')
this.grammarLink.classList.add('inline-block')
this.element.appendChild(this.grammarLink)
constructor(statusBar) {
this.statusBar = statusBar;
this.element = document.createElement('grammar-selector-status');
this.element.classList.add('grammar-status', 'inline-block');
this.grammarLink = document.createElement('a');
this.grammarLink.classList.add('inline-block');
this.element.appendChild(this.grammarLink);
this.activeItemSubscription = atom.workspace.observeActiveTextEditor(
this.subscribeToActiveTextEditor.bind(this)
)
);
this.configSubscription = atom.config.observe(
'grammar-selector.showOnRightSideOfStatusBar',
this.attach.bind(this)
)
);
const clickHandler = event => {
event.preventDefault()
event.preventDefault();
atom.commands.dispatch(
atom.views.getView(atom.workspace.getActiveTextEditor()),
'grammar-selector:show'
)
}
this.element.addEventListener('click', clickHandler)
);
};
this.element.addEventListener('click', clickHandler);
this.clickSubscription = new Disposable(() => {
this.element.removeEventListener('click', clickHandler)
})
this.element.removeEventListener('click', clickHandler);
});
}
attach () {
attach() {
if (this.tile) {
this.tile.destroy()
this.tile.destroy();
}
this.tile = atom.config.get('grammar-selector.showOnRightSideOfStatusBar')
? this.statusBar.addRightTile({ item: this.element, priority: 10 })
: this.statusBar.addLeftTile({ item: this.element, priority: 10 })
: this.statusBar.addLeftTile({ item: this.element, priority: 10 });
}
destroy () {
destroy() {
if (this.activeItemSubscription) {
this.activeItemSubscription.dispose()
this.activeItemSubscription.dispose();
}
if (this.grammarSubscription) {
this.grammarSubscription.dispose()
this.grammarSubscription.dispose();
}
if (this.clickSubscription) {
this.clickSubscription.dispose()
this.clickSubscription.dispose();
}
if (this.configSubscription) {
this.configSubscription.dispose()
this.configSubscription.dispose();
}
if (this.tile) {
this.tile.destroy()
this.tile.destroy();
}
if (this.tooltip) {
this.tooltip.dispose()
this.tooltip.dispose();
}
}
subscribeToActiveTextEditor () {
subscribeToActiveTextEditor() {
if (this.grammarSubscription) {
this.grammarSubscription.dispose()
this.grammarSubscription = null
this.grammarSubscription.dispose();
this.grammarSubscription = null;
}
const editor = atom.workspace.getActiveTextEditor()
const editor = atom.workspace.getActiveTextEditor();
if (editor) {
this.grammarSubscription = editor.onDidChangeGrammar(
this.updateGrammarText.bind(this)
)
);
}
this.updateGrammarText()
this.updateGrammarText();
}
updateGrammarText () {
updateGrammarText() {
atom.views.updateDocument(() => {
const editor = atom.workspace.getActiveTextEditor()
const grammar = editor ? editor.getGrammar() : null
const editor = atom.workspace.getActiveTextEditor();
const grammar = editor ? editor.getGrammar() : null;
if (this.tooltip) {
this.tooltip.dispose()
this.tooltip = null
this.tooltip.dispose();
this.tooltip = null;
}
if (grammar) {
let grammarName = null
let grammarName = null;
if (grammar === atom.grammars.nullGrammar) {
grammarName = 'Plain Text'
grammarName = 'Plain Text';
} else {
grammarName = grammar.name || grammar.scopeName
grammarName = grammar.name || grammar.scopeName;
}
this.grammarLink.textContent = grammarName
this.grammarLink.dataset.grammar = grammarName
this.element.style.display = ''
this.grammarLink.textContent = grammarName;
this.grammarLink.dataset.grammar = grammarName;
this.element.style.display = '';
this.tooltip = atom.tooltips.add(this.element, {
title: `File uses the ${grammarName} grammar`
})
});
} else {
this.element.style.display = 'none'
this.element.style.display = 'none';
}
})
});
}
}
};

View File

@ -1,35 +1,35 @@
const GrammarListView = require('./grammar-list-view')
const GrammarStatusView = require('./grammar-status-view')
const GrammarListView = require('./grammar-list-view');
const GrammarStatusView = require('./grammar-status-view');
let commandDisposable = null
let grammarListView = null
let grammarStatusView = null
let commandDisposable = null;
let grammarListView = null;
let grammarStatusView = null;
module.exports = {
activate () {
activate() {
commandDisposable = atom.commands.add(
'atom-text-editor',
'grammar-selector:show',
() => {
if (!grammarListView) grammarListView = new GrammarListView()
grammarListView.toggle()
if (!grammarListView) grammarListView = new GrammarListView();
grammarListView.toggle();
}
)
);
},
deactivate () {
if (commandDisposable) commandDisposable.dispose()
commandDisposable = null
deactivate() {
if (commandDisposable) commandDisposable.dispose();
commandDisposable = null;
if (grammarStatusView) grammarStatusView.destroy()
grammarStatusView = null
if (grammarStatusView) grammarStatusView.destroy();
grammarStatusView = null;
if (grammarListView) grammarListView.destroy()
grammarListView = null
if (grammarListView) grammarListView.destroy();
grammarListView = null;
},
consumeStatusBar (statusBar) {
grammarStatusView = new GrammarStatusView(statusBar)
grammarStatusView.attach()
consumeStatusBar(statusBar) {
grammarStatusView = new GrammarStatusView(statusBar);
grammarStatusView.attach();
}
}
};

View File

@ -1,226 +1,226 @@
const path = require('path')
const SelectListView = require('atom-select-list')
const path = require('path');
const SelectListView = require('atom-select-list');
describe('GrammarSelector', () => {
let [editor, textGrammar, jsGrammar] = []
let [editor, textGrammar, jsGrammar] = [];
beforeEach(async () => {
jasmine.attachToDOM(atom.views.getView(atom.workspace))
atom.config.set('grammar-selector.showOnRightSideOfStatusBar', false)
jasmine.attachToDOM(atom.views.getView(atom.workspace));
atom.config.set('grammar-selector.showOnRightSideOfStatusBar', false);
await atom.packages.activatePackage('status-bar')
await atom.packages.activatePackage('grammar-selector')
await atom.packages.activatePackage('language-text')
await atom.packages.activatePackage('language-javascript')
await atom.packages.activatePackage('status-bar');
await atom.packages.activatePackage('grammar-selector');
await atom.packages.activatePackage('language-text');
await atom.packages.activatePackage('language-javascript');
await atom.packages.activatePackage(
path.join(__dirname, 'fixtures', 'language-with-no-name')
)
);
editor = await atom.workspace.open('sample.js')
editor = await atom.workspace.open('sample.js');
textGrammar = atom.grammars.grammarForScopeName('text.plain')
expect(textGrammar).toBeTruthy()
jsGrammar = atom.grammars.grammarForScopeName('source.js')
expect(jsGrammar).toBeTruthy()
expect(editor.getGrammar()).toBe(jsGrammar)
})
textGrammar = atom.grammars.grammarForScopeName('text.plain');
expect(textGrammar).toBeTruthy();
jsGrammar = atom.grammars.grammarForScopeName('source.js');
expect(jsGrammar).toBeTruthy();
expect(editor.getGrammar()).toBe(jsGrammar);
});
describe('when grammar-selector:show is triggered', () =>
it('displays a list of all the available grammars', async () => {
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show')
await SelectListView.getScheduler().getNextUpdatePromise()
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show');
await SelectListView.getScheduler().getNextUpdatePromise();
const grammarView = atom.workspace.getModalPanels()[0].getItem().element
const grammarView = atom.workspace.getModalPanels()[0].getItem().element;
// TODO: Remove once Atom 1.23 reaches stable
if (parseFloat(atom.getVersion()) >= 1.23) {
// Do not take into account the two JS regex grammars or language-with-no-name
expect(grammarView.querySelectorAll('li').length).toBe(
atom.grammars.grammars.length - 3
)
);
} else {
expect(grammarView.querySelectorAll('li').length).toBe(
atom.grammars.grammars.length - 1
)
);
}
expect(grammarView.querySelectorAll('li')[0].textContent).toBe(
'Auto Detect'
)
expect(grammarView.textContent.includes('source.a')).toBe(false)
);
expect(grammarView.textContent.includes('source.a')).toBe(false);
grammarView
.querySelectorAll('li')
.forEach(li =>
expect(li.textContent).not.toBe(atom.grammars.nullGrammar.name)
)
}))
);
}));
describe('when a grammar is selected', () =>
it('sets the new grammar on the editor', async () => {
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show')
await SelectListView.getScheduler().getNextUpdatePromise()
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show');
await SelectListView.getScheduler().getNextUpdatePromise();
const grammarView = atom.workspace.getModalPanels()[0].getItem()
grammarView.props.didConfirmSelection(textGrammar)
expect(editor.getGrammar()).toBe(textGrammar)
}))
const grammarView = atom.workspace.getModalPanels()[0].getItem();
grammarView.props.didConfirmSelection(textGrammar);
expect(editor.getGrammar()).toBe(textGrammar);
}));
describe('when auto-detect is selected', () =>
it('restores the auto-detected grammar on the editor', async () => {
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show')
await SelectListView.getScheduler().getNextUpdatePromise()
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show');
await SelectListView.getScheduler().getNextUpdatePromise();
let grammarView = atom.workspace.getModalPanels()[0].getItem()
grammarView.props.didConfirmSelection(textGrammar)
expect(editor.getGrammar()).toBe(textGrammar)
let grammarView = atom.workspace.getModalPanels()[0].getItem();
grammarView.props.didConfirmSelection(textGrammar);
expect(editor.getGrammar()).toBe(textGrammar);
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show')
await SelectListView.getScheduler().getNextUpdatePromise()
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show');
await SelectListView.getScheduler().getNextUpdatePromise();
grammarView = atom.workspace.getModalPanels()[0].getItem()
grammarView.props.didConfirmSelection(grammarView.items[0])
expect(editor.getGrammar()).toBe(jsGrammar)
}))
grammarView = atom.workspace.getModalPanels()[0].getItem();
grammarView.props.didConfirmSelection(grammarView.items[0]);
expect(editor.getGrammar()).toBe(jsGrammar);
}));
describe("when the editor's current grammar is the null grammar", () =>
it('displays Auto Detect as the selected grammar', async () => {
editor.setGrammar(atom.grammars.nullGrammar)
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show')
await SelectListView.getScheduler().getNextUpdatePromise()
editor.setGrammar(atom.grammars.nullGrammar);
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show');
await SelectListView.getScheduler().getNextUpdatePromise();
const grammarView = atom.workspace.getModalPanels()[0].getItem().element
const grammarView = atom.workspace.getModalPanels()[0].getItem().element;
expect(grammarView.querySelector('li.active').textContent).toBe(
'Auto Detect'
)
}))
);
}));
describe('when editor is untitled', () =>
it('sets the new grammar on the editor', async () => {
editor = await atom.workspace.open()
expect(editor.getGrammar()).not.toBe(jsGrammar)
editor = await atom.workspace.open();
expect(editor.getGrammar()).not.toBe(jsGrammar);
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show')
await SelectListView.getScheduler().getNextUpdatePromise()
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show');
await SelectListView.getScheduler().getNextUpdatePromise();
const grammarView = atom.workspace.getModalPanels()[0].getItem()
grammarView.props.didConfirmSelection(jsGrammar)
expect(editor.getGrammar()).toBe(jsGrammar)
}))
const grammarView = atom.workspace.getModalPanels()[0].getItem();
grammarView.props.didConfirmSelection(jsGrammar);
expect(editor.getGrammar()).toBe(jsGrammar);
}));
describe('Status bar grammar label', () => {
let [grammarStatus, grammarTile, statusBar] = []
let [grammarStatus, grammarTile, statusBar] = [];
beforeEach(async () => {
statusBar = document.querySelector('status-bar')
;[grammarTile] = statusBar.getLeftTiles().slice(-1)
grammarStatus = grammarTile.getItem()
statusBar = document.querySelector('status-bar');
[grammarTile] = statusBar.getLeftTiles().slice(-1);
grammarStatus = grammarTile.getItem();
// Wait for status bar service hook to fire
while (!grammarStatus || !grammarStatus.textContent) {
await atom.views.getNextUpdatePromise()
grammarStatus = document.querySelector('.grammar-status')
await atom.views.getNextUpdatePromise();
grammarStatus = document.querySelector('.grammar-status');
}
})
});
it('displays the name of the current grammar', () => {
expect(grammarStatus.querySelector('a').textContent).toBe('JavaScript')
expect(grammarStatus.querySelector('a').textContent).toBe('JavaScript');
expect(getTooltipText(grammarStatus)).toBe(
'File uses the JavaScript grammar'
)
})
);
});
it('displays Plain Text when the current grammar is the null grammar', async () => {
editor.setGrammar(atom.grammars.nullGrammar)
await atom.views.getNextUpdatePromise()
editor.setGrammar(atom.grammars.nullGrammar);
await atom.views.getNextUpdatePromise();
expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text')
expect(grammarStatus).toBeVisible()
expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text');
expect(grammarStatus).toBeVisible();
expect(getTooltipText(grammarStatus)).toBe(
'File uses the Plain Text grammar'
)
);
editor.setGrammar(atom.grammars.grammarForScopeName('source.js'))
await atom.views.getNextUpdatePromise()
editor.setGrammar(atom.grammars.grammarForScopeName('source.js'));
await atom.views.getNextUpdatePromise();
expect(grammarStatus.querySelector('a').textContent).toBe('JavaScript')
expect(grammarStatus).toBeVisible()
})
expect(grammarStatus.querySelector('a').textContent).toBe('JavaScript');
expect(grammarStatus).toBeVisible();
});
it('hides the label when the current grammar is null', async () => {
jasmine.attachToDOM(editor.getElement())
spyOn(editor, 'getGrammar').andReturn(null)
editor.setGrammar(atom.grammars.nullGrammar)
await atom.views.getNextUpdatePromise()
expect(grammarStatus.offsetHeight).toBe(0)
})
jasmine.attachToDOM(editor.getElement());
spyOn(editor, 'getGrammar').andReturn(null);
editor.setGrammar(atom.grammars.nullGrammar);
await atom.views.getNextUpdatePromise();
expect(grammarStatus.offsetHeight).toBe(0);
});
describe('when the grammar-selector.showOnRightSideOfStatusBar setting changes', () =>
it('moves the item to the preferred side of the status bar', () => {
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain(
grammarStatus
)
);
expect(
statusBar.getRightTiles().map(tile => tile.getItem())
).not.toContain(grammarStatus)
).not.toContain(grammarStatus);
atom.config.set('grammar-selector.showOnRightSideOfStatusBar', true)
atom.config.set('grammar-selector.showOnRightSideOfStatusBar', true);
expect(
statusBar.getLeftTiles().map(tile => tile.getItem())
).not.toContain(grammarStatus)
).not.toContain(grammarStatus);
expect(statusBar.getRightTiles().map(tile => tile.getItem())).toContain(
grammarStatus
)
);
atom.config.set('grammar-selector.showOnRightSideOfStatusBar', false)
atom.config.set('grammar-selector.showOnRightSideOfStatusBar', false);
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain(
grammarStatus
)
);
expect(
statusBar.getRightTiles().map(tile => tile.getItem())
).not.toContain(grammarStatus)
}))
).not.toContain(grammarStatus);
}));
describe("when the editor's grammar changes", () =>
it('displays the new grammar of the editor', async () => {
editor.setGrammar(atom.grammars.grammarForScopeName('text.plain'))
await atom.views.getNextUpdatePromise()
editor.setGrammar(atom.grammars.grammarForScopeName('text.plain'));
await atom.views.getNextUpdatePromise();
expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text')
expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text');
expect(getTooltipText(grammarStatus)).toBe(
'File uses the Plain Text grammar'
)
);
editor.setGrammar(atom.grammars.grammarForScopeName('source.a'))
await atom.views.getNextUpdatePromise()
editor.setGrammar(atom.grammars.grammarForScopeName('source.a'));
await atom.views.getNextUpdatePromise();
expect(grammarStatus.querySelector('a').textContent).toBe('source.a')
expect(grammarStatus.querySelector('a').textContent).toBe('source.a');
expect(getTooltipText(grammarStatus)).toBe(
'File uses the source.a grammar'
)
}))
);
}));
describe('when clicked', () =>
it('shows the grammar selector modal', () => {
const eventHandler = jasmine.createSpy('eventHandler')
const eventHandler = jasmine.createSpy('eventHandler');
atom.commands.add(
editor.getElement(),
'grammar-selector:show',
eventHandler
)
grammarStatus.click()
expect(eventHandler).toHaveBeenCalled()
}))
);
grammarStatus.click();
expect(eventHandler).toHaveBeenCalled();
}));
describe('when the package is deactivated', () =>
it('removes the view', () => {
spyOn(grammarTile, 'destroy')
atom.packages.deactivatePackage('grammar-selector')
expect(grammarTile.destroy).toHaveBeenCalled()
}))
})
})
spyOn(grammarTile, 'destroy');
atom.packages.deactivatePackage('grammar-selector');
expect(grammarTile.destroy).toHaveBeenCalled();
}));
});
});
function getTooltipText (element) {
const [tooltip] = atom.tooltips.findTooltips(element)
return tooltip.getTitle()
function getTooltipText(element) {
const [tooltip] = atom.tooltips.findTooltips(element);
return tooltip.getTitle();
}

View File

@ -1,239 +1,239 @@
/** @babel */
/** @jsx etch.dom */
import etch from 'etch'
import etch from 'etch';
import VIEW_URI from './view-uri'
const REBUILDING = 'rebuilding'
const REBUILD_FAILED = 'rebuild-failed'
const REBUILD_SUCCEEDED = 'rebuild-succeeded'
import VIEW_URI from './view-uri';
const REBUILDING = 'rebuilding';
const REBUILD_FAILED = 'rebuild-failed';
const REBUILD_SUCCEEDED = 'rebuild-succeeded';
export default class IncompatiblePackagesComponent {
constructor (packageManager) {
this.rebuildStatuses = new Map()
this.rebuildFailureOutputs = new Map()
this.rebuildInProgress = false
this.rebuiltPackageCount = 0
this.packageManager = packageManager
this.loaded = false
etch.initialize(this)
constructor(packageManager) {
this.rebuildStatuses = new Map();
this.rebuildFailureOutputs = new Map();
this.rebuildInProgress = false;
this.rebuiltPackageCount = 0;
this.packageManager = packageManager;
this.loaded = false;
etch.initialize(this);
if (this.packageManager.getActivePackages().length > 0) {
this.populateIncompatiblePackages()
this.populateIncompatiblePackages();
} else {
global.setImmediate(this.populateIncompatiblePackages.bind(this))
global.setImmediate(this.populateIncompatiblePackages.bind(this));
}
this.element.addEventListener('click', event => {
if (event.target === this.refs.rebuildButton) {
this.rebuildIncompatiblePackages()
this.rebuildIncompatiblePackages();
} else if (event.target === this.refs.reloadButton) {
atom.reload()
atom.reload();
} else if (event.target.classList.contains('view-settings')) {
atom.workspace.open(
`atom://config/packages/${event.target.package.name}`
)
);
}
})
});
}
update () {}
update() {}
render () {
render() {
if (!this.loaded) {
return <div className='incompatible-packages padded'>Loading...</div>
return <div className="incompatible-packages padded">Loading...</div>;
}
return (
<div
className='incompatible-packages padded native-key-bindings'
tabIndex='-1'
className="incompatible-packages padded native-key-bindings"
tabIndex="-1"
>
{this.renderHeading()}
{this.renderIncompatiblePackageList()}
</div>
)
);
}
renderHeading () {
renderHeading() {
if (this.incompatiblePackages.length > 0) {
if (this.rebuiltPackageCount > 0) {
let alertClass =
this.rebuiltPackageCount === this.incompatiblePackages.length
? 'alert-success icon-check'
: 'alert-warning icon-bug'
: 'alert-warning icon-bug';
return (
<div className={'alert icon ' + alertClass}>
{this.rebuiltPackageCount} of {this.incompatiblePackages.length}{' '}
packages were rebuilt successfully. Reload Atom to activate them.
<button ref='reloadButton' className='btn pull-right'>
<button ref="reloadButton" className="btn pull-right">
Reload Atom
</button>
</div>
)
);
} else {
return (
<div className='alert alert-danger icon icon-bug'>
<div className="alert alert-danger icon icon-bug">
Some installed packages could not be loaded because they contain
native modules that were compiled for an earlier version of Atom.
<button
ref='rebuildButton'
className='btn pull-right'
ref="rebuildButton"
className="btn pull-right"
disabled={this.rebuildInProgress}
>
Rebuild Packages
</button>
</div>
)
);
}
} else {
return (
<div className='alert alert-success icon icon-check'>
<div className="alert alert-success icon icon-check">
None of your packages contain incompatible native modules.
</div>
)
);
}
}
renderIncompatiblePackageList () {
renderIncompatiblePackageList() {
return (
<div>
{this.incompatiblePackages.map(
this.renderIncompatiblePackage.bind(this)
)}
</div>
)
);
}
renderIncompatiblePackage (pack) {
let rebuildStatus = this.rebuildStatuses.get(pack)
renderIncompatiblePackage(pack) {
let rebuildStatus = this.rebuildStatuses.get(pack);
return (
<div className={'incompatible-package'}>
{this.renderRebuildStatusIndicator(rebuildStatus)}
<button
className='btn view-settings icon icon-gear pull-right'
className="btn view-settings icon icon-gear pull-right"
package={pack}
>
Package Settings
</button>
<h4 className='heading'>
<h4 className="heading">
{pack.name} {pack.metadata.version}
</h4>
{rebuildStatus
? this.renderRebuildOutput(pack)
: this.renderIncompatibleModules(pack)}
</div>
)
);
}
renderRebuildStatusIndicator (rebuildStatus) {
renderRebuildStatusIndicator(rebuildStatus) {
if (rebuildStatus === REBUILDING) {
return (
<div className='badge badge-info pull-right icon icon-gear'>
<div className="badge badge-info pull-right icon icon-gear">
Rebuilding
</div>
)
);
} else if (rebuildStatus === REBUILD_SUCCEEDED) {
return (
<div className='badge badge-success pull-right icon icon-check'>
<div className="badge badge-success pull-right icon icon-check">
Rebuild Succeeded
</div>
)
);
} else if (rebuildStatus === REBUILD_FAILED) {
return (
<div className='badge badge-error pull-right icon icon-x'>
<div className="badge badge-error pull-right icon icon-x">
Rebuild Failed
</div>
)
);
} else {
return ''
return '';
}
}
renderRebuildOutput (pack) {
renderRebuildOutput(pack) {
if (this.rebuildStatuses.get(pack) === REBUILD_FAILED) {
return <pre>{this.rebuildFailureOutputs.get(pack)}</pre>
return <pre>{this.rebuildFailureOutputs.get(pack)}</pre>;
} else {
return ''
return '';
}
}
renderIncompatibleModules (pack) {
renderIncompatibleModules(pack) {
return (
<ul>
{pack.incompatibleModules.map(nativeModule => (
<li>
<div className='icon icon-file-binary'>
<div className="icon icon-file-binary">
{nativeModule.name}@{nativeModule.version || 'unknown'} {' '}
<span className='text-warning'>{nativeModule.error}</span>
<span className="text-warning">{nativeModule.error}</span>
</div>
</li>
))}
</ul>
)
);
}
populateIncompatiblePackages () {
populateIncompatiblePackages() {
this.incompatiblePackages = this.packageManager
.getLoadedPackages()
.filter(pack => !pack.isCompatible())
.filter(pack => !pack.isCompatible());
for (let pack of this.incompatiblePackages) {
let buildFailureOutput = pack.getBuildFailureOutput()
let buildFailureOutput = pack.getBuildFailureOutput();
if (buildFailureOutput) {
this.setPackageStatus(pack, REBUILD_FAILED)
this.setRebuildFailureOutput(pack, buildFailureOutput)
this.setPackageStatus(pack, REBUILD_FAILED);
this.setRebuildFailureOutput(pack, buildFailureOutput);
}
}
this.loaded = true
etch.update(this)
this.loaded = true;
etch.update(this);
}
async rebuildIncompatiblePackages () {
this.rebuildInProgress = true
let rebuiltPackageCount = 0
async rebuildIncompatiblePackages() {
this.rebuildInProgress = true;
let rebuiltPackageCount = 0;
for (let pack of this.incompatiblePackages) {
this.setPackageStatus(pack, REBUILDING)
let { code, stderr } = await pack.rebuild()
this.setPackageStatus(pack, REBUILDING);
let { code, stderr } = await pack.rebuild();
if (code === 0) {
this.setPackageStatus(pack, REBUILD_SUCCEEDED)
rebuiltPackageCount++
this.setPackageStatus(pack, REBUILD_SUCCEEDED);
rebuiltPackageCount++;
} else {
this.setRebuildFailureOutput(pack, stderr)
this.setPackageStatus(pack, REBUILD_FAILED)
this.setRebuildFailureOutput(pack, stderr);
this.setPackageStatus(pack, REBUILD_FAILED);
}
}
this.rebuildInProgress = false
this.rebuiltPackageCount = rebuiltPackageCount
etch.update(this)
this.rebuildInProgress = false;
this.rebuiltPackageCount = rebuiltPackageCount;
etch.update(this);
}
setPackageStatus (pack, status) {
this.rebuildStatuses.set(pack, status)
etch.update(this)
setPackageStatus(pack, status) {
this.rebuildStatuses.set(pack, status);
etch.update(this);
}
setRebuildFailureOutput (pack, output) {
this.rebuildFailureOutputs.set(pack, output)
etch.update(this)
setRebuildFailureOutput(pack, output) {
this.rebuildFailureOutputs.set(pack, output);
etch.update(this);
}
getTitle () {
return 'Incompatible Packages'
getTitle() {
return 'Incompatible Packages';
}
getURI () {
return VIEW_URI
getURI() {
return VIEW_URI;
}
getIconName () {
return 'package'
getIconName() {
return 'package';
}
serialize () {
return { deserializer: 'IncompatiblePackagesComponent' }
serialize() {
return { deserializer: 'IncompatiblePackagesComponent' };
}
}

View File

@ -1,56 +1,56 @@
/** @babel */
import { Disposable, CompositeDisposable } from 'atom'
import VIEW_URI from './view-uri'
import { Disposable, CompositeDisposable } from 'atom';
import VIEW_URI from './view-uri';
let disposables = null
let disposables = null;
export function activate () {
disposables = new CompositeDisposable()
export function activate() {
disposables = new CompositeDisposable();
disposables.add(
atom.workspace.addOpener(uri => {
if (uri === VIEW_URI) {
return deserializeIncompatiblePackagesComponent()
return deserializeIncompatiblePackagesComponent();
}
})
)
);
disposables.add(
atom.commands.add('atom-workspace', {
'incompatible-packages:view': () => {
atom.workspace.open(VIEW_URI)
atom.workspace.open(VIEW_URI);
}
})
)
);
}
export function deactivate () {
disposables.dispose()
export function deactivate() {
disposables.dispose();
}
export function consumeStatusBar (statusBar) {
let incompatibleCount = 0
export function consumeStatusBar(statusBar) {
let incompatibleCount = 0;
for (let pack of atom.packages.getLoadedPackages()) {
if (!pack.isCompatible()) incompatibleCount++
if (!pack.isCompatible()) incompatibleCount++;
}
if (incompatibleCount > 0) {
let icon = createIcon(incompatibleCount)
let tile = statusBar.addRightTile({ item: icon, priority: 200 })
let icon = createIcon(incompatibleCount);
let tile = statusBar.addRightTile({ item: icon, priority: 200 });
icon.element.addEventListener('click', () => {
atom.commands.dispatch(icon.element, 'incompatible-packages:view')
})
disposables.add(new Disposable(() => tile.destroy()))
atom.commands.dispatch(icon.element, 'incompatible-packages:view');
});
disposables.add(new Disposable(() => tile.destroy()));
}
}
export function deserializeIncompatiblePackagesComponent () {
const IncompatiblePackagesComponent = require('./incompatible-packages-component')
return new IncompatiblePackagesComponent(atom.packages)
export function deserializeIncompatiblePackagesComponent() {
const IncompatiblePackagesComponent = require('./incompatible-packages-component');
return new IncompatiblePackagesComponent(atom.packages);
}
function createIcon (count) {
const StatusIconComponent = require('./status-icon-component')
return new StatusIconComponent({ count })
function createIcon(count) {
const StatusIconComponent = require('./status-icon-component');
return new StatusIconComponent({ count });
}

View File

@ -1,22 +1,22 @@
/** @babel */
/** @jsx etch.dom */
import etch from 'etch'
import etch from 'etch';
export default class StatusIconComponent {
constructor ({ count }) {
this.count = count
etch.initialize(this)
constructor({ count }) {
this.count = count;
etch.initialize(this);
}
update () {}
update() {}
render () {
render() {
return (
<div className='incompatible-packages-status inline-block text text-error'>
<span className='icon icon-bug' />
<span className='incompatible-packages-count'>{this.count}</span>
<div className="incompatible-packages-status inline-block text text-error">
<span className="icon icon-bug" />
<span className="incompatible-packages-count">{this.count}</span>
</div>
)
);
}
}

View File

@ -1,3 +1,3 @@
/** @babel */
export default 'atom://incompatible-packages'
export default 'atom://incompatible-packages';

View File

@ -1,25 +1,25 @@
/** @babel */
import etch from 'etch'
import IncompatiblePackagesComponent from '../lib/incompatible-packages-component'
import etch from 'etch';
import IncompatiblePackagesComponent from '../lib/incompatible-packages-component';
describe('IncompatiblePackagesComponent', () => {
let packages, etchScheduler
let packages, etchScheduler;
beforeEach(() => {
etchScheduler = etch.getScheduler()
etchScheduler = etch.getScheduler();
packages = [
{
name: 'incompatible-1',
isCompatible () {
return false
isCompatible() {
return false;
},
rebuild: function () {
return new Promise(resolve => (this.resolveRebuild = resolve))
rebuild: function() {
return new Promise(resolve => (this.resolveRebuild = resolve));
},
getBuildFailureOutput () {
return null
getBuildFailureOutput() {
return null;
},
path: '/Users/joe/.atom/packages/incompatible-1',
metadata: {
@ -33,14 +33,14 @@ describe('IncompatiblePackagesComponent', () => {
},
{
name: 'incompatible-2',
isCompatible () {
return false
isCompatible() {
return false;
},
rebuild () {
return new Promise(resolve => (this.resolveRebuild = resolve))
rebuild() {
return new Promise(resolve => (this.resolveRebuild = resolve));
},
getBuildFailureOutput () {
return null
getBuildFailureOutput() {
return null;
},
path: '/Users/joe/.atom/packages/incompatible-2',
metadata: {
@ -53,14 +53,14 @@ describe('IncompatiblePackagesComponent', () => {
},
{
name: 'compatible',
isCompatible () {
return true
isCompatible() {
return true;
},
rebuild () {
throw new Error('Should not rebuild a compatible package')
rebuild() {
throw new Error('Should not rebuild a compatible package');
},
getBuildFailureOutput () {
return null
getBuildFailureOutput() {
return null;
},
path: '/Users/joe/.atom/packages/b',
metadata: {
@ -69,8 +69,8 @@ describe('IncompatiblePackagesComponent', () => {
},
incompatibleModules: []
}
]
})
];
});
describe('when packages have not finished loading', () => {
it('delays rendering incompatible packages until the end of the tick', () => {
@ -78,69 +78,71 @@ describe('IncompatiblePackagesComponent', () => {
let component = new IncompatiblePackagesComponent({
getActivePackages: () => [],
getLoadedPackages: () => packages
})
let { element } = component
});
let { element } = component;
expect(
element.querySelectorAll('.incompatible-package').length
).toEqual(0)
).toEqual(0);
await etchScheduler.getNextUpdatePromise()
await etchScheduler.getNextUpdatePromise();
expect(
element.querySelectorAll('.incompatible-package').length
).toBeGreaterThan(0)
})
})
})
).toBeGreaterThan(0);
});
});
});
describe('when there are no incompatible packages', () => {
it('does not render incompatible packages or the rebuild button', () => {
waitsForPromise(async () => {
expect(packages[2].isCompatible()).toBe(true)
let compatiblePackages = [packages[2]]
expect(packages[2].isCompatible()).toBe(true);
let compatiblePackages = [packages[2]];
let component = new IncompatiblePackagesComponent({
getActivePackages: () => compatiblePackages,
getLoadedPackages: () => compatiblePackages
})
let { element } = component
});
let { element } = component;
await etchScheduler.getNextUpdatePromise()
await etchScheduler.getNextUpdatePromise();
expect(element.querySelectorAll('.incompatible-package').length).toBe(0)
expect(element.querySelector('button')).toBeNull()
})
})
})
expect(element.querySelectorAll('.incompatible-package').length).toBe(
0
);
expect(element.querySelector('button')).toBeNull();
});
});
});
describe('when some packages previously failed to rebuild', () => {
it('renders them with failed build status and error output', () => {
waitsForPromise(async () => {
packages[1].getBuildFailureOutput = function () {
return 'The build failed'
}
packages[1].getBuildFailureOutput = function() {
return 'The build failed';
};
let component = new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let { element } = component
});
let { element } = component;
await etchScheduler.getNextUpdatePromise()
await etchScheduler.getNextUpdatePromise();
let packageElement = element.querySelector(
'.incompatible-package:nth-child(2)'
)
);
expect(packageElement.querySelector('.badge').textContent).toBe(
'Rebuild Failed'
)
);
expect(packageElement.querySelector('pre').textContent).toBe(
'The build failed'
)
})
})
})
);
});
});
});
describe('when there are incompatible packages', () => {
it('renders incompatible packages and the rebuild button', () => {
@ -148,17 +150,17 @@ describe('IncompatiblePackagesComponent', () => {
let component = new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let { element } = component
});
let { element } = component;
await etchScheduler.getNextUpdatePromise()
await etchScheduler.getNextUpdatePromise();
expect(
element.querySelectorAll('.incompatible-package').length
).toEqual(2)
expect(element.querySelector('button')).not.toBeNull()
})
})
).toEqual(2);
expect(element.querySelector('button')).not.toBeNull();
});
});
describe('when the "Rebuild All" button is clicked', () => {
it("rebuilds every incompatible package, updating each package's view with status", () => {
@ -166,94 +168,94 @@ describe('IncompatiblePackagesComponent', () => {
let component = new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let { element } = component
jasmine.attachToDOM(element)
});
let { element } = component;
jasmine.attachToDOM(element);
await etchScheduler.getNextUpdatePromise()
await etchScheduler.getNextUpdatePromise();
component.refs.rebuildButton.dispatchEvent(
new CustomEvent('click', { bubbles: true })
)
await etchScheduler.getNextUpdatePromise() // view update
);
await etchScheduler.getNextUpdatePromise(); // view update
expect(component.refs.rebuildButton.disabled).toBe(true)
expect(component.refs.rebuildButton.disabled).toBe(true);
expect(packages[0].resolveRebuild).toBeDefined()
expect(packages[0].resolveRebuild).toBeDefined();
expect(
element.querySelector('.incompatible-package:nth-child(1) .badge')
.textContent
).toBe('Rebuilding')
).toBe('Rebuilding');
expect(
element.querySelector('.incompatible-package:nth-child(2) .badge')
).toBeNull()
).toBeNull();
packages[0].resolveRebuild({ code: 0 }) // simulate rebuild success
await etchScheduler.getNextUpdatePromise() // view update
packages[0].resolveRebuild({ code: 0 }); // simulate rebuild success
await etchScheduler.getNextUpdatePromise(); // view update
expect(packages[1].resolveRebuild).toBeDefined()
expect(packages[1].resolveRebuild).toBeDefined();
expect(
element.querySelector('.incompatible-package:nth-child(1) .badge')
.textContent
).toBe('Rebuild Succeeded')
).toBe('Rebuild Succeeded');
expect(
element.querySelector('.incompatible-package:nth-child(2) .badge')
.textContent
).toBe('Rebuilding')
).toBe('Rebuilding');
packages[1].resolveRebuild({
code: 12,
stderr: 'This is an error from the test!'
}) // simulate rebuild failure
await etchScheduler.getNextUpdatePromise() // view update
}); // simulate rebuild failure
await etchScheduler.getNextUpdatePromise(); // view update
expect(
element.querySelector('.incompatible-package:nth-child(1) .badge')
.textContent
).toBe('Rebuild Succeeded')
).toBe('Rebuild Succeeded');
expect(
element.querySelector('.incompatible-package:nth-child(2) .badge')
.textContent
).toBe('Rebuild Failed')
).toBe('Rebuild Failed');
expect(
element.querySelector('.incompatible-package:nth-child(2) pre')
.textContent
).toBe('This is an error from the test!')
})
})
).toBe('This is an error from the test!');
});
});
it('displays a prompt to reload Atom when the packages finish rebuilding', () => {
waitsForPromise(async () => {
let component = new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let { element } = component
jasmine.attachToDOM(element)
await etchScheduler.getNextUpdatePromise() // view update
});
let { element } = component;
jasmine.attachToDOM(element);
await etchScheduler.getNextUpdatePromise(); // view update
component.refs.rebuildButton.dispatchEvent(
new CustomEvent('click', { bubbles: true })
)
expect(packages[0].resolveRebuild({ code: 0 }))
await new Promise(global.setImmediate)
expect(packages[1].resolveRebuild({ code: 0 }))
);
expect(packages[0].resolveRebuild({ code: 0 }));
await new Promise(global.setImmediate);
expect(packages[1].resolveRebuild({ code: 0 }));
await etchScheduler.getNextUpdatePromise() // view update
await etchScheduler.getNextUpdatePromise(); // view update
expect(component.refs.reloadButton).toBeDefined()
expect(element.querySelector('.alert').textContent).toMatch(/2 of 2/)
expect(component.refs.reloadButton).toBeDefined();
expect(element.querySelector('.alert').textContent).toMatch(/2 of 2/);
spyOn(atom, 'reload')
spyOn(atom, 'reload');
component.refs.reloadButton.dispatchEvent(
new CustomEvent('click', { bubbles: true })
)
expect(atom.reload).toHaveBeenCalled()
})
})
})
);
expect(atom.reload).toHaveBeenCalled();
});
});
});
describe('when the "Package Settings" button is clicked', () => {
it('opens the settings panel for the package', () => {
@ -261,21 +263,21 @@ describe('IncompatiblePackagesComponent', () => {
let component = new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let { element } = component
jasmine.attachToDOM(element)
});
let { element } = component;
jasmine.attachToDOM(element);
await etchScheduler.getNextUpdatePromise()
await etchScheduler.getNextUpdatePromise();
spyOn(atom.workspace, 'open')
spyOn(atom.workspace, 'open');
element
.querySelector('.incompatible-package:nth-child(2) button')
.dispatchEvent(new CustomEvent('click', { bubbles: true }))
.dispatchEvent(new CustomEvent('click', { bubbles: true }));
expect(atom.workspace.open).toHaveBeenCalledWith(
'atom://config/packages/incompatible-2'
)
})
})
})
})
})
);
});
});
});
});
});

View File

@ -1,81 +1,83 @@
/** @babel */
import path from 'path'
import IncompatiblePackagesComponent from '../lib/incompatible-packages-component'
import StatusIconComponent from '../lib/status-icon-component'
import path from 'path';
import IncompatiblePackagesComponent from '../lib/incompatible-packages-component';
import StatusIconComponent from '../lib/status-icon-component';
// This exists only so that CI passes on both Atom 1.6 and Atom 1.8+.
function findStatusBar () {
function findStatusBar() {
if (typeof atom.workspace.getFooterPanels === 'function') {
const footerPanels = atom.workspace.getFooterPanels()
const footerPanels = atom.workspace.getFooterPanels();
if (footerPanels.length > 0) {
return footerPanels[0].getItem()
return footerPanels[0].getItem();
}
}
return atom.workspace.getBottomPanels()[0].getItem()
return atom.workspace.getBottomPanels()[0].getItem();
}
describe('Incompatible packages', () => {
let statusBar
let statusBar;
beforeEach(() => {
atom.views.getView(atom.workspace)
atom.views.getView(atom.workspace);
waitsForPromise(() => atom.packages.activatePackage('status-bar'))
waitsForPromise(() => atom.packages.activatePackage('status-bar'));
runs(() => {
statusBar = findStatusBar()
})
})
statusBar = findStatusBar();
});
});
describe('when there are packages with incompatible native modules', () => {
beforeEach(() => {
let incompatiblePackage = atom.packages.loadPackage(
path.join(__dirname, 'fixtures', 'incompatible-package')
)
spyOn(incompatiblePackage, 'isCompatible').andReturn(false)
incompatiblePackage.incompatibleModules = []
);
spyOn(incompatiblePackage, 'isCompatible').andReturn(false);
incompatiblePackage.incompatibleModules = [];
waitsForPromise(() =>
atom.packages.activatePackage('incompatible-packages')
)
);
waits(1)
})
waits(1);
});
it('adds an icon to the status bar', () => {
let statusBarIcon = statusBar.getRightTiles()[0].getItem()
expect(statusBarIcon.constructor).toBe(StatusIconComponent)
})
let statusBarIcon = statusBar.getRightTiles()[0].getItem();
expect(statusBarIcon.constructor).toBe(StatusIconComponent);
});
describe('clicking the icon', () => {
it('displays the incompatible packages view in a pane', () => {
let statusBarIcon = statusBar.getRightTiles()[0].getItem()
statusBarIcon.element.dispatchEvent(new MouseEvent('click'))
let statusBarIcon = statusBar.getRightTiles()[0].getItem();
statusBarIcon.element.dispatchEvent(new MouseEvent('click'));
let activePaneItem
waitsFor(() => (activePaneItem = atom.workspace.getActivePaneItem()))
let activePaneItem;
waitsFor(() => (activePaneItem = atom.workspace.getActivePaneItem()));
runs(() => {
expect(activePaneItem.constructor).toBe(IncompatiblePackagesComponent)
})
})
})
})
expect(activePaneItem.constructor).toBe(
IncompatiblePackagesComponent
);
});
});
});
});
describe('when there are no packages with incompatible native modules', () => {
beforeEach(() => {
waitsForPromise(() =>
atom.packages.activatePackage('incompatible-packages')
)
})
);
});
it('does not add an icon to the status bar', () => {
let statusBarItemClasses = statusBar
.getRightTiles()
.map(tile => tile.getItem().className)
.map(tile => tile.getItem().className);
expect(statusBarItemClasses).not.toContain('incompatible-packages')
})
})
})
expect(statusBarItemClasses).not.toContain('incompatible-packages');
});
});
});

View File

@ -1,7 +1,7 @@
'use babel'
'use babel';
export default {
getProcessPlatform () {
return process.platform
getProcessPlatform() {
return process.platform;
}
}
};

View File

@ -1,26 +1,26 @@
'use babel'
'use babel';
import _ from 'underscore-plus'
import { CompositeDisposable, Disposable } from 'atom'
import SelectListView from 'atom-select-list'
import StatusBarItem from './status-bar-item'
import helpers from './helpers'
import _ from 'underscore-plus';
import { CompositeDisposable, Disposable } from 'atom';
import SelectListView from 'atom-select-list';
import StatusBarItem from './status-bar-item';
import helpers from './helpers';
const LineEndingRegExp = /\r\n|\n/g
const LineEndingRegExp = /\r\n|\n/g;
// the following regular expression is executed natively via the `substring` package,
// where `\A` corresponds to the beginning of the string.
// More info: https://github.com/atom/line-ending-selector/pull/56
// eslint-disable-next-line no-useless-escape
const LFRegExp = /(\A|[^\r])\n/g
const CRLFRegExp = /\r\n/g
const LFRegExp = /(\A|[^\r])\n/g;
const CRLFRegExp = /\r\n/g;
let disposables = null
let modalPanel = null
let lineEndingListView = null
let disposables = null;
let modalPanel = null;
let lineEndingListView = null;
export function activate () {
disposables = new CompositeDisposable()
export function activate() {
disposables = new CompositeDisposable();
disposables.add(
atom.commands.add('atom-text-editor', {
@ -36,161 +36,161 @@ export function activate () {
setLineEnding(
atom.workspace.getActiveTextEditor(),
lineEnding.value
)
modalPanel.hide()
);
modalPanel.hide();
},
didCancelSelection: () => {
modalPanel.hide()
modalPanel.hide();
},
elementForItem: lineEnding => {
const element = document.createElement('li')
element.textContent = lineEnding.name
return element
const element = document.createElement('li');
element.textContent = lineEnding.name;
return element;
}
})
});
modalPanel = atom.workspace.addModalPanel({
item: lineEndingListView
})
});
disposables.add(
new Disposable(() => {
lineEndingListView.destroy()
modalPanel.destroy()
modalPanel = null
lineEndingListView.destroy();
modalPanel.destroy();
modalPanel = null;
})
)
);
}
lineEndingListView.reset()
modalPanel.show()
lineEndingListView.focus()
lineEndingListView.reset();
modalPanel.show();
lineEndingListView.focus();
},
'line-ending-selector:convert-to-LF': event => {
const editorElement = event.target.closest('atom-text-editor')
setLineEnding(editorElement.getModel(), '\n')
const editorElement = event.target.closest('atom-text-editor');
setLineEnding(editorElement.getModel(), '\n');
},
'line-ending-selector:convert-to-CRLF': event => {
const editorElement = event.target.closest('atom-text-editor')
setLineEnding(editorElement.getModel(), '\r\n')
const editorElement = event.target.closest('atom-text-editor');
setLineEnding(editorElement.getModel(), '\r\n');
}
})
)
);
}
export function deactivate () {
disposables.dispose()
export function deactivate() {
disposables.dispose();
}
export function consumeStatusBar (statusBar) {
let statusBarItem = new StatusBarItem()
let currentBufferDisposable = null
let tooltipDisposable = null
export function consumeStatusBar(statusBar) {
let statusBarItem = new StatusBarItem();
let currentBufferDisposable = null;
let tooltipDisposable = null;
const updateTile = _.debounce(buffer => {
getLineEndings(buffer).then(lineEndings => {
if (lineEndings.size === 0) {
let defaultLineEnding = getDefaultLineEnding()
buffer.setPreferredLineEnding(defaultLineEnding)
lineEndings = new Set().add(defaultLineEnding)
let defaultLineEnding = getDefaultLineEnding();
buffer.setPreferredLineEnding(defaultLineEnding);
lineEndings = new Set().add(defaultLineEnding);
}
statusBarItem.setLineEndings(lineEndings)
})
}, 0)
statusBarItem.setLineEndings(lineEndings);
});
}, 0);
disposables.add(
atom.workspace.observeActiveTextEditor(editor => {
if (currentBufferDisposable) currentBufferDisposable.dispose()
if (currentBufferDisposable) currentBufferDisposable.dispose();
if (editor && editor.getBuffer) {
let buffer = editor.getBuffer()
updateTile(buffer)
let buffer = editor.getBuffer();
updateTile(buffer);
currentBufferDisposable = buffer.onDidChange(({ oldText, newText }) => {
if (!statusBarItem.hasLineEnding('\n')) {
if (newText.indexOf('\n') >= 0) {
updateTile(buffer)
updateTile(buffer);
}
} else if (!statusBarItem.hasLineEnding('\r\n')) {
if (newText.indexOf('\r\n') >= 0) {
updateTile(buffer)
updateTile(buffer);
}
} else if (oldText.indexOf('\n')) {
updateTile(buffer)
updateTile(buffer);
}
})
});
} else {
statusBarItem.setLineEndings(new Set())
currentBufferDisposable = null
statusBarItem.setLineEndings(new Set());
currentBufferDisposable = null;
}
if (tooltipDisposable) {
disposables.remove(tooltipDisposable)
tooltipDisposable.dispose()
disposables.remove(tooltipDisposable);
tooltipDisposable.dispose();
}
tooltipDisposable = atom.tooltips.add(statusBarItem.element, {
title () {
return `File uses ${statusBarItem.description()} line endings`
title() {
return `File uses ${statusBarItem.description()} line endings`;
}
});
disposables.add(tooltipDisposable);
})
disposables.add(tooltipDisposable)
})
)
);
disposables.add(
new Disposable(() => {
if (currentBufferDisposable) currentBufferDisposable.dispose()
if (currentBufferDisposable) currentBufferDisposable.dispose();
})
)
);
statusBarItem.onClick(() => {
const editor = atom.workspace.getActiveTextEditor()
const editor = atom.workspace.getActiveTextEditor();
atom.commands.dispatch(
atom.views.getView(editor),
'line-ending-selector:show'
)
})
);
});
let tile = statusBar.addRightTile({ item: statusBarItem, priority: 200 })
disposables.add(new Disposable(() => tile.destroy()))
let tile = statusBar.addRightTile({ item: statusBarItem, priority: 200 });
disposables.add(new Disposable(() => tile.destroy()));
}
function getDefaultLineEnding () {
function getDefaultLineEnding() {
switch (atom.config.get('line-ending-selector.defaultLineEnding')) {
case 'LF':
return '\n'
return '\n';
case 'CRLF':
return '\r\n'
return '\r\n';
case 'OS Default':
default:
return helpers.getProcessPlatform() === 'win32' ? '\r\n' : '\n'
return helpers.getProcessPlatform() === 'win32' ? '\r\n' : '\n';
}
}
function getLineEndings (buffer) {
function getLineEndings(buffer) {
if (typeof buffer.find === 'function') {
return Promise.all([buffer.find(LFRegExp), buffer.find(CRLFRegExp)]).then(
([hasLF, hasCRLF]) => {
const result = new Set()
if (hasLF) result.add('\n')
if (hasCRLF) result.add('\r\n')
return result
const result = new Set();
if (hasLF) result.add('\n');
if (hasCRLF) result.add('\r\n');
return result;
}
)
);
} else {
return new Promise(resolve => {
const result = new Set()
const result = new Set();
for (let i = 0; i < buffer.getLineCount() - 1; i++) {
result.add(buffer.lineEndingForRow(i))
result.add(buffer.lineEndingForRow(i));
}
resolve(result)
})
resolve(result);
});
}
}
function setLineEnding (item, lineEnding) {
function setLineEnding(item, lineEnding) {
if (item && item.getBuffer) {
let buffer = item.getBuffer()
buffer.setPreferredLineEnding(lineEnding)
buffer.setText(buffer.getText().replace(LineEndingRegExp, lineEnding))
let buffer = item.getBuffer();
buffer.setPreferredLineEnding(lineEnding);
buffer.setText(buffer.getText().replace(LineEndingRegExp, lineEnding));
}
}

View File

@ -1,57 +1,57 @@
const { Emitter } = require('atom')
const { Emitter } = require('atom');
module.exports = class StatusBarItem {
constructor () {
this.element = document.createElement('a')
this.element.className = 'line-ending-tile inline-block'
this.emitter = new Emitter()
this.setLineEndings(new Set())
constructor() {
this.element = document.createElement('a');
this.element.className = 'line-ending-tile inline-block';
this.emitter = new Emitter();
this.setLineEndings(new Set());
}
setLineEndings (lineEndings) {
this.lineEndings = lineEndings
this.element.textContent = lineEndingName(lineEndings)
this.emitter.emit('did-change')
setLineEndings(lineEndings) {
this.lineEndings = lineEndings;
this.element.textContent = lineEndingName(lineEndings);
this.emitter.emit('did-change');
}
onDidChange (callback) {
return this.emitter.on('did-change', callback)
onDidChange(callback) {
return this.emitter.on('did-change', callback);
}
hasLineEnding (lineEnding) {
return this.lineEndings.has(lineEnding)
hasLineEnding(lineEnding) {
return this.lineEndings.has(lineEnding);
}
description () {
return lineEndingDescription(this.lineEndings)
description() {
return lineEndingDescription(this.lineEndings);
}
onClick (callback) {
this.element.addEventListener('click', callback)
onClick(callback) {
this.element.addEventListener('click', callback);
}
}
};
function lineEndingName (lineEndings) {
function lineEndingName(lineEndings) {
if (lineEndings.size > 1) {
return 'Mixed'
return 'Mixed';
} else if (lineEndings.has('\n')) {
return 'LF'
return 'LF';
} else if (lineEndings.has('\r\n')) {
return 'CRLF'
return 'CRLF';
} else {
return ''
return '';
}
}
function lineEndingDescription (lineEndings) {
function lineEndingDescription(lineEndings) {
switch (lineEndingName(lineEndings)) {
case 'Mixed':
return 'mixed'
return 'mixed';
case 'LF':
return 'LF (Unix)'
return 'LF (Unix)';
case 'CRLF':
return 'CRLF (Windows)'
return 'CRLF (Windows)';
default:
return 'unknown'
return 'unknown';
}
}

View File

@ -1,374 +1,378 @@
const helpers = require('../lib/helpers')
const { TextEditor } = require('atom')
const helpers = require('../lib/helpers');
const { TextEditor } = require('atom');
describe('line ending selector', () => {
let lineEndingTile
let lineEndingTile;
beforeEach(() => {
jasmine.useRealClock()
jasmine.useRealClock();
waitsForPromise(() => {
return atom.packages.activatePackage('status-bar')
})
return atom.packages.activatePackage('status-bar');
});
waitsForPromise(() => {
return atom.packages.activatePackage('line-ending-selector')
})
return atom.packages.activatePackage('line-ending-selector');
});
waits(1)
waits(1);
runs(() => {
const statusBar = atom.workspace.getFooterPanels()[0].getItem()
lineEndingTile = statusBar.getRightTiles()[0].getItem()
expect(lineEndingTile.element.className).toMatch(/line-ending-tile/)
expect(lineEndingTile.element.textContent).toBe('')
})
})
const statusBar = atom.workspace.getFooterPanels()[0].getItem();
lineEndingTile = statusBar.getRightTiles()[0].getItem();
expect(lineEndingTile.element.className).toMatch(/line-ending-tile/);
expect(lineEndingTile.element.textContent).toBe('');
});
});
describe('Commands', () => {
let editor, editorElement
let editor, editorElement;
beforeEach(() => {
waitsForPromise(() => {
return atom.workspace.open('mixed-endings.md').then(e => {
editor = e
editorElement = atom.views.getView(editor)
jasmine.attachToDOM(editorElement)
})
})
})
editor = e;
editorElement = atom.views.getView(editor);
jasmine.attachToDOM(editorElement);
});
});
});
describe('When "line-ending-selector:convert-to-LF" is run', () => {
it('converts the file to LF line endings', () => {
editorElement.focus()
editorElement.focus();
atom.commands.dispatch(
document.activeElement,
'line-ending-selector:convert-to-LF'
)
expect(editor.getText()).toBe('Hello\nGoodbye\nMixed\n')
})
})
);
expect(editor.getText()).toBe('Hello\nGoodbye\nMixed\n');
});
});
describe('When "line-ending-selector:convert-to-LF" is run', () => {
it('converts the file to CRLF line endings', () => {
editorElement.focus()
editorElement.focus();
atom.commands.dispatch(
document.activeElement,
'line-ending-selector:convert-to-CRLF'
)
expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nMixed\r\n')
})
})
})
);
expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nMixed\r\n');
});
});
});
describe('Status bar tile', () => {
describe('when an empty file is opened', () => {
it('uses the default line endings for the platform', () => {
waitsFor(done => {
spyOn(helpers, 'getProcessPlatform').andReturn('win32')
spyOn(helpers, 'getProcessPlatform').andReturn('win32');
atom.workspace.open('').then(editor => {
const subscription = lineEndingTile.onDidChange(() => {
subscription.dispose()
expect(lineEndingTile.element.textContent).toBe('CRLF')
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n')
subscription.dispose();
expect(lineEndingTile.element.textContent).toBe('CRLF');
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n');
expect(getTooltipText(lineEndingTile.element)).toBe(
'File uses CRLF (Windows) line endings'
)
);
done()
})
})
})
done();
});
});
});
waitsFor(done => {
helpers.getProcessPlatform.andReturn('darwin')
helpers.getProcessPlatform.andReturn('darwin');
atom.workspace.open('').then(editor => {
const subscription = lineEndingTile.onDidChange(() => {
subscription.dispose()
expect(lineEndingTile.element.textContent).toBe('LF')
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n')
subscription.dispose();
expect(lineEndingTile.element.textContent).toBe('LF');
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n');
expect(getTooltipText(lineEndingTile.element)).toBe(
'File uses LF (Unix) line endings'
)
);
done()
})
})
})
})
done();
});
});
});
});
describe('when the "defaultLineEnding" setting is set to "LF"', () => {
beforeEach(() => {
atom.config.set('line-ending-selector.defaultLineEnding', 'LF')
})
atom.config.set('line-ending-selector.defaultLineEnding', 'LF');
});
it('uses LF line endings, regardless of the platform', () => {
waitsFor(done => {
spyOn(helpers, 'getProcessPlatform').andReturn('win32')
spyOn(helpers, 'getProcessPlatform').andReturn('win32');
atom.workspace.open('').then(editor => {
lineEndingTile.onDidChange(() => {
expect(lineEndingTile.element.textContent).toBe('LF')
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n')
done()
})
})
})
})
})
expect(lineEndingTile.element.textContent).toBe('LF');
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n');
done();
});
});
});
});
});
describe('when the "defaultLineEnding" setting is set to "CRLF"', () => {
beforeEach(() => {
atom.config.set('line-ending-selector.defaultLineEnding', 'CRLF')
})
atom.config.set('line-ending-selector.defaultLineEnding', 'CRLF');
});
it('uses CRLF line endings, regardless of the platform', () => {
waitsFor(done => {
atom.workspace.open('').then(editor => {
lineEndingTile.onDidChange(() => {
expect(lineEndingTile.element.textContent).toBe('CRLF')
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n')
done()
})
})
})
})
})
})
expect(lineEndingTile.element.textContent).toBe('CRLF');
expect(editor.getBuffer().getPreferredLineEnding()).toBe(
'\r\n'
);
done();
});
});
});
});
});
});
describe('when a file is opened that contains only CRLF line endings', () => {
it('displays "CRLF" as the line ending', () => {
waitsFor(done => {
atom.workspace.open('windows-endings.md').then(() => {
lineEndingTile.onDidChange(() => {
expect(lineEndingTile.element.textContent).toBe('CRLF')
done()
})
})
})
})
})
expect(lineEndingTile.element.textContent).toBe('CRLF');
done();
});
});
});
});
});
describe('when a file is opened that contains only LF line endings', () => {
it('displays "LF" as the line ending', () => {
waitsFor(done => {
atom.workspace.open('unix-endings.md').then(editor => {
lineEndingTile.onDidChange(() => {
expect(lineEndingTile.element.textContent).toBe('LF')
expect(editor.getBuffer().getPreferredLineEnding()).toBe(null)
done()
})
})
})
})
})
expect(lineEndingTile.element.textContent).toBe('LF');
expect(editor.getBuffer().getPreferredLineEnding()).toBe(null);
done();
});
});
});
});
});
describe('when a file is opened that contains mixed line endings', () => {
it('displays "Mixed" as the line ending', () => {
waitsFor(done => {
atom.workspace.open('mixed-endings.md').then(() => {
lineEndingTile.onDidChange(() => {
expect(lineEndingTile.element.textContent).toBe('Mixed')
done()
})
})
})
})
})
expect(lineEndingTile.element.textContent).toBe('Mixed');
done();
});
});
});
});
});
describe('clicking the tile', () => {
let lineEndingModal, lineEndingSelector
let lineEndingModal, lineEndingSelector;
beforeEach(() => {
jasmine.attachToDOM(atom.views.getView(atom.workspace))
jasmine.attachToDOM(atom.views.getView(atom.workspace));
waitsFor(done =>
atom.workspace
.open('unix-endings.md')
.then(() => lineEndingTile.onDidChange(done))
)
})
);
});
describe('when the text editor has focus', () => {
it('opens the line ending selector modal for the text editor', () => {
atom.workspace.getCenter().activate()
const item = atom.workspace.getActivePaneItem()
expect(item.getFileName && item.getFileName()).toBe('unix-endings.md')
atom.workspace.getCenter().activate();
const item = atom.workspace.getActivePaneItem();
expect(item.getFileName && item.getFileName()).toBe(
'unix-endings.md'
);
lineEndingTile.element.dispatchEvent(new MouseEvent('click', {}))
lineEndingModal = atom.workspace.getModalPanels()[0]
lineEndingSelector = lineEndingModal.getItem()
lineEndingTile.element.dispatchEvent(new MouseEvent('click', {}));
lineEndingModal = atom.workspace.getModalPanels()[0];
lineEndingSelector = lineEndingModal.getItem();
expect(lineEndingModal.isVisible()).toBe(true)
expect(lineEndingModal.isVisible()).toBe(true);
expect(
lineEndingSelector.element.contains(document.activeElement)
).toBe(true)
let listItems = lineEndingSelector.element.querySelectorAll('li')
expect(listItems[0].textContent).toBe('LF')
expect(listItems[1].textContent).toBe('CRLF')
})
})
).toBe(true);
let listItems = lineEndingSelector.element.querySelectorAll('li');
expect(listItems[0].textContent).toBe('LF');
expect(listItems[1].textContent).toBe('CRLF');
});
});
describe('when the text editor does not have focus', () => {
it('opens the line ending selector modal for the active text editor', () => {
atom.workspace.getLeftDock().activate()
const item = atom.workspace.getActivePaneItem()
expect(item instanceof TextEditor).toBe(false)
atom.workspace.getLeftDock().activate();
const item = atom.workspace.getActivePaneItem();
expect(item instanceof TextEditor).toBe(false);
lineEndingTile.element.dispatchEvent(new MouseEvent('click', {}))
lineEndingModal = atom.workspace.getModalPanels()[0]
lineEndingSelector = lineEndingModal.getItem()
lineEndingTile.element.dispatchEvent(new MouseEvent('click', {}));
lineEndingModal = atom.workspace.getModalPanels()[0];
lineEndingSelector = lineEndingModal.getItem();
expect(lineEndingModal.isVisible()).toBe(true)
expect(lineEndingModal.isVisible()).toBe(true);
expect(
lineEndingSelector.element.contains(document.activeElement)
).toBe(true)
let listItems = lineEndingSelector.element.querySelectorAll('li')
expect(listItems[0].textContent).toBe('LF')
expect(listItems[1].textContent).toBe('CRLF')
})
})
).toBe(true);
let listItems = lineEndingSelector.element.querySelectorAll('li');
expect(listItems[0].textContent).toBe('LF');
expect(listItems[1].textContent).toBe('CRLF');
});
});
describe('when selecting a different line ending for the file', () => {
it('changes the line endings in the buffer', () => {
lineEndingTile.element.dispatchEvent(new MouseEvent('click', {}))
lineEndingModal = atom.workspace.getModalPanels()[0]
lineEndingSelector = lineEndingModal.getItem()
lineEndingTile.element.dispatchEvent(new MouseEvent('click', {}));
lineEndingModal = atom.workspace.getModalPanels()[0];
lineEndingSelector = lineEndingModal.getItem();
const lineEndingChangedPromise = new Promise(resolve => {
lineEndingTile.onDidChange(() => {
expect(lineEndingTile.element.textContent).toBe('CRLF')
const editor = atom.workspace.getActiveTextEditor()
expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nUnix\r\n')
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n')
resolve()
})
})
expect(lineEndingTile.element.textContent).toBe('CRLF');
const editor = atom.workspace.getActiveTextEditor();
expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nUnix\r\n');
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n');
resolve();
});
});
lineEndingSelector.refs.queryEditor.setText('CR')
lineEndingSelector.confirmSelection()
expect(lineEndingModal.isVisible()).toBe(false)
lineEndingSelector.refs.queryEditor.setText('CR');
lineEndingSelector.confirmSelection();
expect(lineEndingModal.isVisible()).toBe(false);
waitsForPromise(() => lineEndingChangedPromise)
})
})
waitsForPromise(() => lineEndingChangedPromise);
});
});
describe('when modal is exited', () => {
it('leaves the tile selection as-is', () => {
lineEndingTile.element.dispatchEvent(new MouseEvent('click', {}))
lineEndingModal = atom.workspace.getModalPanels()[0]
lineEndingSelector = lineEndingModal.getItem()
lineEndingTile.element.dispatchEvent(new MouseEvent('click', {}));
lineEndingModal = atom.workspace.getModalPanels()[0];
lineEndingSelector = lineEndingModal.getItem();
lineEndingSelector.cancelSelection()
expect(lineEndingTile.element.textContent).toBe('LF')
})
})
})
lineEndingSelector.cancelSelection();
expect(lineEndingTile.element.textContent).toBe('LF');
});
});
});
describe('closing the last text editor', () => {
it('displays no line ending in the status bar', () => {
waitsForPromise(() => {
return atom.workspace.open('unix-endings.md').then(() => {
atom.workspace.getActivePane().destroy()
expect(lineEndingTile.element.textContent).toBe('')
})
})
})
})
atom.workspace.getActivePane().destroy();
expect(lineEndingTile.element.textContent).toBe('');
});
});
});
});
describe("when the buffer's line endings change", () => {
let editor
let editor;
beforeEach(() => {
waitsFor(done => {
atom.workspace.open('unix-endings.md').then(e => {
editor = e
lineEndingTile.onDidChange(done)
})
})
})
editor = e;
lineEndingTile.onDidChange(done);
});
});
});
it('updates the line ending text in the tile', () => {
let tileText = lineEndingTile.element.textContent
let tileUpdateCount = 0
let tileText = lineEndingTile.element.textContent;
let tileUpdateCount = 0;
Object.defineProperty(lineEndingTile.element, 'textContent', {
get () {
return tileText
get() {
return tileText;
},
set (text) {
tileUpdateCount++
tileText = text
set(text) {
tileUpdateCount++;
tileText = text;
}
})
});
expect(lineEndingTile.element.textContent).toBe('LF')
expect(lineEndingTile.element.textContent).toBe('LF');
expect(getTooltipText(lineEndingTile.element)).toBe(
'File uses LF (Unix) line endings'
)
);
waitsFor(done => {
editor.setTextInBufferRange([[0, 0], [0, 0]], '... ')
editor.setTextInBufferRange([[0, 0], [0, 0]], '... ');
editor.setTextInBufferRange([[0, Infinity], [1, 0]], '\r\n', {
normalizeLineEndings: false
})
lineEndingTile.onDidChange(done)
})
});
lineEndingTile.onDidChange(done);
});
runs(() => {
expect(tileUpdateCount).toBe(1)
expect(lineEndingTile.element.textContent).toBe('Mixed')
expect(tileUpdateCount).toBe(1);
expect(lineEndingTile.element.textContent).toBe('Mixed');
expect(getTooltipText(lineEndingTile.element)).toBe(
'File uses mixed line endings'
)
})
);
});
waitsFor(done => {
atom.commands.dispatch(
editor.getElement(),
'line-ending-selector:convert-to-CRLF'
)
lineEndingTile.onDidChange(done)
})
);
lineEndingTile.onDidChange(done);
});
runs(() => {
expect(tileUpdateCount).toBe(2)
expect(lineEndingTile.element.textContent).toBe('CRLF')
expect(tileUpdateCount).toBe(2);
expect(lineEndingTile.element.textContent).toBe('CRLF');
expect(getTooltipText(lineEndingTile.element)).toBe(
'File uses CRLF (Windows) line endings'
)
})
);
});
waitsFor(done => {
atom.commands.dispatch(
editor.getElement(),
'line-ending-selector:convert-to-LF'
)
lineEndingTile.onDidChange(done)
})
);
lineEndingTile.onDidChange(done);
});
runs(() => {
expect(tileUpdateCount).toBe(3)
expect(lineEndingTile.element.textContent).toBe('LF')
})
expect(tileUpdateCount).toBe(3);
expect(lineEndingTile.element.textContent).toBe('LF');
});
runs(() => {
editor.setTextInBufferRange([[0, 0], [0, 0]], '\n')
})
editor.setTextInBufferRange([[0, 0], [0, 0]], '\n');
});
waits(100)
waits(100);
runs(() => {
expect(tileUpdateCount).toBe(3)
})
})
})
})
})
expect(tileUpdateCount).toBe(3);
});
});
});
});
});
function getTooltipText (element) {
const [tooltip] = atom.tooltips.findTooltips(element)
return tooltip.getTitle()
function getTooltipText(element) {
const [tooltip] = atom.tooltips.findTooltips(element);
return tooltip.getTitle();
}

View File

@ -1,64 +1,64 @@
const url = require('url')
const { shell } = require('electron')
const _ = require('underscore-plus')
const url = require('url');
const { shell } = require('electron');
const _ = require('underscore-plus');
const LINK_SCOPE_REGEX = /markup\.underline\.link/
const LINK_SCOPE_REGEX = /markup\.underline\.link/;
module.exports = {
activate () {
activate() {
this.commandDisposable = atom.commands.add(
'atom-text-editor',
'link:open',
() => this.openLink()
)
);
},
deactivate () {
this.commandDisposable.dispose()
deactivate() {
this.commandDisposable.dispose();
},
openLink () {
const editor = atom.workspace.getActiveTextEditor()
if (editor == null) return
openLink() {
const editor = atom.workspace.getActiveTextEditor();
if (editor == null) return;
let link = this.linkUnderCursor(editor)
if (link == null) return
let link = this.linkUnderCursor(editor);
if (link == null) return;
if (editor.getGrammar().scopeName === 'source.gfm') {
link = this.linkForName(editor, link)
link = this.linkForName(editor, link);
}
const { protocol } = url.parse(link)
const { protocol } = url.parse(link);
if (protocol === 'http:' || protocol === 'https:' || protocol === 'atom:') {
shell.openExternal(link)
shell.openExternal(link);
}
},
// Get the link under the cursor in the editor
//
// Returns a {String} link or undefined if no link found.
linkUnderCursor (editor) {
const cursorPosition = editor.getCursorBufferPosition()
const link = this.linkAtPosition(editor, cursorPosition)
if (link != null) return link
linkUnderCursor(editor) {
const cursorPosition = editor.getCursorBufferPosition();
const link = this.linkAtPosition(editor, cursorPosition);
if (link != null) return link;
// Look for a link to the left of the cursor
if (cursorPosition.column > 0) {
return this.linkAtPosition(editor, cursorPosition.translate([0, -1]))
return this.linkAtPosition(editor, cursorPosition.translate([0, -1]));
}
},
// Get the link at the buffer position in the editor.
//
// Returns a {String} link or undefined if no link found.
linkAtPosition (editor, bufferPosition) {
const token = editor.tokenForBufferPosition(bufferPosition)
linkAtPosition(editor, bufferPosition) {
const token = editor.tokenForBufferPosition(bufferPosition);
if (
token &&
token.value &&
token.scopes.some(scope => LINK_SCOPE_REGEX.test(scope))
) {
return token.value
return token.value;
}
},
@ -73,20 +73,20 @@ module.exports = {
// ```
//
// Returns a {String} link
linkForName (editor, linkName) {
let link = linkName
linkForName(editor, linkName) {
let link = linkName;
const regex = new RegExp(
`^\\s*\\[${_.escapeRegExp(linkName)}\\]\\s*:\\s*(.+)$`,
'g'
)
);
editor.backwardsScanInBufferRange(
regex,
[[0, 0], [Infinity, Infinity]],
({ match, stop }) => {
link = match[1]
stop()
link = match[1];
stop();
}
)
return link
);
return link;
}
}
};

View File

@ -1,136 +1,136 @@
const { shell } = require('electron')
const { shell } = require('electron');
describe('link package', () => {
beforeEach(async () => {
await atom.packages.activatePackage('language-gfm')
await atom.packages.activatePackage('language-hyperlink')
await atom.packages.activatePackage('language-gfm');
await atom.packages.activatePackage('language-hyperlink');
const activationPromise = atom.packages.activatePackage('link')
atom.commands.dispatch(atom.views.getView(atom.workspace), 'link:open')
await activationPromise
})
const activationPromise = atom.packages.activatePackage('link');
atom.commands.dispatch(atom.views.getView(atom.workspace), 'link:open');
await activationPromise;
});
describe('when the cursor is on a link', () => {
it("opens the link using the 'open' command", async () => {
await atom.workspace.open('sample.md')
await atom.workspace.open('sample.md');
const editor = atom.workspace.getActiveTextEditor()
editor.setText('// "http://github.com"')
const editor = atom.workspace.getActiveTextEditor();
editor.setText('// "http://github.com"');
spyOn(shell, 'openExternal')
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
expect(shell.openExternal).not.toHaveBeenCalled()
spyOn(shell, 'openExternal');
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).not.toHaveBeenCalled();
editor.setCursorBufferPosition([0, 4])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
editor.setCursorBufferPosition([0, 4]);
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com')
expect(shell.openExternal).toHaveBeenCalled();
expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com');
shell.openExternal.reset()
editor.setCursorBufferPosition([0, 8])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
shell.openExternal.reset();
editor.setCursorBufferPosition([0, 8]);
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com')
expect(shell.openExternal).toHaveBeenCalled();
expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com');
shell.openExternal.reset()
editor.setCursorBufferPosition([0, 21])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
shell.openExternal.reset();
editor.setCursorBufferPosition([0, 21]);
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com')
})
expect(shell.openExternal).toHaveBeenCalled();
expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com');
});
// only works in Atom >= 1.33.0
// https://github.com/atom/link/pull/33#issuecomment-419643655
const atomVersion = atom.getVersion().split('.')
console.error('atomVersion', atomVersion)
const atomVersion = atom.getVersion().split('.');
console.error('atomVersion', atomVersion);
if (+atomVersion[0] > 1 || +atomVersion[1] >= 33) {
it("opens an 'atom:' link", async () => {
await atom.workspace.open('sample.md')
await atom.workspace.open('sample.md');
const editor = atom.workspace.getActiveTextEditor()
const editor = atom.workspace.getActiveTextEditor();
editor.setText(
'// "atom://core/open/file?filename=sample.js&line=1&column=2"'
)
);
spyOn(shell, 'openExternal')
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
expect(shell.openExternal).not.toHaveBeenCalled()
spyOn(shell, 'openExternal');
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).not.toHaveBeenCalled();
editor.setCursorBufferPosition([0, 4])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
editor.setCursorBufferPosition([0, 4]);
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal).toHaveBeenCalled();
expect(shell.openExternal.argsForCall[0][0]).toBe(
'atom://core/open/file?filename=sample.js&line=1&column=2'
)
);
shell.openExternal.reset()
editor.setCursorBufferPosition([0, 8])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
shell.openExternal.reset();
editor.setCursorBufferPosition([0, 8]);
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal).toHaveBeenCalled();
expect(shell.openExternal.argsForCall[0][0]).toBe(
'atom://core/open/file?filename=sample.js&line=1&column=2'
)
);
shell.openExternal.reset()
editor.setCursorBufferPosition([0, 60])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
shell.openExternal.reset();
editor.setCursorBufferPosition([0, 60]);
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal).toHaveBeenCalled();
expect(shell.openExternal.argsForCall[0][0]).toBe(
'atom://core/open/file?filename=sample.js&line=1&column=2'
)
})
);
});
}
describe('when the cursor is on a [name][url-name] style markdown link', () =>
it('opens the named url', async () => {
await atom.workspace.open('README.md')
await atom.workspace.open('README.md');
const editor = atom.workspace.getActiveTextEditor()
const editor = atom.workspace.getActiveTextEditor();
editor.setText(`\
you should [click][here]
you should not [click][her]
[here]: http://github.com\
`)
`);
spyOn(shell, 'openExternal')
editor.setCursorBufferPosition([0, 0])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
expect(shell.openExternal).not.toHaveBeenCalled()
spyOn(shell, 'openExternal');
editor.setCursorBufferPosition([0, 0]);
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).not.toHaveBeenCalled();
editor.setCursorBufferPosition([0, 20])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
editor.setCursorBufferPosition([0, 20]);
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com')
expect(shell.openExternal).toHaveBeenCalled();
expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com');
shell.openExternal.reset()
editor.setCursorBufferPosition([1, 24])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
shell.openExternal.reset();
editor.setCursorBufferPosition([1, 24]);
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).not.toHaveBeenCalled()
}))
expect(shell.openExternal).not.toHaveBeenCalled();
}));
it('does not open non http/https/atom links', async () => {
await atom.workspace.open('sample.md')
await atom.workspace.open('sample.md');
const editor = atom.workspace.getActiveTextEditor()
editor.setText('// ftp://github.com\n')
const editor = atom.workspace.getActiveTextEditor();
editor.setText('// ftp://github.com\n');
spyOn(shell, 'openExternal')
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
expect(shell.openExternal).not.toHaveBeenCalled()
spyOn(shell, 'openExternal');
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).not.toHaveBeenCalled();
editor.setCursorBufferPosition([0, 5])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
editor.setCursorBufferPosition([0, 5]);
atom.commands.dispatch(atom.views.getView(editor), 'link:open');
expect(shell.openExternal).not.toHaveBeenCalled()
})
})
})
expect(shell.openExternal).not.toHaveBeenCalled();
});
});
});

View File

@ -1,88 +1,88 @@
const root = document.documentElement
const themeName = 'one-dark-ui'
const root = document.documentElement;
const themeName = 'one-dark-ui';
module.exports = {
activate (state) {
atom.config.observe(`${themeName}.fontSize`, setFontSize)
atom.config.observe(`${themeName}.tabSizing`, setTabSizing)
atom.config.observe(`${themeName}.tabCloseButton`, setTabCloseButton)
atom.config.observe(`${themeName}.hideDockButtons`, setHideDockButtons)
atom.config.observe(`${themeName}.stickyHeaders`, setStickyHeaders)
activate(state) {
atom.config.observe(`${themeName}.fontSize`, setFontSize);
atom.config.observe(`${themeName}.tabSizing`, setTabSizing);
atom.config.observe(`${themeName}.tabCloseButton`, setTabCloseButton);
atom.config.observe(`${themeName}.hideDockButtons`, setHideDockButtons);
atom.config.observe(`${themeName}.stickyHeaders`, setStickyHeaders);
// DEPRECATED: This can be removed at some point (added in Atom 1.17/1.18ish)
// It removes `layoutMode`
if (atom.config.get(`${themeName}.layoutMode`)) {
atom.config.unset(`${themeName}.layoutMode`)
atom.config.unset(`${themeName}.layoutMode`);
}
},
deactivate () {
unsetFontSize()
unsetTabSizing()
unsetTabCloseButton()
unsetHideDockButtons()
unsetStickyHeaders()
deactivate() {
unsetFontSize();
unsetTabSizing();
unsetTabCloseButton();
unsetHideDockButtons();
unsetStickyHeaders();
}
}
};
// Font Size -----------------------
function setFontSize (currentFontSize) {
root.style.fontSize = `${currentFontSize}px`
function setFontSize(currentFontSize) {
root.style.fontSize = `${currentFontSize}px`;
}
function unsetFontSize () {
root.style.fontSize = ''
function unsetFontSize() {
root.style.fontSize = '';
}
// Tab Sizing -----------------------
function setTabSizing (tabSizing) {
root.setAttribute(`theme-${themeName}-tabsizing`, tabSizing.toLowerCase())
function setTabSizing(tabSizing) {
root.setAttribute(`theme-${themeName}-tabsizing`, tabSizing.toLowerCase());
}
function unsetTabSizing () {
root.removeAttribute(`theme-${themeName}-tabsizing`)
function unsetTabSizing() {
root.removeAttribute(`theme-${themeName}-tabsizing`);
}
// Tab Close Button -----------------------
function setTabCloseButton (tabCloseButton) {
function setTabCloseButton(tabCloseButton) {
if (tabCloseButton === 'Left') {
root.setAttribute(`theme-${themeName}-tab-close-button`, 'left')
root.setAttribute(`theme-${themeName}-tab-close-button`, 'left');
} else {
unsetTabCloseButton()
unsetTabCloseButton();
}
}
function unsetTabCloseButton () {
root.removeAttribute(`theme-${themeName}-tab-close-button`)
function unsetTabCloseButton() {
root.removeAttribute(`theme-${themeName}-tab-close-button`);
}
// Dock Buttons -----------------------
function setHideDockButtons (hideDockButtons) {
function setHideDockButtons(hideDockButtons) {
if (hideDockButtons) {
root.setAttribute(`theme-${themeName}-dock-buttons`, 'hidden')
root.setAttribute(`theme-${themeName}-dock-buttons`, 'hidden');
} else {
unsetHideDockButtons()
unsetHideDockButtons();
}
}
function unsetHideDockButtons () {
root.removeAttribute(`theme-${themeName}-dock-buttons`)
function unsetHideDockButtons() {
root.removeAttribute(`theme-${themeName}-dock-buttons`);
}
// Sticky Headers -----------------------
function setStickyHeaders (stickyHeaders) {
function setStickyHeaders(stickyHeaders) {
if (stickyHeaders) {
root.setAttribute(`theme-${themeName}-sticky-headers`, 'sticky')
root.setAttribute(`theme-${themeName}-sticky-headers`, 'sticky');
} else {
unsetStickyHeaders()
unsetStickyHeaders();
}
}
function unsetStickyHeaders () {
root.removeAttribute(`theme-${themeName}-sticky-headers`)
function unsetStickyHeaders() {
root.removeAttribute(`theme-${themeName}-sticky-headers`);
}

View File

@ -1,58 +1,58 @@
const themeName = 'one-dark-ui'
const themeName = 'one-dark-ui';
describe(`${themeName} theme`, () => {
beforeEach(() => {
waitsForPromise(() => atom.packages.activatePackage(themeName))
})
waitsForPromise(() => atom.packages.activatePackage(themeName));
});
it('allows the font size to be set via config', () => {
expect(document.documentElement.style.fontSize).toBe('12px')
expect(document.documentElement.style.fontSize).toBe('12px');
atom.config.set(`${themeName}.fontSize`, '10')
expect(document.documentElement.style.fontSize).toBe('10px')
})
atom.config.set(`${themeName}.fontSize`, '10');
expect(document.documentElement.style.fontSize).toBe('10px');
});
it('allows the tab sizing to be set via config', () => {
atom.config.set(`${themeName}.tabSizing`, 'Maximum')
atom.config.set(`${themeName}.tabSizing`, 'Maximum');
expect(
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
).toBe('maximum')
})
).toBe('maximum');
});
it('allows the tab sizing to be set via config', () => {
atom.config.set(`${themeName}.tabSizing`, 'Minimum')
atom.config.set(`${themeName}.tabSizing`, 'Minimum');
expect(
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
).toBe('minimum')
})
).toBe('minimum');
});
it('allows the tab close button to be shown on the left via config', () => {
atom.config.set(`${themeName}.tabCloseButton`, 'Left')
atom.config.set(`${themeName}.tabCloseButton`, 'Left');
expect(
document.documentElement.getAttribute(
`theme-${themeName}-tab-close-button`
)
).toBe('left')
})
).toBe('left');
});
it('allows the dock toggle buttons to be hidden via config', () => {
atom.config.set(`${themeName}.hideDockButtons`, true)
atom.config.set(`${themeName}.hideDockButtons`, true);
expect(
document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`)
).toBe('hidden')
})
).toBe('hidden');
});
it('allows the tree-view headers to be sticky via config', () => {
atom.config.set(`${themeName}.stickyHeaders`, true)
atom.config.set(`${themeName}.stickyHeaders`, true);
expect(
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
).toBe('sticky')
})
).toBe('sticky');
});
it('allows the tree-view headers to not be sticky via config', () => {
atom.config.set(`${themeName}.stickyHeaders`, false)
atom.config.set(`${themeName}.stickyHeaders`, false);
expect(
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
).toBe(null)
})
})
).toBe(null);
});
});

View File

@ -1,88 +1,88 @@
const root = document.documentElement
const themeName = 'one-light-ui'
const root = document.documentElement;
const themeName = 'one-light-ui';
module.exports = {
activate (state) {
atom.config.observe(`${themeName}.fontSize`, setFontSize)
atom.config.observe(`${themeName}.tabSizing`, setTabSizing)
atom.config.observe(`${themeName}.tabCloseButton`, setTabCloseButton)
atom.config.observe(`${themeName}.hideDockButtons`, setHideDockButtons)
atom.config.observe(`${themeName}.stickyHeaders`, setStickyHeaders)
activate(state) {
atom.config.observe(`${themeName}.fontSize`, setFontSize);
atom.config.observe(`${themeName}.tabSizing`, setTabSizing);
atom.config.observe(`${themeName}.tabCloseButton`, setTabCloseButton);
atom.config.observe(`${themeName}.hideDockButtons`, setHideDockButtons);
atom.config.observe(`${themeName}.stickyHeaders`, setStickyHeaders);
// DEPRECATED: This can be removed at some point (added in Atom 1.17/1.18ish)
// It removes `layoutMode`
if (atom.config.get(`${themeName}.layoutMode`)) {
atom.config.unset(`${themeName}.layoutMode`)
atom.config.unset(`${themeName}.layoutMode`);
}
},
deactivate () {
unsetFontSize()
unsetTabSizing()
unsetTabCloseButton()
unsetHideDockButtons()
unsetStickyHeaders()
deactivate() {
unsetFontSize();
unsetTabSizing();
unsetTabCloseButton();
unsetHideDockButtons();
unsetStickyHeaders();
}
}
};
// Font Size -----------------------
function setFontSize (currentFontSize) {
root.style.fontSize = `${currentFontSize}px`
function setFontSize(currentFontSize) {
root.style.fontSize = `${currentFontSize}px`;
}
function unsetFontSize () {
root.style.fontSize = ''
function unsetFontSize() {
root.style.fontSize = '';
}
// Tab Sizing -----------------------
function setTabSizing (tabSizing) {
root.setAttribute(`theme-${themeName}-tabsizing`, tabSizing.toLowerCase())
function setTabSizing(tabSizing) {
root.setAttribute(`theme-${themeName}-tabsizing`, tabSizing.toLowerCase());
}
function unsetTabSizing () {
root.removeAttribute(`theme-${themeName}-tabsizing`)
function unsetTabSizing() {
root.removeAttribute(`theme-${themeName}-tabsizing`);
}
// Tab Close Button -----------------------
function setTabCloseButton (tabCloseButton) {
function setTabCloseButton(tabCloseButton) {
if (tabCloseButton === 'Left') {
root.setAttribute(`theme-${themeName}-tab-close-button`, 'left')
root.setAttribute(`theme-${themeName}-tab-close-button`, 'left');
} else {
unsetTabCloseButton()
unsetTabCloseButton();
}
}
function unsetTabCloseButton () {
root.removeAttribute(`theme-${themeName}-tab-close-button`)
function unsetTabCloseButton() {
root.removeAttribute(`theme-${themeName}-tab-close-button`);
}
// Dock Buttons -----------------------
function setHideDockButtons (hideDockButtons) {
function setHideDockButtons(hideDockButtons) {
if (hideDockButtons) {
root.setAttribute(`theme-${themeName}-dock-buttons`, 'hidden')
root.setAttribute(`theme-${themeName}-dock-buttons`, 'hidden');
} else {
unsetHideDockButtons()
unsetHideDockButtons();
}
}
function unsetHideDockButtons () {
root.removeAttribute(`theme-${themeName}-dock-buttons`)
function unsetHideDockButtons() {
root.removeAttribute(`theme-${themeName}-dock-buttons`);
}
// Sticky Headers -----------------------
function setStickyHeaders (stickyHeaders) {
function setStickyHeaders(stickyHeaders) {
if (stickyHeaders) {
root.setAttribute(`theme-${themeName}-sticky-headers`, 'sticky')
root.setAttribute(`theme-${themeName}-sticky-headers`, 'sticky');
} else {
unsetStickyHeaders()
unsetStickyHeaders();
}
}
function unsetStickyHeaders () {
root.removeAttribute(`theme-${themeName}-sticky-headers`)
function unsetStickyHeaders() {
root.removeAttribute(`theme-${themeName}-sticky-headers`);
}

View File

@ -1,58 +1,58 @@
const themeName = 'one-light-ui'
const themeName = 'one-light-ui';
describe(`${themeName} theme`, () => {
beforeEach(() => {
waitsForPromise(() => atom.packages.activatePackage(themeName))
})
waitsForPromise(() => atom.packages.activatePackage(themeName));
});
it('allows the font size to be set via config', () => {
expect(document.documentElement.style.fontSize).toBe('12px')
expect(document.documentElement.style.fontSize).toBe('12px');
atom.config.set(`${themeName}.fontSize`, '10')
expect(document.documentElement.style.fontSize).toBe('10px')
})
atom.config.set(`${themeName}.fontSize`, '10');
expect(document.documentElement.style.fontSize).toBe('10px');
});
it('allows the tab sizing to be set via config', () => {
atom.config.set(`${themeName}.tabSizing`, 'Maximum')
atom.config.set(`${themeName}.tabSizing`, 'Maximum');
expect(
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
).toBe('maximum')
})
).toBe('maximum');
});
it('allows the tab sizing to be set via config', () => {
atom.config.set(`${themeName}.tabSizing`, 'Minimum')
atom.config.set(`${themeName}.tabSizing`, 'Minimum');
expect(
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
).toBe('minimum')
})
).toBe('minimum');
});
it('allows the tab close button to be shown on the left via config', () => {
atom.config.set(`${themeName}.tabCloseButton`, 'Left')
atom.config.set(`${themeName}.tabCloseButton`, 'Left');
expect(
document.documentElement.getAttribute(
`theme-${themeName}-tab-close-button`
)
).toBe('left')
})
).toBe('left');
});
it('allows the dock toggle buttons to be hidden via config', () => {
atom.config.set(`${themeName}.hideDockButtons`, true)
atom.config.set(`${themeName}.hideDockButtons`, true);
expect(
document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`)
).toBe('hidden')
})
).toBe('hidden');
});
it('allows the tree-view headers to be sticky via config', () => {
atom.config.set(`${themeName}.stickyHeaders`, true)
atom.config.set(`${themeName}.stickyHeaders`, true);
expect(
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
).toBe('sticky')
})
).toBe('sticky');
});
it('allows the tree-view headers to not be sticky via config', () => {
atom.config.set(`${themeName}.stickyHeaders`, false)
atom.config.set(`${themeName}.stickyHeaders`, false);
expect(
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
).toBe(null)
})
})
).toBe(null);
});
});

View File

@ -4,6 +4,6 @@ var spawn = require('child_process').spawn;
var atomCommandPath = path.resolve(__dirname, '..', '..', process.argv[2]);
var args = process.argv.slice(3);
args.unshift('--executed-from', process.cwd());
var options = {detached: true, stdio: 'ignore'};
var options = { detached: true, stdio: 'ignore' };
spawn(atomCommandPath, args, options);
process.exit(0);

View File

@ -1,22 +1,23 @@
// This module exports paths, names, and other metadata that is referenced
// throughout the build.
'use strict'
'use strict';
const fs = require('fs')
const path = require('path')
const spawnSync = require('./lib/spawn-sync')
const fs = require('fs');
const path = require('path');
const spawnSync = require('./lib/spawn-sync');
const repositoryRootPath = path.resolve(__dirname, '..')
const apmRootPath = path.join(repositoryRootPath, 'apm')
const scriptRootPath = path.join(repositoryRootPath, 'script')
const buildOutputPath = path.join(repositoryRootPath, 'out')
const docsOutputPath = path.join(repositoryRootPath, 'docs', 'output')
const intermediateAppPath = path.join(buildOutputPath, 'app')
const symbolsPath = path.join(buildOutputPath, 'symbols')
const electronDownloadPath = path.join(repositoryRootPath, 'electron')
const homeDirPath = process.env.HOME || process.env.USERPROFILE
const atomHomeDirPath = process.env.ATOM_HOME || path.join(homeDirPath, '.atom')
const repositoryRootPath = path.resolve(__dirname, '..');
const apmRootPath = path.join(repositoryRootPath, 'apm');
const scriptRootPath = path.join(repositoryRootPath, 'script');
const buildOutputPath = path.join(repositoryRootPath, 'out');
const docsOutputPath = path.join(repositoryRootPath, 'docs', 'output');
const intermediateAppPath = path.join(buildOutputPath, 'app');
const symbolsPath = path.join(buildOutputPath, 'symbols');
const electronDownloadPath = path.join(repositoryRootPath, 'electron');
const homeDirPath = process.env.HOME || process.env.USERPROFILE;
const atomHomeDirPath =
process.env.ATOM_HOME || path.join(homeDirPath, '.atom');
const appMetadata = require(path.join(repositoryRootPath, 'package.json'))
const apmMetadata = require(path.join(apmRootPath, 'package.json'))
@ -45,23 +46,24 @@ module.exports = {
getApmBinPath,
getNpmBinPath,
snapshotAuxiliaryData: {}
}
};
function getChannel (version) {
const match = version.match(/\d+\.\d+\.\d+(-([a-z]+)(\d+|-\w{4,})?)?$/)
function getChannel(version) {
const match = version.match(/\d+\.\d+\.\d+(-([a-z]+)(\d+|-\w{4,})?)?$/);
if (!match) {
throw new Error(`Found incorrectly formatted Atom version ${version}`)
throw new Error(`Found incorrectly formatted Atom version ${version}`);
} else if (match[2]) {
return match[2]
return match[2];
}
return 'stable'
return 'stable';
}
function getAppName (channel) {
function getAppName(channel) {
return channel === 'stable'
? 'Atom'
: `Atom ${process.env.ATOM_CHANNEL_DISPLAY_NAME || channel.charAt(0).toUpperCase() + channel.slice(1)}`
: `Atom ${process.env.ATOM_CHANNEL_DISPLAY_NAME ||
channel.charAt(0).toUpperCase() + channel.slice(1)}`;
}
function getExecutableName (channel, appName) {
@ -76,22 +78,38 @@ function getExecutableName (channel, appName) {
function computeAppVersion (version) {
if (version.match(/-dev$/)) {
const result = spawnSync('git', ['rev-parse', '--short', 'HEAD'], {cwd: repositoryRootPath})
const commitHash = result.stdout.toString().trim()
version += '-' + commitHash
const result = spawnSync('git', ['rev-parse', '--short', 'HEAD'], {
cwd: repositoryRootPath
});
const commitHash = result.stdout.toString().trim();
version += '-' + commitHash;
}
return version
return version;
}
function getApmBinPath () {
const apmBinName = process.platform === 'win32' ? 'apm.cmd' : 'apm'
return path.join(apmRootPath, 'node_modules', 'atom-package-manager', 'bin', apmBinName)
function getApmBinPath() {
const apmBinName = process.platform === 'win32' ? 'apm.cmd' : 'apm';
return path.join(
apmRootPath,
'node_modules',
'atom-package-manager',
'bin',
apmBinName
);
}
function getNpmBinPath (external = false) {
if (process.env.NPM_BIN_PATH) return process.env.NPM_BIN_PATH
function getNpmBinPath(external = false) {
if (process.env.NPM_BIN_PATH) return process.env.NPM_BIN_PATH;
const npmBinName = process.platform === 'win32' ? 'npm.cmd' : 'npm'
const localNpmBinPath = path.resolve(repositoryRootPath, 'script', 'node_modules', '.bin', npmBinName)
return !external && fs.existsSync(localNpmBinPath) ? localNpmBinPath : npmBinName
const npmBinName = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const localNpmBinPath = path.resolve(
repositoryRootPath,
'script',
'node_modules',
'.bin',
npmBinName
);
return !external && fs.existsSync(localNpmBinPath)
? localNpmBinPath
: npmBinName;
}

View File

@ -1,41 +1,54 @@
const fs = require('fs-extra')
const path = require('path')
const fs = require('fs-extra');
const path = require('path');
module.exports = function (packagePath) {
const nodeModulesPath = path.join(packagePath, 'node_modules')
const nodeModulesBackupPath = path.join(packagePath, 'node_modules.bak')
module.exports = function(packagePath) {
const nodeModulesPath = path.join(packagePath, 'node_modules');
const nodeModulesBackupPath = path.join(packagePath, 'node_modules.bak');
if (fs.existsSync(nodeModulesBackupPath)) {
throw new Error('Cannot back up ' + nodeModulesPath + '; ' + nodeModulesBackupPath + ' already exists')
throw new Error(
'Cannot back up ' +
nodeModulesPath +
'; ' +
nodeModulesBackupPath +
' already exists'
);
}
// some packages may have no node_modules after deduping, but we still want
// to "back-up" and later restore that fact
if (!fs.existsSync(nodeModulesPath)) {
const msg = 'Skipping backing up ' + nodeModulesPath + ' as it does not exist'
console.log(msg.gray)
const msg =
'Skipping backing up ' + nodeModulesPath + ' as it does not exist';
console.log(msg.gray);
const restore = function stubRestoreNodeModules () {
const restore = function stubRestoreNodeModules() {
if (fs.existsSync(nodeModulesPath)) {
fs.removeSync(nodeModulesPath)
fs.removeSync(nodeModulesPath);
}
};
return { restore, nodeModulesPath, nodeModulesBackupPath };
}
return {restore, nodeModulesPath, nodeModulesBackupPath}
}
fs.copySync(nodeModulesPath, nodeModulesBackupPath);
fs.copySync(nodeModulesPath, nodeModulesBackupPath)
const restore = function restoreNodeModules () {
const restore = function restoreNodeModules() {
if (!fs.existsSync(nodeModulesBackupPath)) {
throw new Error('Cannot restore ' + nodeModulesPath + '; ' + nodeModulesBackupPath + ' does not exist')
throw new Error(
'Cannot restore ' +
nodeModulesPath +
'; ' +
nodeModulesBackupPath +
' does not exist'
);
}
if (fs.existsSync(nodeModulesPath)) {
fs.removeSync(nodeModulesPath)
}
fs.renameSync(nodeModulesBackupPath, nodeModulesPath)
fs.removeSync(nodeModulesPath);
}
fs.renameSync(nodeModulesBackupPath, nodeModulesPath);
};
return {restore, nodeModulesPath, nodeModulesBackupPath}
}
return { restore, nodeModulesPath, nodeModulesBackupPath };
};

View File

@ -1,33 +1,45 @@
'use strict'
'use strict';
const buildMetadata = require('../package.json')
const CONFIG = require('../config')
const semver = require('semver')
const buildMetadata = require('../package.json');
const CONFIG = require('../config');
const semver = require('semver');
module.exports = function () {
module.exports = function() {
// Chromedriver should be specified as ^n.x where n matches the Electron major version
const chromedriverVer = buildMetadata.dependencies['electron-chromedriver']
const mksnapshotVer = buildMetadata.dependencies['electron-mksnapshot']
const chromedriverVer = buildMetadata.dependencies['electron-chromedriver'];
const mksnapshotVer = buildMetadata.dependencies['electron-mksnapshot'];
// Always use caret on electron-chromedriver so that it can pick up the best minor/patch versions
if (!chromedriverVer.startsWith('^')) {
throw new Error(`electron-chromedriver version in script/package.json should start with a caret to match latest patch version.`)
throw new Error(
`electron-chromedriver version in script/package.json should start with a caret to match latest patch version.`
);
}
if (!mksnapshotVer.startsWith('^')) {
throw new Error(`electron-mksnapshot version in script/package.json should start with a caret to match latest patch version.`)
throw new Error(
`electron-mksnapshot version in script/package.json should start with a caret to match latest patch version.`
);
}
const electronVer = CONFIG.appMetadata.electronVersion
const electronVer = CONFIG.appMetadata.electronVersion;
if (!semver.satisfies(electronVer, chromedriverVer)) {
throw new Error(`electron-chromedriver ${chromedriverVer} incompatible with electron ${electronVer}.\n` +
throw new Error(
`electron-chromedriver ${chromedriverVer} incompatible with electron ${electronVer}.\n` +
'Did you upgrade electron in package.json and forget to upgrade electron-chromedriver in ' +
`script/package.json to '~${semver.major(electronVer)}.${semver.minor(electronVer)}' ?`)
`script/package.json to '~${semver.major(electronVer)}.${semver.minor(
electronVer
)}' ?`
);
}
if (!semver.satisfies(electronVer, mksnapshotVer)) {
throw new Error(`electron-mksnapshot ${mksnapshotVer} incompatible with electron ${electronVer}.\n` +
throw new Error(
`electron-mksnapshot ${mksnapshotVer} incompatible with electron ${electronVer}.\n` +
'Did you upgrade electron in package.json and forget to upgrade electron-mksnapshot in ' +
`script/package.json to '~${semver.major(electronVer)}.${semver.minor(electronVer)}' ?`)
`script/package.json to '~${semver.major(electronVer)}.${semver.minor(
electronVer
)}' ?`
);
}
}
};

View File

@ -1,12 +1,12 @@
'use strict'
'use strict';
const fs = require('fs-extra')
const os = require('os')
const path = require('path')
const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const CONFIG = require('../config')
const CONFIG = require('../config');
module.exports = function () {
module.exports = function() {
const cachePaths = [
path.join(CONFIG.repositoryRootPath, 'electron'),
path.join(CONFIG.atomHomeDirPath, '.node-gyp'),
@ -19,10 +19,10 @@ module.exports = function () {
path.join(CONFIG.atomHomeDirPath, 'electron'),
path.join(os.tmpdir(), 'atom-build'),
path.join(os.tmpdir(), 'atom-cached-atom-shells')
]
];
for (let path of cachePaths) {
console.log(`Cleaning ${path}`)
fs.removeSync(path)
console.log(`Cleaning ${path}`);
fs.removeSync(path);
}
}
};

View File

@ -1,29 +1,42 @@
const path = require('path')
const path = require('path');
const CONFIG = require('../config')
const CONFIG = require('../config');
module.exports = function () {
module.exports = function() {
// We can't require fs-extra or glob if `script/bootstrap` has never been run,
// because they are third party modules. This is okay because cleaning
// dependencies only makes sense if dependencies have been installed at least
// once.
const fs = require('fs-extra')
const glob = require('glob')
const fs = require('fs-extra');
const glob = require('glob');
const apmDependenciesPath = path.join(CONFIG.apmRootPath, 'node_modules')
console.log(`Cleaning ${apmDependenciesPath}`)
fs.removeSync(apmDependenciesPath)
const apmDependenciesPath = path.join(CONFIG.apmRootPath, 'node_modules');
console.log(`Cleaning ${apmDependenciesPath}`);
fs.removeSync(apmDependenciesPath);
const atomDependenciesPath = path.join(CONFIG.repositoryRootPath, 'node_modules')
console.log(`Cleaning ${atomDependenciesPath}`)
fs.removeSync(atomDependenciesPath)
const atomDependenciesPath = path.join(
CONFIG.repositoryRootPath,
'node_modules'
);
console.log(`Cleaning ${atomDependenciesPath}`);
fs.removeSync(atomDependenciesPath);
const scriptDependenciesPath = path.join(CONFIG.scriptRootPath, 'node_modules')
console.log(`Cleaning ${scriptDependenciesPath}`)
fs.removeSync(scriptDependenciesPath)
const scriptDependenciesPath = path.join(
CONFIG.scriptRootPath,
'node_modules'
);
console.log(`Cleaning ${scriptDependenciesPath}`);
fs.removeSync(scriptDependenciesPath);
const bundledPackageDependenciesPaths = path.join(CONFIG.repositoryRootPath, 'packages', '**', 'node_modules')
for (const bundledPackageDependencyPath of glob.sync(bundledPackageDependenciesPaths)) {
fs.removeSync(bundledPackageDependencyPath)
const bundledPackageDependenciesPaths = path.join(
CONFIG.repositoryRootPath,
'packages',
'**',
'node_modules'
);
for (const bundledPackageDependencyPath of glob.sync(
bundledPackageDependenciesPaths
)) {
fs.removeSync(bundledPackageDependencyPath);
}
}
};

View File

@ -1,9 +1,9 @@
const fs = require('fs-extra')
const CONFIG = require('../config')
const fs = require('fs-extra');
const CONFIG = require('../config');
module.exports = function () {
module.exports = function() {
if (fs.existsSync(CONFIG.buildOutputPath)) {
console.log(`Cleaning ${CONFIG.buildOutputPath}`)
fs.removeSync(CONFIG.buildOutputPath)
console.log(`Cleaning ${CONFIG.buildOutputPath}`);
fs.removeSync(CONFIG.buildOutputPath);
}
}
};

View File

@ -1,89 +1,143 @@
const downloadFileFromGithub = require('./download-file-from-github')
const fs = require('fs-extra')
const os = require('os')
const path = require('path')
const spawnSync = require('./spawn-sync')
const downloadFileFromGithub = require('./download-file-from-github');
const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const spawnSync = require('./spawn-sync');
module.exports = function (packagedAppPath) {
if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL && !process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH) {
console.log('Skipping code signing because the ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable is not defined'.gray)
return
module.exports = function(packagedAppPath) {
if (
!process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL &&
!process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH
) {
console.log(
'Skipping code signing because the ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable is not defined'
.gray
);
return;
}
let certPath = process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH
let certPath = process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH;
if (!certPath) {
certPath = path.join(os.tmpdir(), 'mac.p12')
downloadFileFromGithub(process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath)
certPath = path.join(os.tmpdir(), 'mac.p12');
downloadFileFromGithub(
process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL,
certPath
);
}
try {
console.log(`Ensuring keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN} exists`)
console.log(
`Ensuring keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN} exists`
);
try {
spawnSync('security', [
'show-keychain-info',
process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN
], {stdio: 'inherit'})
spawnSync(
'security',
['show-keychain-info', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN],
{ stdio: 'inherit' }
);
} catch (err) {
console.log(`Creating keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN}`)
console.log(
`Creating keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN}`
);
// The keychain doesn't exist, try to create it
spawnSync('security', [
spawnSync(
'security',
[
'create-keychain',
'-p', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD,
'-p',
process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD,
process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN
], {stdio: 'inherit'})
],
{ stdio: 'inherit' }
);
// List the keychain to "activate" it. Somehow this seems
// to be needed otherwise the signing operation fails
spawnSync('security', [
'list-keychains',
'-s', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN
], {stdio: 'inherit'})
spawnSync(
'security',
['list-keychains', '-s', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN],
{ stdio: 'inherit' }
);
// Make sure it doesn't time out before we use it
spawnSync('security', [
spawnSync(
'security',
[
'set-keychain-settings',
'-t', '3600',
'-u', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN
], {stdio: 'inherit'})
'-t',
'3600',
'-u',
process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN
],
{ stdio: 'inherit' }
);
}
console.log(`Unlocking keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN}`)
const unlockArgs = ['unlock-keychain']
console.log(
`Unlocking keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN}`
);
const unlockArgs = ['unlock-keychain'];
// For signing on local workstations, password could be entered interactively
if (process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD) {
unlockArgs.push('-p', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD)
unlockArgs.push(
'-p',
process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD
);
}
unlockArgs.push(process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN)
spawnSync('security', unlockArgs, {stdio: 'inherit'})
unlockArgs.push(process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN);
spawnSync('security', unlockArgs, { stdio: 'inherit' });
console.log(`Importing certificate at ${certPath} into ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN} keychain`)
console.log(
`Importing certificate at ${certPath} into ${
process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN
} keychain`
);
spawnSync('security', [
'import', certPath,
'-P', process.env.ATOM_MAC_CODE_SIGNING_CERT_PASSWORD,
'-k', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN,
'-T', '/usr/bin/codesign'
])
'import',
certPath,
'-P',
process.env.ATOM_MAC_CODE_SIGNING_CERT_PASSWORD,
'-k',
process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN,
'-T',
'/usr/bin/codesign'
]);
console.log('Running incantation to suppress dialog when signing on macOS Sierra')
console.log(
'Running incantation to suppress dialog when signing on macOS Sierra'
);
try {
spawnSync('security', [
'set-key-partition-list', '-S', 'apple-tool:,apple:', '-s',
'-k', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD,
'set-key-partition-list',
'-S',
'apple-tool:,apple:',
'-s',
'-k',
process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD,
process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN
])
]);
} catch (e) {
console.log('Incantation failed... maybe this isn\'t Sierra?')
console.log("Incantation failed... maybe this isn't Sierra?");
}
console.log(`Code-signing application at ${packagedAppPath}`)
spawnSync('codesign', [
'--deep', '--force', '--verbose',
'--keychain', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN,
'--sign', 'Developer ID Application: GitHub', packagedAppPath
], {stdio: 'inherit'})
console.log(`Code-signing application at ${packagedAppPath}`);
spawnSync(
'codesign',
[
'--deep',
'--force',
'--verbose',
'--keychain',
process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN,
'--sign',
'Developer ID Application: GitHub',
packagedAppPath
],
{ stdio: 'inherit' }
);
} finally {
if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH) {
console.log(`Deleting certificate at ${certPath}`)
fs.removeSync(certPath)
console.log(`Deleting certificate at ${certPath}`);
fs.removeSync(certPath);
}
}
}
};

View File

@ -1,33 +1,49 @@
const downloadFileFromGithub = require('./download-file-from-github')
const fs = require('fs-extra')
const os = require('os')
const path = require('path')
const {spawnSync} = require('child_process')
const downloadFileFromGithub = require('./download-file-from-github');
const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const { spawnSync } = require('child_process');
module.exports = function (filesToSign) {
if (!process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL && !process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH) {
console.log('Skipping code signing because the ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable is not defined'.gray)
return
module.exports = function(filesToSign) {
if (
!process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL &&
!process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH
) {
console.log(
'Skipping code signing because the ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable is not defined'
.gray
);
return;
}
let certPath = process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH
let certPath = process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH;
if (!certPath) {
certPath = path.join(os.tmpdir(), 'win.p12')
downloadFileFromGithub(process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath)
certPath = path.join(os.tmpdir(), 'win.p12');
downloadFileFromGithub(
process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL,
certPath
);
}
try {
for (const fileToSign of filesToSign) {
console.log(`Code-signing executable at ${fileToSign}`)
signFile(fileToSign)
console.log(`Code-signing executable at ${fileToSign}`);
signFile(fileToSign);
}
} finally {
if (!process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH) {
fs.removeSync(certPath)
fs.removeSync(certPath);
}
}
function signFile (fileToSign) {
const signCommand = path.resolve(__dirname, '..', 'node_modules', 'electron-winstaller', 'vendor', 'signtool.exe')
function signFile(fileToSign) {
const signCommand = path.resolve(
__dirname,
'..',
'node_modules',
'electron-winstaller',
'vendor',
'signtool.exe'
);
const args = [
'sign',
`/f ${certPath}`, // Signing cert file
@ -36,11 +52,20 @@ module.exports = function (filesToSign) {
'/tr http://timestamp.digicert.com', // Time stamp server
'/td sha256', // Times stamp algorithm
`"${fileToSign}"`
]
const result = spawnSync(signCommand, args, {stdio: 'inherit', shell: true})
];
const result = spawnSync(signCommand, args, {
stdio: 'inherit',
shell: true
});
if (result.status !== 0) {
// Ensure we do not dump the signing password into the logs if something goes wrong
throw new Error(`Command ${signCommand} ${args.map(a => a.replace(process.env.ATOM_WIN_CODE_SIGNING_CERT_PASSWORD, '******')).join(' ')} exited with code ${result.status}`)
throw new Error(
`Command ${signCommand} ${args
.map(a =>
a.replace(process.env.ATOM_WIN_CODE_SIGNING_CERT_PASSWORD, '******')
)
.join(' ')} exited with code ${result.status}`
);
}
}
}
};

View File

@ -1,56 +1,67 @@
'use strict'
'use strict';
const fs = require('fs-extra')
const path = require('path')
const spawnSync = require('./spawn-sync')
const { path7za } = require('7zip-bin')
const fs = require('fs-extra');
const path = require('path');
const spawnSync = require('./spawn-sync');
const { path7za } = require('7zip-bin');
const CONFIG = require('../config')
const CONFIG = require('../config');
module.exports = function (packagedAppPath) {
const appArchivePath = path.join(CONFIG.buildOutputPath, getArchiveName())
compress(packagedAppPath, appArchivePath)
module.exports = function(packagedAppPath) {
const appArchivePath = path.join(CONFIG.buildOutputPath, getArchiveName());
compress(packagedAppPath, appArchivePath);
if (process.platform === 'darwin') {
const symbolsArchivePath = path.join(CONFIG.buildOutputPath, 'atom-mac-symbols.zip')
compress(CONFIG.symbolsPath, symbolsArchivePath)
const symbolsArchivePath = path.join(
CONFIG.buildOutputPath,
'atom-mac-symbols.zip'
);
compress(CONFIG.symbolsPath, symbolsArchivePath);
}
}
};
function getArchiveName () {
function getArchiveName() {
switch (process.platform) {
case 'darwin': return 'atom-mac.zip'
case 'win32': return `atom-${process.arch === 'x64' ? 'x64-' : ''}windows.zip`
default: return `atom-${getLinuxArchiveArch()}.tar.gz`
case 'darwin':
return 'atom-mac.zip';
case 'win32':
return `atom-${process.arch === 'x64' ? 'x64-' : ''}windows.zip`;
default:
return `atom-${getLinuxArchiveArch()}.tar.gz`;
}
}
function getLinuxArchiveArch () {
function getLinuxArchiveArch() {
switch (process.arch) {
case 'ia32': return 'i386'
case 'x64' : return 'amd64'
default: return process.arch
case 'ia32':
return 'i386';
case 'x64':
return 'amd64';
default:
return process.arch;
}
}
function compress (inputDirPath, outputArchivePath) {
function compress(inputDirPath, outputArchivePath) {
if (fs.existsSync(outputArchivePath)) {
console.log(`Deleting "${outputArchivePath}"`)
fs.removeSync(outputArchivePath)
console.log(`Deleting "${outputArchivePath}"`);
fs.removeSync(outputArchivePath);
}
console.log(`Compressing "${inputDirPath}" to "${outputArchivePath}"`)
let compressCommand, compressArguments
console.log(`Compressing "${inputDirPath}" to "${outputArchivePath}"`);
let compressCommand, compressArguments;
if (process.platform === 'darwin') {
compressCommand = 'zip'
compressArguments = ['-r', '--symlinks']
compressCommand = 'zip';
compressArguments = ['-r', '--symlinks'];
} else if (process.platform === 'win32') {
compressCommand = path7za
compressArguments = ['a', '-r']
compressCommand = path7za;
compressArguments = ['a', '-r'];
} else {
compressCommand = 'tar'
compressArguments = ['caf']
compressCommand = 'tar';
compressArguments = ['caf'];
}
compressArguments.push(outputArchivePath, path.basename(inputDirPath))
spawnSync(compressCommand, compressArguments, {cwd: path.dirname(inputDirPath)})
compressArguments.push(outputArchivePath, path.basename(inputDirPath));
spawnSync(compressCommand, compressArguments, {
cwd: path.dirname(inputDirPath)
});
}

View File

@ -1,16 +1,16 @@
// This module exports a function that copies all the static assets into the
// appropriate location in the build output directory.
'use strict'
'use strict';
const path = require('path')
const fs = require('fs-extra')
const CONFIG = require('../config')
const glob = require('glob')
const includePathInPackagedApp = require('./include-path-in-packaged-app')
const path = require('path');
const fs = require('fs-extra');
const CONFIG = require('../config');
const glob = require('glob');
const includePathInPackagedApp = require('./include-path-in-packaged-app');
module.exports = function () {
console.log(`Copying assets to ${CONFIG.intermediateAppPath}`)
module.exports = function() {
console.log(`Copying assets to ${CONFIG.intermediateAppPath}`);
let srcPaths = [
path.join(CONFIG.repositoryRootPath, 'benchmarks', 'benchmark-runner.js'),
path.join(CONFIG.repositoryRootPath, 'dot-atom'),
@ -19,10 +19,16 @@ module.exports = function () {
path.join(CONFIG.repositoryRootPath, 'static'),
path.join(CONFIG.repositoryRootPath, 'src'),
path.join(CONFIG.repositoryRootPath, 'vendor')
]
srcPaths = srcPaths.concat(glob.sync(path.join(CONFIG.repositoryRootPath, 'spec', '*.*'), {ignore: path.join('**', '*-spec.*')}))
];
srcPaths = srcPaths.concat(
glob.sync(path.join(CONFIG.repositoryRootPath, 'spec', '*.*'), {
ignore: path.join('**', '*-spec.*')
})
);
for (let srcPath of srcPaths) {
fs.copySync(srcPath, computeDestinationPath(srcPath), {filter: includePathInPackagedApp})
fs.copySync(srcPath, computeDestinationPath(srcPath), {
filter: includePathInPackagedApp
});
}
// Run a copy pass to dereference symlinked directories under node_modules.
@ -30,21 +36,37 @@ module.exports = function () {
// copied to the output folder correctly. We dereference only the top-level
// symlinks and not nested symlinks to avoid issues where symlinked binaries
// are duplicated in Atom's installation packages (see atom/atom#18490).
const nodeModulesPath = path.join(CONFIG.repositoryRootPath, 'node_modules')
glob.sync(path.join(nodeModulesPath, '*'))
.map(p => fs.lstatSync(p).isSymbolicLink() ? path.resolve(nodeModulesPath, fs.readlinkSync(p)) : p)
const nodeModulesPath = path.join(CONFIG.repositoryRootPath, 'node_modules');
glob
.sync(path.join(nodeModulesPath, '*'))
.map(p =>
fs.lstatSync(p).isSymbolicLink()
? path.resolve(nodeModulesPath, fs.readlinkSync(p))
: p
)
.forEach(modulePath => {
const destPath = path.join(CONFIG.intermediateAppPath, 'node_modules', path.basename(modulePath))
fs.copySync(modulePath, destPath, { filter: includePathInPackagedApp })
})
const destPath = path.join(
CONFIG.intermediateAppPath,
'node_modules',
path.basename(modulePath)
);
fs.copySync(modulePath, destPath, { filter: includePathInPackagedApp });
});
fs.copySync(
path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'png', '1024.png'),
path.join(
CONFIG.repositoryRootPath,
'resources',
'app-icons',
CONFIG.channel,
'png',
'1024.png'
),
path.join(CONFIG.intermediateAppPath, 'resources', 'atom.png')
)
}
);
};
function computeDestinationPath (srcPath) {
const relativePath = path.relative(CONFIG.repositoryRootPath, srcPath)
return path.join(CONFIG.intermediateAppPath, relativePath)
function computeDestinationPath(srcPath) {
const relativePath = path.relative(CONFIG.repositoryRootPath, srcPath);
return path.join(CONFIG.intermediateAppPath, relativePath);
}

View File

@ -1,127 +1,224 @@
'use strict'
'use strict';
const fs = require('fs-extra')
const os = require('os')
const path = require('path')
const spawnSync = require('./spawn-sync')
const template = require('lodash.template')
const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const spawnSync = require('./spawn-sync');
const template = require('lodash.template');
const CONFIG = require('../config')
const CONFIG = require('../config');
module.exports = function (packagedAppPath) {
console.log(`Creating Debian package for "${packagedAppPath}"`)
const atomExecutableName = CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}`
const apmExecutableName = CONFIG.channel === 'stable' ? 'apm' : `apm-${CONFIG.channel}`
const appDescription = CONFIG.appMetadata.description
const appVersion = CONFIG.appMetadata.version
let arch
module.exports = function(packagedAppPath) {
console.log(`Creating Debian package for "${packagedAppPath}"`);
const atomExecutableName =
CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}`;
const apmExecutableName =
CONFIG.channel === 'stable' ? 'apm' : `apm-${CONFIG.channel}`;
const appDescription = CONFIG.appMetadata.description;
const appVersion = CONFIG.appMetadata.version;
let arch;
if (process.arch === 'ia32') {
arch = 'i386'
arch = 'i386';
} else if (process.arch === 'x64') {
arch = 'amd64'
arch = 'amd64';
} else if (process.arch === 'ppc') {
arch = 'powerpc'
arch = 'powerpc';
} else {
arch = process.arch
arch = process.arch;
}
const outputDebianPackageFilePath = path.join(CONFIG.buildOutputPath, `atom-${arch}.deb`)
const debianPackageDirPath = path.join(os.tmpdir(), path.basename(packagedAppPath))
const debianPackageConfigPath = path.join(debianPackageDirPath, 'DEBIAN')
const debianPackageInstallDirPath = path.join(debianPackageDirPath, 'usr')
const debianPackageBinDirPath = path.join(debianPackageInstallDirPath, 'bin')
const debianPackageShareDirPath = path.join(debianPackageInstallDirPath, 'share')
const debianPackageAtomDirPath = path.join(debianPackageShareDirPath, atomExecutableName)
const debianPackageApplicationsDirPath = path.join(debianPackageShareDirPath, 'applications')
const debianPackageIconsDirPath = path.join(debianPackageShareDirPath, 'pixmaps')
const debianPackageLintianOverridesDirPath = path.join(debianPackageShareDirPath, 'lintian', 'overrides')
const debianPackageDocsDirPath = path.join(debianPackageShareDirPath, 'doc', atomExecutableName)
const outputDebianPackageFilePath = path.join(
CONFIG.buildOutputPath,
`atom-${arch}.deb`
);
const debianPackageDirPath = path.join(
os.tmpdir(),
path.basename(packagedAppPath)
);
const debianPackageConfigPath = path.join(debianPackageDirPath, 'DEBIAN');
const debianPackageInstallDirPath = path.join(debianPackageDirPath, 'usr');
const debianPackageBinDirPath = path.join(debianPackageInstallDirPath, 'bin');
const debianPackageShareDirPath = path.join(
debianPackageInstallDirPath,
'share'
);
const debianPackageAtomDirPath = path.join(
debianPackageShareDirPath,
atomExecutableName
);
const debianPackageApplicationsDirPath = path.join(
debianPackageShareDirPath,
'applications'
);
const debianPackageIconsDirPath = path.join(
debianPackageShareDirPath,
'pixmaps'
);
const debianPackageLintianOverridesDirPath = path.join(
debianPackageShareDirPath,
'lintian',
'overrides'
);
const debianPackageDocsDirPath = path.join(
debianPackageShareDirPath,
'doc',
atomExecutableName
);
if (fs.existsSync(debianPackageDirPath)) {
console.log(`Deleting existing build dir for Debian package at "${debianPackageDirPath}"`)
fs.removeSync(debianPackageDirPath)
console.log(
`Deleting existing build dir for Debian package at "${debianPackageDirPath}"`
);
fs.removeSync(debianPackageDirPath);
}
if (fs.existsSync(`${debianPackageDirPath}.deb`)) {
console.log(`Deleting existing Debian package at "${debianPackageDirPath}.deb"`)
fs.removeSync(`${debianPackageDirPath}.deb`)
console.log(
`Deleting existing Debian package at "${debianPackageDirPath}.deb"`
);
fs.removeSync(`${debianPackageDirPath}.deb`);
}
if (fs.existsSync(debianPackageDirPath)) {
console.log(`Deleting existing Debian package at "${outputDebianPackageFilePath}"`)
fs.removeSync(debianPackageDirPath)
console.log(
`Deleting existing Debian package at "${outputDebianPackageFilePath}"`
);
fs.removeSync(debianPackageDirPath);
}
console.log(`Creating Debian package directory structure at "${debianPackageDirPath}"`)
fs.mkdirpSync(debianPackageDirPath)
fs.mkdirpSync(debianPackageConfigPath)
fs.mkdirpSync(debianPackageInstallDirPath)
fs.mkdirpSync(debianPackageShareDirPath)
fs.mkdirpSync(debianPackageApplicationsDirPath)
fs.mkdirpSync(debianPackageIconsDirPath)
fs.mkdirpSync(debianPackageLintianOverridesDirPath)
fs.mkdirpSync(debianPackageDocsDirPath)
fs.mkdirpSync(debianPackageBinDirPath)
console.log(
`Creating Debian package directory structure at "${debianPackageDirPath}"`
);
fs.mkdirpSync(debianPackageDirPath);
fs.mkdirpSync(debianPackageConfigPath);
fs.mkdirpSync(debianPackageInstallDirPath);
fs.mkdirpSync(debianPackageShareDirPath);
fs.mkdirpSync(debianPackageApplicationsDirPath);
fs.mkdirpSync(debianPackageIconsDirPath);
fs.mkdirpSync(debianPackageLintianOverridesDirPath);
fs.mkdirpSync(debianPackageDocsDirPath);
fs.mkdirpSync(debianPackageBinDirPath);
console.log(`Copying "${packagedAppPath}" to "${debianPackageAtomDirPath}"`)
fs.copySync(packagedAppPath, debianPackageAtomDirPath)
fs.chmodSync(debianPackageAtomDirPath, '755')
console.log(`Copying "${packagedAppPath}" to "${debianPackageAtomDirPath}"`);
fs.copySync(packagedAppPath, debianPackageAtomDirPath);
fs.chmodSync(debianPackageAtomDirPath, '755');
console.log(`Copying binaries into "${debianPackageBinDirPath}"`)
fs.copySync(path.join(CONFIG.repositoryRootPath, 'atom.sh'), path.join(debianPackageBinDirPath, atomExecutableName))
console.log(`Copying binaries into "${debianPackageBinDirPath}"`);
fs.copySync(
path.join(CONFIG.repositoryRootPath, 'atom.sh'),
path.join(debianPackageBinDirPath, atomExecutableName)
);
fs.symlinkSync(
path.join('..', 'share', atomExecutableName, 'resources', 'app', 'apm', 'node_modules', '.bin', 'apm'),
path.join(
'..',
'share',
atomExecutableName,
'resources',
'app',
'apm',
'node_modules',
'.bin',
'apm'
),
path.join(debianPackageBinDirPath, apmExecutableName)
)
);
console.log(`Writing control file into "${debianPackageConfigPath}"`)
const packageSizeInKilobytes = spawnSync('du', ['-sk', packagedAppPath]).stdout.toString().split(/\s+/)[0]
const controlFileTemplate = fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'debian', 'control.in'))
console.log(`Writing control file into "${debianPackageConfigPath}"`);
const packageSizeInKilobytes = spawnSync('du', ['-sk', packagedAppPath])
.stdout.toString()
.split(/\s+/)[0];
const controlFileTemplate = fs.readFileSync(
path.join(
CONFIG.repositoryRootPath,
'resources',
'linux',
'debian',
'control.in'
)
);
const controlFileContents = template(controlFileTemplate)({
appFileName: atomExecutableName,
version: appVersion,
arch: arch,
installedSize: packageSizeInKilobytes,
description: appDescription
})
fs.writeFileSync(path.join(debianPackageConfigPath, 'control'), controlFileContents)
});
fs.writeFileSync(
path.join(debianPackageConfigPath, 'control'),
controlFileContents
);
console.log(`Writing desktop entry file into "${debianPackageApplicationsDirPath}"`)
const desktopEntryTemplate = fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'atom.desktop.in'))
console.log(
`Writing desktop entry file into "${debianPackageApplicationsDirPath}"`
);
const desktopEntryTemplate = fs.readFileSync(
path.join(
CONFIG.repositoryRootPath,
'resources',
'linux',
'atom.desktop.in'
)
);
const desktopEntryContents = template(desktopEntryTemplate)({
appName: CONFIG.appName,
appFileName: atomExecutableName,
description: appDescription,
installDir: '/usr',
iconPath: atomExecutableName
})
fs.writeFileSync(path.join(debianPackageApplicationsDirPath, `${atomExecutableName}.desktop`), desktopEntryContents)
});
fs.writeFileSync(
path.join(
debianPackageApplicationsDirPath,
`${atomExecutableName}.desktop`
),
desktopEntryContents
);
console.log(`Copying icon into "${debianPackageIconsDirPath}"`)
console.log(`Copying icon into "${debianPackageIconsDirPath}"`);
fs.copySync(
path.join(packagedAppPath, 'resources', 'app.asar.unpacked', 'resources', 'atom.png'),
path.join(
packagedAppPath,
'resources',
'app.asar.unpacked',
'resources',
'atom.png'
),
path.join(debianPackageIconsDirPath, `${atomExecutableName}.png`)
)
);
console.log(`Copying license into "${debianPackageDocsDirPath}"`)
console.log(`Copying license into "${debianPackageDocsDirPath}"`);
fs.copySync(
path.join(packagedAppPath, 'resources', 'LICENSE.md'),
path.join(debianPackageDocsDirPath, 'copyright')
)
);
console.log(`Copying lintian overrides into "${debianPackageLintianOverridesDirPath}"`)
console.log(
`Copying lintian overrides into "${debianPackageLintianOverridesDirPath}"`
);
fs.copySync(
path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'debian', 'lintian-overrides'),
path.join(
CONFIG.repositoryRootPath,
'resources',
'linux',
'debian',
'lintian-overrides'
),
path.join(debianPackageLintianOverridesDirPath, atomExecutableName)
)
);
console.log(`Copying polkit configuration into "${debianPackageShareDirPath}"`)
console.log(
`Copying polkit configuration into "${debianPackageShareDirPath}"`
);
fs.copySync(
path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'atom.policy'),
path.join(debianPackageShareDirPath, 'polkit-1', 'actions', 'atom.policy')
)
);
console.log(`Generating .deb file from ${debianPackageDirPath}`)
spawnSync('fakeroot', ['dpkg-deb', '-b', debianPackageDirPath], {stdio: 'inherit'})
console.log(`Generating .deb file from ${debianPackageDirPath}`);
spawnSync('fakeroot', ['dpkg-deb', '-b', debianPackageDirPath], {
stdio: 'inherit'
});
console.log(`Copying generated package into "${outputDebianPackageFilePath}"`)
fs.copySync(`${debianPackageDirPath}.deb`, outputDebianPackageFilePath)
}
console.log(
`Copying generated package into "${outputDebianPackageFilePath}"`
);
fs.copySync(`${debianPackageDirPath}.deb`, outputDebianPackageFilePath);
};

View File

@ -1,54 +1,79 @@
'use strict'
'use strict';
const assert = require('assert')
const fs = require('fs-extra')
const path = require('path')
const spawnSync = require('./spawn-sync')
const template = require('lodash.template')
const assert = require('assert');
const fs = require('fs-extra');
const path = require('path');
const spawnSync = require('./spawn-sync');
const template = require('lodash.template');
const CONFIG = require('../config')
const CONFIG = require('../config');
module.exports = function (packagedAppPath) {
console.log(`Creating rpm package for "${packagedAppPath}"`)
const atomExecutableName = CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}`
const apmExecutableName = CONFIG.channel === 'stable' ? 'apm' : `apm-${CONFIG.channel}`
const appName = CONFIG.appName
const appDescription = CONFIG.appMetadata.description
module.exports = function(packagedAppPath) {
console.log(`Creating rpm package for "${packagedAppPath}"`);
const atomExecutableName =
CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}`;
const apmExecutableName =
CONFIG.channel === 'stable' ? 'apm' : `apm-${CONFIG.channel}`;
const appName = CONFIG.appName;
const appDescription = CONFIG.appMetadata.description;
// RPM versions can't have dashes or tildes in them.
// (Ref.: https://twiki.cern.ch/twiki/bin/view/Main/RPMAndDebVersioning)
const appVersion = CONFIG.appMetadata.version.replace(/-/g, '.')
const appVersion = CONFIG.appMetadata.version.replace(/-/g, '.');
const rpmPackageDirPath = path.join(CONFIG.homeDirPath, 'rpmbuild')
const rpmPackageBuildDirPath = path.join(rpmPackageDirPath, 'BUILD')
const rpmPackageSourcesDirPath = path.join(rpmPackageDirPath, 'SOURCES')
const rpmPackageSpecsDirPath = path.join(rpmPackageDirPath, 'SPECS')
const rpmPackageRpmsDirPath = path.join(rpmPackageDirPath, 'RPMS')
const rpmPackageApplicationDirPath = path.join(rpmPackageBuildDirPath, appName)
const rpmPackageIconsDirPath = path.join(rpmPackageBuildDirPath, 'icons')
const rpmPackageDirPath = path.join(CONFIG.homeDirPath, 'rpmbuild');
const rpmPackageBuildDirPath = path.join(rpmPackageDirPath, 'BUILD');
const rpmPackageSourcesDirPath = path.join(rpmPackageDirPath, 'SOURCES');
const rpmPackageSpecsDirPath = path.join(rpmPackageDirPath, 'SPECS');
const rpmPackageRpmsDirPath = path.join(rpmPackageDirPath, 'RPMS');
const rpmPackageApplicationDirPath = path.join(
rpmPackageBuildDirPath,
appName
);
const rpmPackageIconsDirPath = path.join(rpmPackageBuildDirPath, 'icons');
if (fs.existsSync(rpmPackageDirPath)) {
console.log(`Deleting existing rpm build directory at "${rpmPackageDirPath}"`)
fs.removeSync(rpmPackageDirPath)
console.log(
`Deleting existing rpm build directory at "${rpmPackageDirPath}"`
);
fs.removeSync(rpmPackageDirPath);
}
console.log(`Creating rpm package directory structure at "${rpmPackageDirPath}"`)
fs.mkdirpSync(rpmPackageDirPath)
fs.mkdirpSync(rpmPackageBuildDirPath)
fs.mkdirpSync(rpmPackageSourcesDirPath)
fs.mkdirpSync(rpmPackageSpecsDirPath)
console.log(
`Creating rpm package directory structure at "${rpmPackageDirPath}"`
);
fs.mkdirpSync(rpmPackageDirPath);
fs.mkdirpSync(rpmPackageBuildDirPath);
fs.mkdirpSync(rpmPackageSourcesDirPath);
fs.mkdirpSync(rpmPackageSpecsDirPath);
console.log(`Copying "${packagedAppPath}" to "${rpmPackageApplicationDirPath}"`)
fs.copySync(packagedAppPath, rpmPackageApplicationDirPath)
console.log(
`Copying "${packagedAppPath}" to "${rpmPackageApplicationDirPath}"`
);
fs.copySync(packagedAppPath, rpmPackageApplicationDirPath);
console.log(`Copying icons into "${rpmPackageIconsDirPath}"`)
console.log(`Copying icons into "${rpmPackageIconsDirPath}"`);
fs.copySync(
path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'png'),
path.join(
CONFIG.repositoryRootPath,
'resources',
'app-icons',
CONFIG.channel,
'png'
),
rpmPackageIconsDirPath
)
);
console.log(`Writing rpm package spec file into "${rpmPackageSpecsDirPath}"`)
const rpmPackageSpecFilePath = path.join(rpmPackageSpecsDirPath, 'atom.spec')
const rpmPackageSpecsTemplate = fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'redhat', 'atom.spec.in'))
console.log(`Writing rpm package spec file into "${rpmPackageSpecsDirPath}"`);
const rpmPackageSpecFilePath = path.join(rpmPackageSpecsDirPath, 'atom.spec');
const rpmPackageSpecsTemplate = fs.readFileSync(
path.join(
CONFIG.repositoryRootPath,
'resources',
'linux',
'redhat',
'atom.spec.in'
)
);
const rpmPackageSpecsContents = template(rpmPackageSpecsTemplate)({
appName: appName,
appFileName: atomExecutableName,
@ -56,41 +81,65 @@ module.exports = function (packagedAppPath) {
description: appDescription,
installDir: '/usr',
version: appVersion
})
fs.writeFileSync(rpmPackageSpecFilePath, rpmPackageSpecsContents)
});
fs.writeFileSync(rpmPackageSpecFilePath, rpmPackageSpecsContents);
console.log(`Writing desktop entry file into "${rpmPackageBuildDirPath}"`)
const desktopEntryTemplate = fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'atom.desktop.in'))
console.log(`Writing desktop entry file into "${rpmPackageBuildDirPath}"`);
const desktopEntryTemplate = fs.readFileSync(
path.join(
CONFIG.repositoryRootPath,
'resources',
'linux',
'atom.desktop.in'
)
);
const desktopEntryContents = template(desktopEntryTemplate)({
appName: appName,
appFileName: atomExecutableName,
description: appDescription,
installDir: '/usr',
iconPath: atomExecutableName
})
fs.writeFileSync(path.join(rpmPackageBuildDirPath, `${atomExecutableName}.desktop`), desktopEntryContents)
});
fs.writeFileSync(
path.join(rpmPackageBuildDirPath, `${atomExecutableName}.desktop`),
desktopEntryContents
);
console.log(`Copying atom.sh into "${rpmPackageBuildDirPath}"`)
console.log(`Copying atom.sh into "${rpmPackageBuildDirPath}"`);
fs.copySync(
path.join(CONFIG.repositoryRootPath, 'atom.sh'),
path.join(rpmPackageBuildDirPath, 'atom.sh')
)
);
console.log(`Copying atom.policy into "${rpmPackageBuildDirPath}"`)
console.log(`Copying atom.policy into "${rpmPackageBuildDirPath}"`);
fs.copySync(
path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'atom.policy'),
path.join(rpmPackageBuildDirPath, 'atom.policy')
)
);
console.log(`Generating .rpm package from "${rpmPackageDirPath}"`)
spawnSync('rpmbuild', ['-ba', '--clean', rpmPackageSpecFilePath])
console.log(`Generating .rpm package from "${rpmPackageDirPath}"`);
spawnSync('rpmbuild', ['-ba', '--clean', rpmPackageSpecFilePath]);
for (let generatedArch of fs.readdirSync(rpmPackageRpmsDirPath)) {
const generatedArchDirPath = path.join(rpmPackageRpmsDirPath, generatedArch)
const generatedPackageFileNames = fs.readdirSync(generatedArchDirPath)
assert(generatedPackageFileNames.length === 1, 'Generated more than one rpm package')
const generatedPackageFilePath = path.join(generatedArchDirPath, generatedPackageFileNames[0])
const outputRpmPackageFilePath = path.join(CONFIG.buildOutputPath, `atom.${generatedArch}.rpm`)
console.log(`Copying "${generatedPackageFilePath}" into "${outputRpmPackageFilePath}"`)
fs.copySync(generatedPackageFilePath, outputRpmPackageFilePath)
const generatedArchDirPath = path.join(
rpmPackageRpmsDirPath,
generatedArch
);
const generatedPackageFileNames = fs.readdirSync(generatedArchDirPath);
assert(
generatedPackageFileNames.length === 1,
'Generated more than one rpm package'
);
const generatedPackageFilePath = path.join(
generatedArchDirPath,
generatedPackageFileNames[0]
);
const outputRpmPackageFilePath = path.join(
CONFIG.buildOutputPath,
`atom.${generatedArch}.rpm`
);
console.log(
`Copying "${generatedPackageFilePath}" into "${outputRpmPackageFilePath}"`
);
fs.copySync(generatedPackageFilePath, outputRpmPackageFilePath);
}
}
};

View File

@ -1,40 +1,58 @@
'use strict'
'use strict';
const electronInstaller = require('electron-winstaller')
const fs = require('fs')
const glob = require('glob')
const path = require('path')
const electronInstaller = require('electron-winstaller');
const fs = require('fs');
const glob = require('glob');
const path = require('path');
const CONFIG = require('../config')
const CONFIG = require('../config');
module.exports = (packagedAppPath) => {
const archSuffix = process.arch === 'ia32' ? '' : '-' + process.arch
const updateUrlPrefix = process.env.ATOM_UPDATE_URL_PREFIX || 'https://atom.io'
module.exports = packagedAppPath => {
const archSuffix = process.arch === 'ia32' ? '' : '-' + process.arch;
const updateUrlPrefix =
process.env.ATOM_UPDATE_URL_PREFIX || 'https://atom.io';
const options = {
title: CONFIG.appName,
appDirectory: packagedAppPath,
authors: 'GitHub Inc.',
iconUrl: `https://raw.githubusercontent.com/atom/atom/master/resources/app-icons/${CONFIG.channel}/atom.ico`,
loadingGif: path.join(CONFIG.repositoryRootPath, 'resources', 'win', 'loading.gif'),
iconUrl: `https://raw.githubusercontent.com/atom/atom/master/resources/app-icons/${
CONFIG.channel
}/atom.ico`,
loadingGif: path.join(
CONFIG.repositoryRootPath,
'resources',
'win',
'loading.gif'
),
outputDirectory: CONFIG.buildOutputPath,
noMsi: true,
noDelta: CONFIG.channel === 'nightly', // Delta packages are broken for nightly versions past nightly9 due to Squirrel/NuGet limitations
remoteReleases: `${updateUrlPrefix}/api/updates${archSuffix}?version=${CONFIG.computedAppVersion}`,
remoteReleases: `${updateUrlPrefix}/api/updates${archSuffix}?version=${
CONFIG.computedAppVersion
}`,
setupExe: `AtomSetup${process.arch === 'x64' ? '-x64' : ''}.exe`,
setupIcon: path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'atom.ico')
}
setupIcon: path.join(
CONFIG.repositoryRootPath,
'resources',
'app-icons',
CONFIG.channel,
'atom.ico'
)
};
const cleanUp = () => {
const releasesPath = `${CONFIG.buildOutputPath}/RELEASES`
const releasesPath = `${CONFIG.buildOutputPath}/RELEASES`;
if (process.arch === 'x64' && fs.existsSync(releasesPath)) {
fs.renameSync(releasesPath, `${releasesPath}-x64`)
fs.renameSync(releasesPath, `${releasesPath}-x64`);
}
let appName = CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}`
for (let nupkgPath of glob.sync(`${CONFIG.buildOutputPath}/${appName}-*.nupkg`)) {
if (!nupkgPath.includes(CONFIG.computedAppVersion)) {
console.log(`Deleting downloaded nupkg for previous version at ${nupkgPath} to prevent it from being stored as an artifact`)
fs.unlinkSync(nupkgPath)
console.log(
`Deleting downloaded nupkg for previous version at ${nupkgPath} to prevent it from being stored as an artifact`
);
fs.unlinkSync(nupkgPath);
} else {
if (process.arch === 'x64') {
// Use the original .nupkg filename to generate the `atom-x64` name by inserting `-x64` after `atom`
@ -44,13 +62,14 @@ module.exports = (packagedAppPath) => {
}
}
return `${CONFIG.buildOutputPath}/${options.setupExe}`
}
return `${CONFIG.buildOutputPath}/${options.setupExe}`;
};
console.log(`Creating Windows Installer for ${packagedAppPath}`)
return electronInstaller.createWindowsInstaller(options)
console.log(`Creating Windows Installer for ${packagedAppPath}`);
return electronInstaller
.createWindowsInstaller(options)
.then(cleanUp, error => {
cleanUp()
return Promise.reject(error)
})
}
cleanUp();
return Promise.reject(error);
});
};

View File

@ -1,19 +1,22 @@
'use strict'
'use strict';
const fs = require('fs')
const path = require('path')
const fs = require('fs');
const path = require('path');
module.exports = function () {
process.env['PATH'] =
process.env['PATH']
module.exports = function() {
process.env['PATH'] = process.env['PATH']
.split(';')
.filter(function (p) {
.filter(function(p) {
if (fs.existsSync(path.join(p, 'msbuild.exe'))) {
console.log('Excluding "' + p + '" from PATH to avoid msbuild.exe mismatch that causes errors during module installation')
return false
console.log(
'Excluding "' +
p +
'" from PATH to avoid msbuild.exe mismatch that causes errors during module installation'
);
return false;
} else {
return true
return true;
}
})
.join(';')
}
.join(';');
};

View File

@ -1,28 +1,49 @@
const crypto = require('crypto')
const fs = require('fs')
const path = require('path')
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const CONFIG = require('../config')
const FINGERPRINT_PATH = path.join(CONFIG.repositoryRootPath, 'node_modules', '.dependencies-fingerprint')
const CONFIG = require('../config');
const FINGERPRINT_PATH = path.join(
CONFIG.repositoryRootPath,
'node_modules',
'.dependencies-fingerprint'
);
module.exports = {
write: function () {
const fingerprint = this.compute()
fs.writeFileSync(FINGERPRINT_PATH, fingerprint)
console.log('Wrote Dependencies Fingerprint:', FINGERPRINT_PATH, fingerprint)
write: function() {
const fingerprint = this.compute();
fs.writeFileSync(FINGERPRINT_PATH, fingerprint);
console.log(
'Wrote Dependencies Fingerprint:',
FINGERPRINT_PATH,
fingerprint
);
},
read: function () {
return fs.existsSync(FINGERPRINT_PATH) ? fs.readFileSync(FINGERPRINT_PATH, 'utf8') : null
read: function() {
return fs.existsSync(FINGERPRINT_PATH)
? fs.readFileSync(FINGERPRINT_PATH, 'utf8')
: null;
},
isOutdated: function () {
const fingerprint = this.read()
return fingerprint ? fingerprint !== this.compute() : false
isOutdated: function() {
const fingerprint = this.read();
return fingerprint ? fingerprint !== this.compute() : false;
},
compute: function () {
compute: function() {
// Include the electron minor version in the fingerprint since that changing requires a re-install
const electronVersion = CONFIG.appMetadata.electronVersion.replace(/\.\d+$/, '')
const apmVersion = CONFIG.apmMetadata.dependencies['atom-package-manager']
const body = electronVersion + apmVersion + process.platform + process.version + process.arch
return crypto.createHash('sha1').update(body).digest('hex')
const electronVersion = CONFIG.appMetadata.electronVersion.replace(
/\.\d+$/,
''
);
const apmVersion = CONFIG.apmMetadata.dependencies['atom-package-manager'];
const body =
electronVersion +
apmVersion +
process.platform +
process.version +
process.arch;
return crypto
.createHash('sha1')
.update(body)
.digest('hex');
}
}
};

View File

@ -1,19 +1,24 @@
'use strict'
'use strict';
const fs = require('fs-extra')
const path = require('path')
const syncRequest = require('sync-request')
const fs = require('fs-extra');
const path = require('path');
const syncRequest = require('sync-request');
module.exports = function (downloadURL, destinationPath) {
console.log(`Downloading file from GitHub Repository to ${destinationPath}`)
module.exports = function(downloadURL, destinationPath) {
console.log(`Downloading file from GitHub Repository to ${destinationPath}`);
const response = syncRequest('GET', downloadURL, {
'headers': {'Accept': 'application/vnd.github.v3.raw', 'User-Agent': 'Atom Build'}
})
headers: {
Accept: 'application/vnd.github.v3.raw',
'User-Agent': 'Atom Build'
}
});
if (response.statusCode === 200) {
fs.mkdirpSync(path.dirname(destinationPath))
fs.writeFileSync(destinationPath, response.body)
fs.mkdirpSync(path.dirname(destinationPath));
fs.writeFileSync(destinationPath, response.body);
} else {
throw new Error('Error downloading file. HTTP Status ' + response.statusCode + '.')
throw new Error(
'Error downloading file. HTTP Status ' + response.statusCode + '.'
);
}
}
};

View File

@ -1,44 +1,55 @@
'use strict'
'use strict';
const fs = require('fs-extra')
const glob = require('glob')
const path = require('path')
const fs = require('fs-extra');
const glob = require('glob');
const path = require('path');
const CONFIG = require('../config')
module.exports = function () {
const CONFIG = require('../config');
module.exports = function() {
if (process.platform === 'win32') {
console.log('Skipping symbol dumping because minidump is not supported on Windows'.gray)
return Promise.resolve()
console.log(
'Skipping symbol dumping because minidump is not supported on Windows'
.gray
);
return Promise.resolve();
} else {
console.log(`Dumping symbols in ${CONFIG.symbolsPath}`)
const binaryPaths = glob.sync(path.join(CONFIG.intermediateAppPath, 'node_modules', '**', '*.node'))
return Promise.all(binaryPaths.map(dumpSymbol))
console.log(`Dumping symbols in ${CONFIG.symbolsPath}`);
const binaryPaths = glob.sync(
path.join(CONFIG.intermediateAppPath, 'node_modules', '**', '*.node')
);
return Promise.all(binaryPaths.map(dumpSymbol));
}
}
};
function dumpSymbol (binaryPath) {
const minidump = require('minidump')
function dumpSymbol(binaryPath) {
const minidump = require('minidump');
return new Promise(function (resolve, reject) {
minidump.dumpSymbol(binaryPath, function (error, content) {
return new Promise(function(resolve, reject) {
minidump.dumpSymbol(binaryPath, function(error, content) {
if (error) {
console.error(error)
throw new Error(error)
console.error(error);
throw new Error(error);
} else {
const moduleLine = /MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\n/.exec(content)
const moduleLine = /MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\n/.exec(
content
);
if (moduleLine.length !== 3) {
const errorMessage = `Invalid output when dumping symbol for ${binaryPath}`
console.error(errorMessage)
throw new Error(errorMessage)
const errorMessage = `Invalid output when dumping symbol for ${binaryPath}`;
console.error(errorMessage);
throw new Error(errorMessage);
} else {
const filename = moduleLine[2]
const symbolDirPath = path.join(CONFIG.symbolsPath, filename, moduleLine[1])
const symbolFilePath = path.join(symbolDirPath, `${filename}.sym`)
fs.mkdirpSync(symbolDirPath)
fs.writeFileSync(symbolFilePath, content)
resolve()
const filename = moduleLine[2];
const symbolDirPath = path.join(
CONFIG.symbolsPath,
filename,
moduleLine[1]
);
const symbolFilePath = path.join(symbolDirPath, `${filename}.sym`);
fs.mkdirpSync(symbolDirPath);
fs.writeFileSync(symbolFilePath, content);
resolve();
}
}
})
})
});
});
}

View File

@ -1,19 +1,21 @@
'use strict'
'use strict';
const glob = require('glob')
const glob = require('glob');
module.exports = function (globPaths) {
return Promise.all(globPaths.map(g => expandGlobPath(g))).then(paths => paths.reduce((a, b) => a.concat(b), []))
}
module.exports = function(globPaths) {
return Promise.all(globPaths.map(g => expandGlobPath(g))).then(paths =>
paths.reduce((a, b) => a.concat(b), [])
);
};
function expandGlobPath (globPath) {
function expandGlobPath(globPath) {
return new Promise((resolve, reject) => {
glob(globPath, (error, paths) => {
if (error) {
reject(error)
reject(error);
} else {
resolve(paths)
resolve(paths);
}
})
})
});
});
}

View File

@ -1,53 +1,55 @@
'use strict'
'use strict';
const donna = require('donna')
const tello = require('tello')
const joanna = require('joanna')
const glob = require('glob')
const fs = require('fs-extra')
const path = require('path')
const donna = require('donna');
const tello = require('tello');
const joanna = require('joanna');
const glob = require('glob');
const fs = require('fs-extra');
const path = require('path');
const CONFIG = require('../config')
const CONFIG = require('../config');
module.exports = function () {
const generatedJSONPath = path.join(CONFIG.docsOutputPath, 'atom-api.json')
console.log(`Generating API docs at ${generatedJSONPath}`)
module.exports = function() {
const generatedJSONPath = path.join(CONFIG.docsOutputPath, 'atom-api.json');
console.log(`Generating API docs at ${generatedJSONPath}`);
// Unfortunately, correct relative paths depend on a specific working
// directory, but this script should be able to run from anywhere, so we
// muck with the cwd temporarily.
const oldWorkingDirectoryPath = process.cwd()
process.chdir(CONFIG.repositoryRootPath)
const coffeeMetadata = donna.generateMetadata(['.'])[0]
const jsMetadata = joanna(glob.sync(`src/**/*.js`))
process.chdir(oldWorkingDirectoryPath)
const oldWorkingDirectoryPath = process.cwd();
process.chdir(CONFIG.repositoryRootPath);
const coffeeMetadata = donna.generateMetadata(['.'])[0];
const jsMetadata = joanna(glob.sync(`src/**/*.js`));
process.chdir(oldWorkingDirectoryPath);
const metadata = {
repository: coffeeMetadata.repository,
version: coffeeMetadata.version,
files: Object.assign(coffeeMetadata.files, jsMetadata.files)
};
const api = tello.digest([metadata]);
Object.assign(api.classes, getAPIDocsForDependencies());
api.classes = sortObjectByKey(api.classes);
fs.mkdirpSync(CONFIG.docsOutputPath);
fs.writeFileSync(generatedJSONPath, JSON.stringify(api, null, 2));
};
function getAPIDocsForDependencies() {
const classes = {};
for (let apiJSONPath of glob.sync(
`${CONFIG.repositoryRootPath}/node_modules/*/api.json`
)) {
Object.assign(classes, require(apiJSONPath).classes);
}
const api = tello.digest([metadata])
Object.assign(api.classes, getAPIDocsForDependencies())
api.classes = sortObjectByKey(api.classes)
fs.mkdirpSync(CONFIG.docsOutputPath)
fs.writeFileSync(generatedJSONPath, JSON.stringify(api, null, 2))
return classes;
}
function getAPIDocsForDependencies () {
const classes = {}
for (let apiJSONPath of glob.sync(`${CONFIG.repositoryRootPath}/node_modules/*/api.json`)) {
Object.assign(classes, require(apiJSONPath).classes)
}
return classes
}
function sortObjectByKey (object) {
const sortedObject = {}
function sortObjectByKey(object) {
const sortedObject = {};
for (let keyName of Object.keys(object).sort()) {
sortedObject[keyName] = object[keyName]
sortedObject[keyName] = object[keyName];
}
return sortedObject
return sortedObject;
}

View File

@ -29,146 +29,255 @@ module.exports = function () {
fs.writeFileSync(path.join(CONFIG.intermediateAppPath, 'package.json'), JSON.stringify(CONFIG.appMetadata))
}
function buildBundledPackagesMetadata () {
const packages = {}
module.exports = function() {
console.log(
`Generating metadata for ${path.join(
CONFIG.intermediateAppPath,
'package.json'
)}`
);
CONFIG.appMetadata._atomPackages = buildBundledPackagesMetadata();
CONFIG.appMetadata._atomMenu = buildPlatformMenuMetadata();
CONFIG.appMetadata._atomKeymaps = buildPlatformKeymapsMetadata();
CONFIG.appMetadata._deprecatedPackages = deprecatedPackagesMetadata;
CONFIG.appMetadata.version = CONFIG.computedAppVersion;
checkDeprecatedPackagesMetadata();
fs.writeFileSync(
path.join(CONFIG.intermediateAppPath, 'package.json'),
JSON.stringify(CONFIG.appMetadata)
);
};
function buildBundledPackagesMetadata() {
const packages = {};
for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) {
const packagePath = path.join(CONFIG.intermediateAppPath, 'node_modules', packageName)
const packageMetadataPath = path.join(packagePath, 'package.json')
const packageMetadata = JSON.parse(fs.readFileSync(packageMetadataPath, 'utf8'))
normalizePackageData(packageMetadata, (msg) => {
const packagePath = path.join(
CONFIG.intermediateAppPath,
'node_modules',
packageName
);
const packageMetadataPath = path.join(packagePath, 'package.json');
const packageMetadata = JSON.parse(
fs.readFileSync(packageMetadataPath, 'utf8')
);
normalizePackageData(
packageMetadata,
msg => {
if (!msg.match(/No README data$/)) {
console.warn(`Invalid package metadata. ${packageMetadata.name}: ${msg}`)
console.warn(
`Invalid package metadata. ${packageMetadata.name}: ${msg}`
);
}
}, true)
if (packageMetadata.repository && packageMetadata.repository.url && packageMetadata.repository.type === 'git') {
packageMetadata.repository.url = packageMetadata.repository.url.replace(/^git\+/, '')
},
true
);
if (
packageMetadata.repository &&
packageMetadata.repository.url &&
packageMetadata.repository.type === 'git'
) {
packageMetadata.repository.url = packageMetadata.repository.url.replace(
/^git\+/,
''
);
}
delete packageMetadata['_from']
delete packageMetadata['_id']
delete packageMetadata['dist']
delete packageMetadata['readme']
delete packageMetadata['readmeFilename']
delete packageMetadata['_from'];
delete packageMetadata['_id'];
delete packageMetadata['dist'];
delete packageMetadata['readme'];
delete packageMetadata['readmeFilename'];
const packageModuleCache = packageMetadata._atomModuleCache || {}
if (packageModuleCache.extensions && packageModuleCache.extensions['.json']) {
const index = packageModuleCache.extensions['.json'].indexOf('package.json')
const packageModuleCache = packageMetadata._atomModuleCache || {};
if (
packageModuleCache.extensions &&
packageModuleCache.extensions['.json']
) {
const index = packageModuleCache.extensions['.json'].indexOf(
'package.json'
);
if (index !== -1) {
packageModuleCache.extensions['.json'].splice(index, 1)
packageModuleCache.extensions['.json'].splice(index, 1);
}
}
const packageNewMetadata = {metadata: packageMetadata, keymaps: {}, menus: {}, grammarPaths: [], settings: {}}
const packageNewMetadata = {
metadata: packageMetadata,
keymaps: {},
menus: {},
grammarPaths: [],
settings: {}
};
packageNewMetadata.rootDirPath = path.relative(CONFIG.intermediateAppPath, packagePath)
packageNewMetadata.rootDirPath = path.relative(
CONFIG.intermediateAppPath,
packagePath
);
if (packageMetadata.main) {
const mainPath = require.resolve(path.resolve(packagePath, packageMetadata.main))
packageNewMetadata.main = path.relative(path.join(CONFIG.intermediateAppPath, 'static'), mainPath)
const mainPath = require.resolve(
path.resolve(packagePath, packageMetadata.main)
);
packageNewMetadata.main = path.relative(
path.join(CONFIG.intermediateAppPath, 'static'),
mainPath
);
// Convert backward slashes to forward slashes in order to allow package
// main modules to be required from the snapshot. This is because we use
// forward slashes to cache the sources in the snapshot, so we need to use
// them here as well.
packageNewMetadata.main = packageNewMetadata.main.replace(/\\/g, '/')
packageNewMetadata.main = packageNewMetadata.main.replace(/\\/g, '/');
}
const packageKeymapsPath = path.join(packagePath, 'keymaps')
const packageKeymapsPath = path.join(packagePath, 'keymaps');
if (fs.existsSync(packageKeymapsPath)) {
for (let packageKeymapName of fs.readdirSync(packageKeymapsPath)) {
const packageKeymapPath = path.join(packageKeymapsPath, packageKeymapName)
if (packageKeymapPath.endsWith('.cson') || packageKeymapPath.endsWith('.json')) {
const relativePath = path.relative(CONFIG.intermediateAppPath, packageKeymapPath)
packageNewMetadata.keymaps[relativePath] = CSON.readFileSync(packageKeymapPath)
const packageKeymapPath = path.join(
packageKeymapsPath,
packageKeymapName
);
if (
packageKeymapPath.endsWith('.cson') ||
packageKeymapPath.endsWith('.json')
) {
const relativePath = path.relative(
CONFIG.intermediateAppPath,
packageKeymapPath
);
packageNewMetadata.keymaps[relativePath] = CSON.readFileSync(
packageKeymapPath
);
}
}
}
const packageMenusPath = path.join(packagePath, 'menus')
const packageMenusPath = path.join(packagePath, 'menus');
if (fs.existsSync(packageMenusPath)) {
for (let packageMenuName of fs.readdirSync(packageMenusPath)) {
const packageMenuPath = path.join(packageMenusPath, packageMenuName)
if (packageMenuPath.endsWith('.cson') || packageMenuPath.endsWith('.json')) {
const relativePath = path.relative(CONFIG.intermediateAppPath, packageMenuPath)
packageNewMetadata.menus[relativePath] = CSON.readFileSync(packageMenuPath)
const packageMenuPath = path.join(packageMenusPath, packageMenuName);
if (
packageMenuPath.endsWith('.cson') ||
packageMenuPath.endsWith('.json')
) {
const relativePath = path.relative(
CONFIG.intermediateAppPath,
packageMenuPath
);
packageNewMetadata.menus[relativePath] = CSON.readFileSync(
packageMenuPath
);
}
}
}
const packageGrammarsPath = path.join(packagePath, 'grammars')
for (let packageGrammarPath of fs.listSync(packageGrammarsPath, ['json', 'cson'])) {
const relativePath = path.relative(CONFIG.intermediateAppPath, packageGrammarPath)
packageNewMetadata.grammarPaths.push(relativePath)
const packageGrammarsPath = path.join(packagePath, 'grammars');
for (let packageGrammarPath of fs.listSync(packageGrammarsPath, [
'json',
'cson'
])) {
const relativePath = path.relative(
CONFIG.intermediateAppPath,
packageGrammarPath
);
packageNewMetadata.grammarPaths.push(relativePath);
}
const packageSettingsPath = path.join(packagePath, 'settings')
for (let packageSettingPath of fs.listSync(packageSettingsPath, ['json', 'cson'])) {
const relativePath = path.relative(CONFIG.intermediateAppPath, packageSettingPath)
packageNewMetadata.settings[relativePath] = CSON.readFileSync(packageSettingPath)
const packageSettingsPath = path.join(packagePath, 'settings');
for (let packageSettingPath of fs.listSync(packageSettingsPath, [
'json',
'cson'
])) {
const relativePath = path.relative(
CONFIG.intermediateAppPath,
packageSettingPath
);
packageNewMetadata.settings[relativePath] = CSON.readFileSync(
packageSettingPath
);
}
const packageStyleSheetsPath = path.join(packagePath, 'styles')
let styleSheets = null
const packageStyleSheetsPath = path.join(packagePath, 'styles');
let styleSheets = null;
if (packageMetadata.mainStyleSheet) {
styleSheets = [fs.resolve(packagePath, packageMetadata.mainStyleSheet)]
styleSheets = [fs.resolve(packagePath, packageMetadata.mainStyleSheet)];
} else if (packageMetadata.styleSheets) {
styleSheets = packageMetadata.styleSheets.map((name) => (
styleSheets = packageMetadata.styleSheets.map(name =>
fs.resolve(packageStyleSheetsPath, name, ['css', 'less', ''])
))
);
} else {
const indexStylesheet = fs.resolve(packagePath, 'index', ['css', 'less'])
const indexStylesheet = fs.resolve(packagePath, 'index', ['css', 'less']);
if (indexStylesheet) {
styleSheets = [indexStylesheet]
styleSheets = [indexStylesheet];
} else {
styleSheets = fs.listSync(packageStyleSheetsPath, ['css', 'less'])
styleSheets = fs.listSync(packageStyleSheetsPath, ['css', 'less']);
}
}
packageNewMetadata.styleSheetPaths =
styleSheets.map(styleSheetPath => path.relative(packagePath, styleSheetPath))
packageNewMetadata.styleSheetPaths = styleSheets.map(styleSheetPath =>
path.relative(packagePath, styleSheetPath)
);
packages[packageMetadata.name] = packageNewMetadata
packages[packageMetadata.name] = packageNewMetadata;
if (packageModuleCache.extensions) {
for (let extension of Object.keys(packageModuleCache.extensions)) {
const paths = packageModuleCache.extensions[extension]
const paths = packageModuleCache.extensions[extension];
if (paths.length === 0) {
delete packageModuleCache.extensions[extension]
delete packageModuleCache.extensions[extension];
}
}
}
}
return packages
return packages;
}
function buildPlatformMenuMetadata () {
const menuPath = path.join(CONFIG.repositoryRootPath, 'menus', `${process.platform}.cson`)
function buildPlatformMenuMetadata() {
const menuPath = path.join(
CONFIG.repositoryRootPath,
'menus',
`${process.platform}.cson`
);
if (fs.existsSync(menuPath)) {
return CSON.readFileSync(menuPath)
return CSON.readFileSync(menuPath);
} else {
return null
return null;
}
}
function buildPlatformKeymapsMetadata () {
const invalidPlatforms = ['darwin', 'freebsd', 'linux', 'sunos', 'win32'].filter(p => p !== process.platform)
const keymapsPath = path.join(CONFIG.repositoryRootPath, 'keymaps')
const keymaps = {}
function buildPlatformKeymapsMetadata() {
const invalidPlatforms = [
'darwin',
'freebsd',
'linux',
'sunos',
'win32'
].filter(p => p !== process.platform);
const keymapsPath = path.join(CONFIG.repositoryRootPath, 'keymaps');
const keymaps = {};
for (let keymapName of fs.readdirSync(keymapsPath)) {
const keymapPath = path.join(keymapsPath, keymapName)
const keymapPath = path.join(keymapsPath, keymapName);
if (keymapPath.endsWith('.cson') || keymapPath.endsWith('.json')) {
const keymapPlatform = path.basename(keymapPath, path.extname(keymapPath))
const keymapPlatform = path.basename(
keymapPath,
path.extname(keymapPath)
);
if (invalidPlatforms.indexOf(keymapPlatform) === -1) {
keymaps[path.basename(keymapPath)] = CSON.readFileSync(keymapPath)
keymaps[path.basename(keymapPath)] = CSON.readFileSync(keymapPath);
}
}
}
return keymaps
return keymaps;
}
function checkDeprecatedPackagesMetadata () {
function checkDeprecatedPackagesMetadata() {
for (let packageName of Object.keys(deprecatedPackagesMetadata)) {
const packageMetadata = deprecatedPackagesMetadata[packageName]
if (packageMetadata.version && !semver.validRange(packageMetadata.version)) {
throw new Error(`Invalid range: ${packageMetadata.version} (${packageName}).`)
const packageMetadata = deprecatedPackagesMetadata[packageName];
if (
packageMetadata.version &&
!semver.validRange(packageMetadata.version)
) {
throw new Error(
`Invalid range: ${packageMetadata.version} (${packageName}).`
);
}
}
}

View File

@ -1,18 +1,22 @@
'use strict'
'use strict';
const fs = require('fs')
const path = require('path')
const ModuleCache = require('../../src/module-cache')
const fs = require('fs');
const path = require('path');
const ModuleCache = require('../../src/module-cache');
const CONFIG = require('../config')
const CONFIG = require('../config');
module.exports = function () {
console.log(`Generating module cache for ${CONFIG.intermediateAppPath}`)
module.exports = function() {
console.log(`Generating module cache for ${CONFIG.intermediateAppPath}`);
for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) {
ModuleCache.create(path.join(CONFIG.intermediateAppPath, 'node_modules', packageName))
ModuleCache.create(
path.join(CONFIG.intermediateAppPath, 'node_modules', packageName)
);
}
ModuleCache.create(CONFIG.intermediateAppPath)
const newMetadata = JSON.parse(fs.readFileSync(path.join(CONFIG.intermediateAppPath, 'package.json')))
ModuleCache.create(CONFIG.intermediateAppPath);
const newMetadata = JSON.parse(
fs.readFileSync(path.join(CONFIG.intermediateAppPath, 'package.json'))
);
for (let folder of newMetadata._atomModuleCache.folders) {
if (folder.paths.indexOf('') !== -1) {
folder.paths = [
@ -23,9 +27,12 @@ module.exports = function () {
'src/main-process',
'static',
'vendor'
]
];
}
}
CONFIG.appMetadata = newMetadata
fs.writeFileSync(path.join(CONFIG.intermediateAppPath, 'package.json'), JSON.stringify(CONFIG.appMetadata))
}
CONFIG.appMetadata = newMetadata;
fs.writeFileSync(
path.join(CONFIG.intermediateAppPath, 'package.json'),
JSON.stringify(CONFIG.appMetadata)
);
};

View File

@ -1,96 +1,262 @@
const childProcess = require('child_process')
const fs = require('fs')
const path = require('path')
const electronLink = require('electron-link')
const terser = require('terser')
const CONFIG = require('../config')
const childProcess = require('child_process');
const fs = require('fs');
const path = require('path');
const electronLink = require('electron-link');
const terser = require('terser');
const CONFIG = require('../config');
module.exports = function (packagedAppPath) {
const snapshotScriptPath = path.join(CONFIG.buildOutputPath, 'startup.js')
const coreModules = new Set(['electron', 'atom', 'shell', 'WNdb', 'lapack', 'remote'])
const baseDirPath = path.join(CONFIG.intermediateAppPath, 'static')
let processedFiles = 0
module.exports = function(packagedAppPath) {
const snapshotScriptPath = path.join(CONFIG.buildOutputPath, 'startup.js');
const coreModules = new Set([
'electron',
'atom',
'shell',
'WNdb',
'lapack',
'remote'
]);
const baseDirPath = path.join(CONFIG.intermediateAppPath, 'static');
let processedFiles = 0;
return electronLink({
baseDirPath,
mainPath: path.resolve(baseDirPath, '..', 'src', 'initialize-application-window.js'),
mainPath: path.resolve(
baseDirPath,
'..',
'src',
'initialize-application-window.js'
),
cachePath: path.join(CONFIG.atomHomeDirPath, 'snapshot-cache'),
auxiliaryData: CONFIG.snapshotAuxiliaryData,
shouldExcludeModule: ({requiringModulePath, requiredModulePath}) => {
shouldExcludeModule: ({ requiringModulePath, requiredModulePath }) => {
if (processedFiles > 0) {
process.stdout.write('\r')
process.stdout.write('\r');
}
process.stdout.write(`Generating snapshot script at "${snapshotScriptPath}" (${++processedFiles})`)
process.stdout.write(
`Generating snapshot script at "${snapshotScriptPath}" (${++processedFiles})`
);
const requiringModuleRelativePath = path.relative(baseDirPath, requiringModulePath)
const requiredModuleRelativePath = path.relative(baseDirPath, requiredModulePath)
const requiringModuleRelativePath = path.relative(
baseDirPath,
requiringModulePath
);
const requiredModuleRelativePath = path.relative(
baseDirPath,
requiredModulePath
);
return (
requiredModulePath.endsWith('.node') ||
coreModules.has(requiredModulePath) ||
requiringModuleRelativePath.endsWith(path.join('node_modules/xregexp/xregexp-all.js')) ||
(requiredModuleRelativePath.startsWith(path.join('..', 'src')) && requiredModuleRelativePath.endsWith('-element.js')) ||
requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'dugite')) ||
requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'markdown-preview', 'node_modules', 'yaml-front-matter')) ||
requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'markdown-preview', 'node_modules', 'cheerio')) ||
requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'markdown-preview', 'node_modules', 'marked')) ||
requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'typescript-simple')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'fs-extra', 'lib', 'index.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'graceful-fs', 'graceful-fs.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'htmlparser2', 'lib', 'index.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'minimatch', 'minimatch.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'request', 'index.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'request', 'request.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'superstring', 'index.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'temp', 'lib', 'temp.js')) ||
requiringModuleRelativePath.endsWith(
path.join('node_modules/xregexp/xregexp-all.js')
) ||
(requiredModuleRelativePath.startsWith(path.join('..', 'src')) &&
requiredModuleRelativePath.endsWith('-element.js')) ||
requiredModuleRelativePath.startsWith(
path.join('..', 'node_modules', 'dugite')
) ||
requiredModuleRelativePath.startsWith(
path.join(
'..',
'node_modules',
'markdown-preview',
'node_modules',
'yaml-front-matter'
)
) ||
requiredModuleRelativePath.startsWith(
path.join(
'..',
'node_modules',
'markdown-preview',
'node_modules',
'cheerio'
)
) ||
requiredModuleRelativePath.startsWith(
path.join(
'..',
'node_modules',
'markdown-preview',
'node_modules',
'marked'
)
) ||
requiredModuleRelativePath.startsWith(
path.join('..', 'node_modules', 'typescript-simple')
) ||
requiredModuleRelativePath.endsWith(
path.join(
'node_modules',
'coffee-script',
'lib',
'coffee-script',
'register.js'
)
) ||
requiredModuleRelativePath.endsWith(
path.join('node_modules', 'fs-extra', 'lib', 'index.js')
) ||
requiredModuleRelativePath.endsWith(
path.join('node_modules', 'graceful-fs', 'graceful-fs.js')
) ||
requiredModuleRelativePath.endsWith(
path.join('node_modules', 'htmlparser2', 'lib', 'index.js')
) ||
requiredModuleRelativePath.endsWith(
path.join('node_modules', 'minimatch', 'minimatch.js')
) ||
requiredModuleRelativePath.endsWith(
path.join('node_modules', 'request', 'index.js')
) ||
requiredModuleRelativePath.endsWith(
path.join('node_modules', 'request', 'request.js')
) ||
requiredModuleRelativePath.endsWith(
path.join('node_modules', 'superstring', 'index.js')
) ||
requiredModuleRelativePath.endsWith(
path.join('node_modules', 'temp', 'lib', 'temp.js')
) ||
requiredModuleRelativePath === path.join('..', 'exports', 'atom.js') ||
requiredModuleRelativePath === path.join('..', 'src', 'electron-shims.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'atom-keymap', 'lib', 'command-event.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'babel-core', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'debug', 'node.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'git-utils', 'src', 'git.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'glob', 'glob.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'iconv-lite', 'lib', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'less', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'less', 'lib', 'less-node', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'lodash.isequal', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'node-fetch', 'lib', 'fetch-error.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'oniguruma', 'src', 'oniguruma.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'resolve', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'settings-view', 'node_modules', 'glob', 'glob.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'spellchecker', 'lib', 'spellchecker.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'spelling-manager', 'node_modules', 'natural', 'lib', 'natural', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'tar', 'tar.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'ls-archive', 'node_modules', 'tar', 'tar.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'tree-sitter', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'yauzl', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'winreg', 'lib', 'registry.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', '@atom', 'fuzzy-native', 'lib', 'main.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'vscode-ripgrep', 'lib', 'index.js') ||
requiredModuleRelativePath ===
path.join('..', 'src', 'electron-shims.js') ||
requiredModuleRelativePath ===
path.join(
'..',
'node_modules',
'atom-keymap',
'lib',
'command-event.js'
) ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'babel-core', 'index.js') ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'debug', 'node.js') ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'git-utils', 'src', 'git.js') ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'glob', 'glob.js') ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'iconv-lite', 'lib', 'index.js') ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'less', 'index.js') ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') ||
requiredModuleRelativePath ===
path.join(
'..',
'node_modules',
'less',
'lib',
'less-node',
'index.js'
) ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'lodash.isequal', 'index.js') ||
requiredModuleRelativePath ===
path.join(
'..',
'node_modules',
'node-fetch',
'lib',
'fetch-error.js'
) ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'oniguruma', 'src', 'oniguruma.js') ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'resolve', 'index.js') ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') ||
requiredModuleRelativePath ===
path.join(
'..',
'node_modules',
'settings-view',
'node_modules',
'glob',
'glob.js'
) ||
requiredModuleRelativePath ===
path.join(
'..',
'node_modules',
'spellchecker',
'lib',
'spellchecker.js'
) ||
requiredModuleRelativePath ===
path.join(
'..',
'node_modules',
'spelling-manager',
'node_modules',
'natural',
'lib',
'natural',
'index.js'
) ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'tar', 'tar.js') ||
requiredModuleRelativePath ===
path.join(
'..',
'node_modules',
'ls-archive',
'node_modules',
'tar',
'tar.js'
) ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'tree-sitter', 'index.js') ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'yauzl', 'index.js') ||
requiredModuleRelativePath ===
path.join('..', 'node_modules', 'winreg', 'lib', 'registry.js') ||
requiredModuleRelativePath ===
path.join(
'..',
'node_modules',
'@atom',
'fuzzy-native',
'lib',
'main.js'
) ||
requiredModuleRelativePath ===
path.join(
'..',
'node_modules',
'vscode-ripgrep',
'lib',
'index.js'
) ||
// The startup-time script is used by both the renderer and the main process and having it in the
// snapshot causes issues.
requiredModuleRelativePath === path.join('..', 'src', 'startup-time.js')
)
);
}
}).then(({snapshotScript}) => {
process.stdout.write('\n')
}).then(({ snapshotScript }) => {
process.stdout.write('\n');
process.stdout.write('Minifying startup script')
process.stdout.write('Minifying startup script');
const minification = terser.minify(snapshotScript, {
keep_fnames: true,
keep_classnames: true,
compress: {keep_fargs: true, keep_infinity: true}
})
if (minification.error) throw minification.error
process.stdout.write('\n')
fs.writeFileSync(snapshotScriptPath, minification.code)
compress: { keep_fargs: true, keep_infinity: true }
});
if (minification.error) throw minification.error;
process.stdout.write('\n');
fs.writeFileSync(snapshotScriptPath, minification.code);
console.log('Verifying if snapshot can be executed via `mksnapshot`')
const verifySnapshotScriptPath = path.join(CONFIG.repositoryRootPath, 'script', 'verify-snapshot-script')
let nodeBundledInElectronPath
console.log('Verifying if snapshot can be executed via `mksnapshot`');
const verifySnapshotScriptPath = path.join(
CONFIG.repositoryRootPath,
'script',
'verify-snapshot-script'
);
let nodeBundledInElectronPath;
if (process.platform === 'darwin') {
nodeBundledInElectronPath = path.join(packagedAppPath, 'Contents', 'MacOS', CONFIG.executableName)
} else {
@ -99,39 +265,49 @@ module.exports = function (packagedAppPath) {
childProcess.execFileSync(
nodeBundledInElectronPath,
[verifySnapshotScriptPath, snapshotScriptPath],
{env: Object.assign({}, process.env, {ELECTRON_RUN_AS_NODE: 1})}
)
{ env: Object.assign({}, process.env, { ELECTRON_RUN_AS_NODE: 1 }) }
);
console.log('Generating startup blob with mksnapshot')
childProcess.spawnSync(
process.execPath, [
path.join(CONFIG.repositoryRootPath, 'script', 'node_modules', 'electron-mksnapshot', 'mksnapshot.js'),
console.log('Generating startup blob with mksnapshot');
childProcess.spawnSync(process.execPath, [
path.join(
CONFIG.repositoryRootPath,
'script',
'node_modules',
'electron-mksnapshot',
'mksnapshot.js'
),
snapshotScriptPath,
'--output_dir',
CONFIG.buildOutputPath
]
)
]);
let startupBlobDestinationPath
let startupBlobDestinationPath;
if (process.platform === 'darwin') {
startupBlobDestinationPath = `${packagedAppPath}/Contents/Frameworks/Electron Framework.framework/Resources`
startupBlobDestinationPath = `${packagedAppPath}/Contents/Frameworks/Electron Framework.framework/Resources`;
} else {
startupBlobDestinationPath = packagedAppPath
startupBlobDestinationPath = packagedAppPath;
}
const snapshotBinaries = ['v8_context_snapshot.bin', 'snapshot_blob.bin']
const snapshotBinaries = ['v8_context_snapshot.bin', 'snapshot_blob.bin'];
for (let snapshotBinary of snapshotBinaries) {
const destinationPath = path.join(startupBlobDestinationPath, snapshotBinary)
console.log(`Moving generated startup blob into "${destinationPath}"`)
const destinationPath = path.join(
startupBlobDestinationPath,
snapshotBinary
);
console.log(`Moving generated startup blob into "${destinationPath}"`);
try {
fs.unlinkSync(destinationPath)
fs.unlinkSync(destinationPath);
} catch (err) {
// Doesn't matter if the file doesn't exist already
if (!err.code || err.code !== 'ENOENT') {
throw err
throw err;
}
}
fs.renameSync(path.join(CONFIG.buildOutputPath, snapshotBinary), destinationPath)
fs.renameSync(
path.join(CONFIG.buildOutputPath, snapshotBinary),
destinationPath
);
}
})
}
});
};

View File

@ -1,38 +1,46 @@
'use strict'
'use strict';
const fs = require('fs')
const path = require('path')
const legalEagle = require('legal-eagle')
const fs = require('fs');
const path = require('path');
const legalEagle = require('legal-eagle');
const licenseOverrides = require('../license-overrides')
const CONFIG = require('../config')
const licenseOverrides = require('../license-overrides');
const CONFIG = require('../config');
module.exports = function () {
module.exports = function() {
return new Promise((resolve, reject) => {
legalEagle({path: CONFIG.repositoryRootPath, overrides: licenseOverrides}, (err, packagesLicenses) => {
legalEagle(
{ path: CONFIG.repositoryRootPath, overrides: licenseOverrides },
(err, packagesLicenses) => {
if (err) {
reject(err)
throw new Error(err)
reject(err);
throw new Error(err);
} else {
let text =
fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'LICENSE.md'), 'utf8') + '\n\n' +
fs.readFileSync(
path.join(CONFIG.repositoryRootPath, 'LICENSE.md'),
'utf8'
) +
'\n\n' +
'This application bundles the following third-party packages in accordance\n' +
'with the following licenses:\n\n'
'with the following licenses:\n\n';
for (let packageName of Object.keys(packagesLicenses).sort()) {
const packageLicense = packagesLicenses[packageName]
text += '-------------------------------------------------------------------------\n\n'
text += `Package: ${packageName}\n`
text += `License: ${packageLicense.license}\n`
const packageLicense = packagesLicenses[packageName];
text +=
'-------------------------------------------------------------------------\n\n';
text += `Package: ${packageName}\n`;
text += `License: ${packageLicense.license}\n`;
if (packageLicense.source) {
text += `License Source: ${packageLicense.source}\n`
text += `License Source: ${packageLicense.source}\n`;
}
if (packageLicense.sourceText) {
text += `Source Text:\n\n${packageLicense.sourceText}`
text += `Source Text:\n\n${packageLicense.sourceText}`;
}
text += '\n'
text += '\n';
}
resolve(text)
resolve(text);
}
})
})
}
}
);
});
};

View File

@ -1,24 +1,29 @@
'use strict'
'use strict';
const os = require('os')
const passwdUser = require('passwd-user')
const path = require('path')
const os = require('os');
const passwdUser = require('passwd-user');
const path = require('path');
module.exports = function (aPath) {
module.exports = function(aPath) {
if (!aPath.startsWith('~')) {
return aPath
return aPath;
}
const sepIndex = aPath.indexOf(path.sep)
const user = (sepIndex < 0) ? aPath.substring(1) : aPath.substring(1, sepIndex)
const rest = (sepIndex < 0) ? '' : aPath.substring(sepIndex)
const home = (user === '') ? os.homedir() : (() => {
const passwd = passwdUser.sync(user)
const sepIndex = aPath.indexOf(path.sep);
const user = sepIndex < 0 ? aPath.substring(1) : aPath.substring(1, sepIndex);
const rest = sepIndex < 0 ? '' : aPath.substring(sepIndex);
const home =
user === ''
? os.homedir()
: (() => {
const passwd = passwdUser.sync(user);
if (passwd === undefined) {
throw new Error(`Failed to expand the tilde in ${aPath} - user "${user}" does not exist`)
throw new Error(
`Failed to expand the tilde in ${aPath} - user "${user}" does not exist`
);
}
return passwd.homedir
})()
return passwd.homedir;
})();
return `${home}${rest}`
}
return `${home}${rest}`;
};

View File

@ -1,11 +1,14 @@
'use strict'
'use strict';
const path = require('path')
const CONFIG = require('../config')
const path = require('path');
const CONFIG = require('../config');
module.exports = function (filePath) {
return !EXCLUDED_PATHS_REGEXP.test(filePath) || INCLUDED_PATHS_REGEXP.test(filePath)
}
module.exports = function(filePath) {
return (
!EXCLUDED_PATHS_REGEXP.test(filePath) ||
INCLUDED_PATHS_REGEXP.test(filePath)
);
};
const EXCLUDE_REGEXPS_SOURCES = [
escapeRegExp('.DS_Store'),
@ -35,7 +38,9 @@ const EXCLUDE_REGEXPS_SOURCES = [
escapeRegExp(path.join('npm', 'node_modules', '.bin', 'starwars')),
escapeRegExp(path.join('pegjs', 'examples')),
escapeRegExp(path.join('get-parameter-names', 'node_modules', 'testla')),
escapeRegExp(path.join('get-parameter-names', 'node_modules', '.bin', 'testla')),
escapeRegExp(
path.join('get-parameter-names', 'node_modules', '.bin', 'testla')
),
escapeRegExp(path.join('jasmine-reporters', 'ext')),
escapeRegExp(path.join('node_modules', 'nan')),
escapeRegExp(path.join('node_modules', 'native-mate')),
@ -53,7 +58,9 @@ const EXCLUDE_REGEXPS_SOURCES = [
escapeRegExp(path.join('node_modules', 'loophole')),
escapeRegExp(path.join('node_modules', 'pegjs')),
escapeRegExp(path.join('node_modules', '.bin', 'pegjs')),
escapeRegExp(path.join('node_modules', 'spellchecker', 'vendor', 'hunspell') + path.sep) + '.*',
escapeRegExp(
path.join('node_modules', 'spellchecker', 'vendor', 'hunspell') + path.sep
) + '.*',
// node_modules of the fuzzy-native package are only required for building it.
escapeRegExp(path.join('node_modules', 'fuzzy-native', 'node_modules')),
@ -66,34 +73,59 @@ const EXCLUDE_REGEXPS_SOURCES = [
escapeRegExp(path.sep) + '.+\\.target.mk$',
escapeRegExp(path.sep) + 'linker\\.lock$',
escapeRegExp(path.join('build', 'Release') + path.sep) + '.+\\.node\\.dSYM',
escapeRegExp(path.join('build', 'Release') + path.sep) + '.*\\.(pdb|lib|exp|map|ipdb|iobj)',
escapeRegExp(path.join('build', 'Release') + path.sep) +
'.*\\.(pdb|lib|exp|map|ipdb|iobj)',
// Ignore node_module files we won't need at runtime
'node_modules' + escapeRegExp(path.sep) + '.*' + escapeRegExp(path.sep) + '_*te?sts?_*' + escapeRegExp(path.sep),
'node_modules' + escapeRegExp(path.sep) + '.*' + escapeRegExp(path.sep) + 'examples?' + escapeRegExp(path.sep),
'node_modules' +
escapeRegExp(path.sep) +
'.*' +
escapeRegExp(path.sep) +
'_*te?sts?_*' +
escapeRegExp(path.sep),
'node_modules' +
escapeRegExp(path.sep) +
'.*' +
escapeRegExp(path.sep) +
'examples?' +
escapeRegExp(path.sep),
'node_modules' + escapeRegExp(path.sep) + '.*' + '\\.d\\.ts$',
'node_modules' + escapeRegExp(path.sep) + '.*' + '\\.js\\.map$',
'.*' + escapeRegExp(path.sep) + 'test.*\\.html$'
]
];
// Ignore spec directories in all bundled packages
for (let packageName in CONFIG.appMetadata.packageDependencies) {
EXCLUDE_REGEXPS_SOURCES.push('^' + escapeRegExp(path.join(CONFIG.repositoryRootPath, 'node_modules', packageName, 'spec')))
EXCLUDE_REGEXPS_SOURCES.push(
'^' +
escapeRegExp(
path.join(
CONFIG.repositoryRootPath,
'node_modules',
packageName,
'spec'
)
)
);
}
// Ignore Hunspell dictionaries only on macOS.
if (process.platform === 'darwin') {
EXCLUDE_REGEXPS_SOURCES.push(escapeRegExp(path.join('spellchecker', 'vendor', 'hunspell_dictionaries')))
EXCLUDE_REGEXPS_SOURCES.push(
escapeRegExp(path.join('spellchecker', 'vendor', 'hunspell_dictionaries'))
);
}
const EXCLUDED_PATHS_REGEXP = new RegExp(
EXCLUDE_REGEXPS_SOURCES.map(path => `(${path})`).join('|')
)
);
const INCLUDED_PATHS_REGEXP = new RegExp(
escapeRegExp(path.join('node_modules', 'node-gyp', 'src', 'win_delay_load_hook.cc'))
)
escapeRegExp(
path.join('node_modules', 'node-gyp', 'src', 'win_delay_load_hook.cc')
)
);
function escapeRegExp (string) {
return string.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&')
function escapeRegExp(string) {
return string.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&');
}

View File

@ -1,15 +1,15 @@
'use strict'
'use strict';
const childProcess = require('child_process')
const childProcess = require('child_process');
const CONFIG = require('../config')
const CONFIG = require('../config');
module.exports = function (ci) {
console.log('Installing apm')
module.exports = function(ci) {
console.log('Installing apm');
// npm ci leaves apm with a bunch of unmet dependencies
childProcess.execFileSync(
CONFIG.getNpmBinPath(),
['--global-style', '--loglevel=error', 'install'],
{env: process.env, cwd: CONFIG.apmRootPath}
)
}
{ env: process.env, cwd: CONFIG.apmRootPath }
);
};

View File

@ -1,22 +1,26 @@
'use strict'
'use strict';
const fs = require('fs-extra')
const handleTilde = require('./handle-tilde')
const path = require('path')
const template = require('lodash.template')
const startCase = require('lodash.startcase')
const execSync = require('child_process').execSync
const fs = require('fs-extra');
const handleTilde = require('./handle-tilde');
const path = require('path');
const template = require('lodash.template');
const startCase = require('lodash.startcase');
const execSync = require('child_process').execSync;
const CONFIG = require('../config')
const CONFIG = require('../config');
function install (installationDirPath, packagedAppFileName, packagedAppPath) {
function install(installationDirPath, packagedAppFileName, packagedAppPath) {
if (fs.existsSync(installationDirPath)) {
console.log(`Removing previously installed "${packagedAppFileName}" at "${installationDirPath}"`)
fs.removeSync(installationDirPath)
console.log(
`Removing previously installed "${packagedAppFileName}" at "${installationDirPath}"`
);
fs.removeSync(installationDirPath);
}
console.log(`Installing "${packagedAppFileName}" at "${installationDirPath}"`)
fs.copySync(packagedAppPath, installationDirPath)
console.log(
`Installing "${packagedAppFileName}" at "${installationDirPath}"`
);
fs.copySync(packagedAppPath, installationDirPath);
}
/**
@ -26,132 +30,205 @@ function install (installationDirPath, packagedAppFileName, packagedAppPath) {
* and the XDG Base Directory Specification:
* https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables
*/
function findBaseIconThemeDirPath () {
const defaultBaseIconThemeDir = '/usr/share/icons/hicolor'
const dataDirsString = process.env.XDG_DATA_DIRS
function findBaseIconThemeDirPath() {
const defaultBaseIconThemeDir = '/usr/share/icons/hicolor';
const dataDirsString = process.env.XDG_DATA_DIRS;
if (dataDirsString) {
const dataDirs = dataDirsString.split(path.delimiter)
const dataDirs = dataDirsString.split(path.delimiter);
if (dataDirs.includes('/usr/share/') || dataDirs.includes('/usr/share')) {
return defaultBaseIconThemeDir
return defaultBaseIconThemeDir;
} else {
return path.join(dataDirs[0], 'icons', 'hicolor')
return path.join(dataDirs[0], 'icons', 'hicolor');
}
} else {
return defaultBaseIconThemeDir
return defaultBaseIconThemeDir;
}
}
module.exports = function (packagedAppPath, installDir) {
const packagedAppFileName = path.basename(packagedAppPath)
module.exports = function(packagedAppPath, installDir) {
const packagedAppFileName = path.basename(packagedAppPath);
if (process.platform === 'darwin') {
const installPrefix = installDir !== '' ? handleTilde(installDir) : path.join(path.sep, 'Applications')
const installationDirPath = path.join(installPrefix, packagedAppFileName)
install(installationDirPath, packagedAppFileName, packagedAppPath)
const installPrefix =
installDir !== ''
? handleTilde(installDir)
: path.join(path.sep, 'Applications');
const installationDirPath = path.join(installPrefix, packagedAppFileName);
install(installationDirPath, packagedAppFileName, packagedAppPath);
} else if (process.platform === 'win32') {
const installPrefix = installDir !== '' ? installDir : process.env.LOCALAPPDATA
const installationDirPath = path.join(installPrefix, packagedAppFileName, 'app-dev')
const installPrefix =
installDir !== '' ? installDir : process.env.LOCALAPPDATA;
const installationDirPath = path.join(
installPrefix,
packagedAppFileName,
'app-dev'
);
try {
install(installationDirPath, packagedAppFileName, packagedAppPath)
install(installationDirPath, packagedAppFileName, packagedAppPath);
} catch (e) {
console.log(`Administrator elevation required to install into "${installationDirPath}"`)
const fsAdmin = require('fs-admin')
console.log(
`Administrator elevation required to install into "${installationDirPath}"`
);
const fsAdmin = require('fs-admin');
return new Promise((resolve, reject) => {
fsAdmin.recursiveCopy(packagedAppPath, installationDirPath, (error) => {
error ? reject(error) : resolve()
})
})
fsAdmin.recursiveCopy(packagedAppPath, installationDirPath, error => {
error ? reject(error) : resolve();
});
});
}
} else {
const atomExecutableName = CONFIG.channel === 'stable' ? 'atom' : 'atom-' + CONFIG.channel
const apmExecutableName = CONFIG.channel === 'stable' ? 'apm' : 'apm-' + CONFIG.channel
const appName = CONFIG.channel === 'stable' ? 'Atom' : startCase('Atom ' + CONFIG.channel)
const appDescription = CONFIG.appMetadata.description
const prefixDirPath = installDir !== '' ? handleTilde(installDir) : path.join('/usr', 'local')
const shareDirPath = path.join(prefixDirPath, 'share')
const installationDirPath = path.join(shareDirPath, atomExecutableName)
const applicationsDirPath = path.join(shareDirPath, 'applications')
const atomExecutableName =
CONFIG.channel === 'stable' ? 'atom' : 'atom-' + CONFIG.channel;
const apmExecutableName =
CONFIG.channel === 'stable' ? 'apm' : 'apm-' + CONFIG.channel;
const appName =
CONFIG.channel === 'stable'
? 'Atom'
: startCase('Atom ' + CONFIG.channel);
const appDescription = CONFIG.appMetadata.description;
const prefixDirPath =
installDir !== '' ? handleTilde(installDir) : path.join('/usr', 'local');
const shareDirPath = path.join(prefixDirPath, 'share');
const installationDirPath = path.join(shareDirPath, atomExecutableName);
const applicationsDirPath = path.join(shareDirPath, 'applications');
const binDirPath = path.join(prefixDirPath, 'bin')
const binDirPath = path.join(prefixDirPath, 'bin');
fs.mkdirpSync(applicationsDirPath)
fs.mkdirpSync(binDirPath)
fs.mkdirpSync(applicationsDirPath);
fs.mkdirpSync(binDirPath);
install(installationDirPath, packagedAppFileName, packagedAppPath)
install(installationDirPath, packagedAppFileName, packagedAppPath);
{ // Install icons
const baseIconThemeDirPath = findBaseIconThemeDirPath()
const fullIconName = atomExecutableName + '.png'
{
// Install icons
const baseIconThemeDirPath = findBaseIconThemeDirPath();
const fullIconName = atomExecutableName + '.png';
let existingIconsFound = false
let existingIconsFound = false;
fs.readdirSync(baseIconThemeDirPath).forEach(size => {
const iconPath = path.join(baseIconThemeDirPath, size, 'apps', fullIconName)
const iconPath = path.join(
baseIconThemeDirPath,
size,
'apps',
fullIconName
);
if (fs.existsSync(iconPath)) {
if (!existingIconsFound) {
console.log(`Removing existing icons from "${baseIconThemeDirPath}"`)
console.log(
`Removing existing icons from "${baseIconThemeDirPath}"`
);
}
existingIconsFound = true
fs.removeSync(iconPath)
existingIconsFound = true;
fs.removeSync(iconPath);
}
})
});
console.log(`Installing icons at "${baseIconThemeDirPath}"`)
const appIconsPath = path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'png')
console.log(`Installing icons at "${baseIconThemeDirPath}"`);
const appIconsPath = path.join(
CONFIG.repositoryRootPath,
'resources',
'app-icons',
CONFIG.channel,
'png'
);
fs.readdirSync(appIconsPath).forEach(imageName => {
if (/\.png$/.test(imageName)) {
const size = path.basename(imageName, '.png')
const iconPath = path.join(appIconsPath, imageName)
fs.copySync(iconPath, path.join(baseIconThemeDirPath, `${size}x${size}`, 'apps', fullIconName))
const size = path.basename(imageName, '.png');
const iconPath = path.join(appIconsPath, imageName);
fs.copySync(
iconPath,
path.join(
baseIconThemeDirPath,
`${size}x${size}`,
'apps',
fullIconName
)
);
}
})
});
console.log(`Updating icon cache for "${baseIconThemeDirPath}"`)
console.log(`Updating icon cache for "${baseIconThemeDirPath}"`);
try {
execSync(`gtk-update-icon-cache ${baseIconThemeDirPath} --force`)
execSync(`gtk-update-icon-cache ${baseIconThemeDirPath} --force`);
} catch (e) {}
}
{ // Install xdg desktop file
const desktopEntryPath = path.join(applicationsDirPath, `${atomExecutableName}.desktop`)
{
// Install xdg desktop file
const desktopEntryPath = path.join(
applicationsDirPath,
`${atomExecutableName}.desktop`
);
if (fs.existsSync(desktopEntryPath)) {
console.log(`Removing existing desktop entry file at "${desktopEntryPath}"`)
fs.removeSync(desktopEntryPath)
console.log(
`Removing existing desktop entry file at "${desktopEntryPath}"`
);
fs.removeSync(desktopEntryPath);
}
console.log(`Writing desktop entry file at "${desktopEntryPath}"`)
const desktopEntryTemplate = fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'atom.desktop.in'))
console.log(`Writing desktop entry file at "${desktopEntryPath}"`);
const desktopEntryTemplate = fs.readFileSync(
path.join(
CONFIG.repositoryRootPath,
'resources',
'linux',
'atom.desktop.in'
)
);
const desktopEntryContents = template(desktopEntryTemplate)({
appName,
appFileName: atomExecutableName,
description: appDescription,
installDir: prefixDirPath,
iconPath: atomExecutableName
})
fs.writeFileSync(desktopEntryPath, desktopEntryContents)
});
fs.writeFileSync(desktopEntryPath, desktopEntryContents);
}
{ // Add atom executable to the PATH
const atomBinDestinationPath = path.join(binDirPath, atomExecutableName)
{
// Add atom executable to the PATH
const atomBinDestinationPath = path.join(binDirPath, atomExecutableName);
if (fs.existsSync(atomBinDestinationPath)) {
console.log(`Removing existing executable at "${atomBinDestinationPath}"`)
fs.removeSync(atomBinDestinationPath)
console.log(
`Removing existing executable at "${atomBinDestinationPath}"`
);
fs.removeSync(atomBinDestinationPath);
}
console.log(`Copying atom.sh to "${atomBinDestinationPath}"`)
fs.copySync(path.join(CONFIG.repositoryRootPath, 'atom.sh'), atomBinDestinationPath)
console.log(`Copying atom.sh to "${atomBinDestinationPath}"`);
fs.copySync(
path.join(CONFIG.repositoryRootPath, 'atom.sh'),
atomBinDestinationPath
);
}
{ // Link apm executable to the PATH
const apmBinDestinationPath = path.join(binDirPath, apmExecutableName)
{
// Link apm executable to the PATH
const apmBinDestinationPath = path.join(binDirPath, apmExecutableName);
try {
fs.lstatSync(apmBinDestinationPath)
console.log(`Removing existing executable at "${apmBinDestinationPath}"`)
fs.removeSync(apmBinDestinationPath)
} catch (e) { }
console.log(`Symlinking apm to "${apmBinDestinationPath}"`)
fs.symlinkSync(path.join('..', 'share', atomExecutableName, 'resources', 'app', 'apm', 'node_modules', '.bin', 'apm'), apmBinDestinationPath)
fs.lstatSync(apmBinDestinationPath);
console.log(
`Removing existing executable at "${apmBinDestinationPath}"`
);
fs.removeSync(apmBinDestinationPath);
} catch (e) {}
console.log(`Symlinking apm to "${apmBinDestinationPath}"`);
fs.symlinkSync(
path.join(
'..',
'share',
atomExecutableName,
'resources',
'app',
'apm',
'node_modules',
'.bin',
'apm'
),
apmBinDestinationPath
);
}
console.log(`Changing permissions to 755 for "${installationDirPath}"`)
fs.chmodSync(installationDirPath, '755')
console.log(`Changing permissions to 755 for "${installationDirPath}"`);
fs.chmodSync(installationDirPath, '755');
}
return Promise.resolve()
}
return Promise.resolve();
};

View File

@ -1,14 +1,14 @@
'use strict'
'use strict';
const childProcess = require('child_process')
const childProcess = require('child_process');
const CONFIG = require('../config')
const CONFIG = require('../config');
module.exports = function (ci) {
console.log('Installing script dependencies')
module.exports = function(ci) {
console.log('Installing script dependencies');
childProcess.execFileSync(
CONFIG.getNpmBinPath(ci),
['--loglevel=error', ci ? 'ci' : 'install'],
{env: process.env, cwd: CONFIG.scriptRootPath}
)
}
{ env: process.env, cwd: CONFIG.scriptRootPath }
);
};

Some files were not shown because too many files have changed in this diff Show More