diff --git a/spec/ci-spec.js b/spec/ci-spec.js index 3329401c..6395e648 100644 --- a/spec/ci-spec.js +++ b/spec/ci-spec.js @@ -57,7 +57,7 @@ describe('apm ci', () => { apm.run(['ci'], callback); waitsFor('waiting for install to complete', 600000, () => callback.callCount > 0); runs(() => { - expect(callback.mostRecentCall.args[0]).toBeNull(); + expect(callback.mostRecentCall.args[0]).toBeUndefined(); const pjson0 = CSON.readFileSync(path.join('node_modules', 'test-module-with-dependencies', 'package.json')); expect(pjson0.version).toBe('1.1.0'); const pjson1 = CSON.readFileSync(path.join('node_modules', 'test-module', 'package.json')); @@ -78,12 +78,12 @@ describe('apm ci', () => { apm.run(['install'], callback0); waitsFor('waiting for install to complete', 600000, () => callback0.callCount > 0); runs(() => { - expect(callback0.mostRecentCall.args[0]).toBeNull(); + expect(callback0.mostRecentCall.args[0]).toBeUndefined(); apm.run(['ci'], callback1); }); waitsFor('waiting for ci to complete', 600000, () => callback1.callCount > 0); runs(() => { - expect(callback1.mostRecentCall.args[0]).toBeNull(); + expect(callback1.mostRecentCall.args[0]).toBeUndefined(); expect(fs.existsSync(path.join(moduleDirectory, 'node_modules', 'native-module', 'build', 'Release', 'native.node'))).toBeTruthy(); }); }); diff --git a/spec/develop-spec.js b/spec/develop-spec.js index 63e85cd4..7340ef26 100644 --- a/spec/develop-spec.js +++ b/spec/develop-spec.js @@ -2,6 +2,7 @@ const path = require('path'); const fs = require('fs-plus'); const temp = require('temp'); const apm = require('../src/apm-cli'); +const Develop = require('../src/develop'); describe('apm develop', () => { let linkedRepoPath, repoPath; @@ -19,10 +20,7 @@ describe('apm develop', () => { describe("when the package doesn't have a published repository url", () => { it('logs an error', () => { - const Develop = require('../src/develop'); - spyOn(Develop.prototype, 'getRepositoryUrl').andCallFake((packageName, callback) => { - callback('Here is the error'); - }); + spyOn(Develop.prototype, 'getRepositoryUrl').andCallFake(_packageName => Promise.reject('Here is the error')); const callback = jasmine.createSpy('callback'); apm.run(['develop', 'fake-package'], callback); waitsFor('waiting for develop to complete', () => callback.callCount === 1); @@ -36,13 +34,12 @@ describe('apm develop', () => { describe("when the repository hasn't been cloned", () => { it('clones the repository to ATOM_REPOS_HOME and links it to ATOM_HOME/dev/packages', () => { - const Develop = require('../src/develop'); - spyOn(Develop.prototype, 'getRepositoryUrl').andCallFake((packageName, callback) => { + spyOn(Develop.prototype, 'getRepositoryUrl').andCallFake(_packageName => { const repoUrl = path.join(__dirname, 'fixtures', 'repo.git'); - callback(null, repoUrl); + return Promise.resolve(repoUrl); }); spyOn(Develop.prototype, 'installDependencies').andCallFake(function (packageDirectory, options) { - this.linkPackage(packageDirectory, options); + return this.linkPackage(packageDirectory, options); }); const callback = jasmine.createSpy('callback'); apm.run(['develop', 'fake-package'], callback); diff --git a/spec/install-spec.js b/spec/install-spec.js index 36d01a70..62a50aa3 100644 --- a/spec/install-spec.js +++ b/spec/install-spec.js @@ -105,7 +105,7 @@ describe('apm install', () => { expect(fs.existsSync(existingTestModuleFile)).toBeFalsy(); expect(fs.existsSync(path.join(testModuleDirectory, 'index.js'))).toBeTruthy(); expect(fs.existsSync(path.join(testModuleDirectory, 'package.json'))).toBeTruthy(); - expect(callback.mostRecentCall.args[0]).toBeNull(); + expect(callback.mostRecentCall.args[0]).toBeUndefined(); }); }); @@ -121,7 +121,7 @@ describe('apm install', () => { runs(() => { expect(JSON.parse(fs.readFileSync(path.join(packageDirectory, 'package.json'))).version).toBe('1.1.0'); - expect(callback.mostRecentCall.args[0]).toBeNull(); + expect(callback.mostRecentCall.args[0]).toBeUndefined(); }); }); @@ -136,7 +136,7 @@ describe('apm install', () => { runs(() => { expect(JSON.parse(fs.readFileSync(path.join(packageDirectory, 'package.json'))).version).toBe('1.1.0'); - expect(callback.mostRecentCall.args[0]).toBeNull(); + expect(callback.mostRecentCall.args[0]).toBeUndefined(); }); }); @@ -173,7 +173,7 @@ describe('apm install', () => { expect(fs.existsSync(testModuleDirectory)).toBeTruthy(); expect(fs.existsSync(path.join(testModuleDirectory, 'index.js'))).toBeTruthy(); expect(fs.existsSync(path.join(testModuleDirectory, 'package.json'))).toBeTruthy(); - expect(callback.mostRecentCall.args[0]).toBeNull(); + expect(callback.mostRecentCall.args[0]).toBeUndefined(); }); }); }); @@ -195,7 +195,7 @@ describe('apm install', () => { expect(fs.existsSync(path.join(testModuleDirectory, 'package.json'))).toBeTruthy(); expect(fs.existsSync(path.join(testModule2Directory, 'index2.js'))).toBeTruthy(); expect(fs.existsSync(path.join(testModule2Directory, 'package.json'))).toBeTruthy(); - expect(callback.mostRecentCall.args[0]).toBeNull(); + expect(callback.mostRecentCall.args[0]).toBeUndefined(); }); }); @@ -305,7 +305,7 @@ describe('apm install', () => { expect(fs.existsSync(path.join(testModuleDirectory, 'package.json'))).toBeTruthy(); expect(fs.existsSync(path.join(testModule2Directory, 'index2.js'))).toBeTruthy(); expect(fs.existsSync(path.join(testModule2Directory, 'package.json'))).toBeTruthy(); - expect(callback.mostRecentCall.args[0]).toBeNull(); + expect(callback.mostRecentCall.args[0]).toBeUndefined(); }); }); @@ -424,18 +424,17 @@ describe('apm install', () => { beforeEach(() => { install = new Install(); - const fakeCloneRepository = (url, ...args) => { - const callback = args[args.length - 1]; + const fakeCloneRepository = async (url, ..._rest) => { if (url !== urls[2]) { - callback(new Error('Failed to clone')); + throw new Error('Failed to clone'); } }; spyOn(install, 'cloneNormalizedUrl').andCallFake(fakeCloneRepository); }); - it('tries cloning the next URL until one works', () => { - install.cloneFirstValidGitUrl(urls, {}, () => {}); + it('tries cloning the next URL until one works', async () => { + await install.cloneFirstValidGitUrl(urls, {}, () => {}); expect(install.cloneNormalizedUrl.calls.length).toBe(3); expect(install.cloneNormalizedUrl.argsForCall[0][0]).toBe(urls[0]); expect(install.cloneNormalizedUrl.argsForCall[1][0]).toBe(urls[1]); @@ -565,7 +564,7 @@ describe('apm install', () => { }); }); - describe('when installing a registred package and --json is specified', () => { + describe('when installing a registered package and --json is specified', () => { beforeEach(() => { const callback = jasmine.createSpy('callback'); apm.run(['install', 'test-module', 'test-module2', '--json'], callback); @@ -609,7 +608,7 @@ describe('apm install', () => { waitsFor('waiting for install to complete', 600000, () => callback.callCount === 1); runs(() => { - expect(callback.mostRecentCall.args[0]).toBeNull(); + expect(callback.mostRecentCall.args[0]).toBeUndefined(); const testModuleDirectory = path.join(atomHome, 'packages', 'native-package'); expect(fs.existsSync(path.join(testModuleDirectory, 'index.js'))).toBeTruthy(); @@ -624,5 +623,6 @@ describe('apm install', () => { }); }); }); + }); }); diff --git a/spec/spec-helper.js b/spec/spec-helper.js index 3ae4cac9..e825916b 100644 --- a/spec/spec-helper.js +++ b/spec/spec-helper.js @@ -14,4 +14,4 @@ global.silenceOutput = (callThrough = false) => { } }; -global.spyOnToken = () => spyOn(auth, 'getToken').andCallFake(callback => callback(null, 'token')); +global.spyOnToken = () => spyOn(auth, 'getToken').andCallFake(() => Promise.resolve('token')); diff --git a/spec/stars-spec.js b/spec/stars-spec.js index 11b81b72..8151bb49 100644 --- a/spec/stars-spec.js +++ b/spec/stars-spec.js @@ -83,7 +83,7 @@ describe('apm stars', () => { waitsFor('waiting for command to complete', () => callback.callCount > 0); runs(() => { - expect(callback.mostRecentCall.args[0]).toBeNull(); + expect(callback.mostRecentCall.args[0]).toBeUndefined(); expect(fs.existsSync(path.join(testModuleDirectory, 'index.js'))).toBeTruthy(); expect(fs.existsSync(path.join(testModuleDirectory, 'package.json'))).toBeTruthy(); }); diff --git a/spec/test-spec.js b/spec/test-spec.js index 6a4ea894..933d9bf6 100644 --- a/spec/test-spec.js +++ b/spec/test-spec.js @@ -58,6 +58,7 @@ describe('apm test', () => { removeListener() {} }); // no op apm.run(['test'], callback); + waitsFor('waiting for command to complete', () => callback.callCount > 0); }; describe('successfully', () => { diff --git a/spec/unpublish-spec.js b/spec/unpublish-spec.js index bfd42b2d..f296aa18 100644 --- a/spec/unpublish-spec.js +++ b/spec/unpublish-spec.js @@ -70,10 +70,7 @@ describe('apm unpublish', () => { describe('when the user accepts the default answer', () => { it('does not unpublish the package', () => { const callback = jasmine.createSpy('callback'); - spyOn(Unpublish.prototype, 'prompt').andCallFake((...args) => { - const cb = args.pop(); - cb(''); - }); + spyOn(Unpublish.prototype, 'prompt').andCallFake(_question => Promise.resolve('')); spyOn(Unpublish.prototype, 'unpublishPackage'); apm.run(['unpublish', 'test-package'], callback); @@ -129,10 +126,7 @@ describe('apm unpublish', () => { describe('when the user accepts the default answer', () => { it('does not unpublish the package', () => { const callback = jasmine.createSpy('callback'); - spyOn(Unpublish.prototype, 'prompt').andCallFake((...args) => { - const cb = args.pop(); - cb(''); - }); + spyOn(Unpublish.prototype, 'prompt').andCallFake(_question => Promise.resolve('')); spyOn(Unpublish.prototype, 'unpublishPackage'); apm.run(['unpublish', 'test-package'], callback); diff --git a/src/apm-cli.js b/src/apm-cli.js index d872af33..fe47a8ea 100644 --- a/src/apm-cli.js +++ b/src/apm-cli.js @@ -14,7 +14,7 @@ const config = require('./apm.js'); const fs = require('./fs.js'); const git = require('./git.js'); -const setupTempDirectory = function() { +function setupTempDirectory() { const temp = require('temp'); let tempDirectory = require('os').tmpdir(); // Resolve ~ in tmp dir atom/atom#2271 @@ -61,13 +61,13 @@ const commandClasses = [ const commands = {}; for (let commandClass of commandClasses) { - for (let name of commandClass.commandNames != null ? commandClass.commandNames : []) { + for (let name of commandClass.commandNames ?? []) { commands[name] = commandClass; } } -const parseOptions = function(args) { - if (args == null) { args = []; } +function parseOptions(args) { + args ??= []; const options = yargs(args).wrap(Math.min(100, yargs.terminalWidth())); options.usage(`\ @@ -95,7 +95,7 @@ Pulsar Package Manager powered by https://pulsar-edit.dev return options; }; -const showHelp = function(options) { +function showHelp(options) { if (options == null) { return; } let help = options.help(); @@ -104,15 +104,17 @@ const showHelp = function(options) { help += "\n colored output."; } - return console.error(help); + console.error(help); }; -const printVersions = function(args, callback) { - const apmVersion = require("../package.json").version ?? ""; - const npmVersion = require("npm/package.json").version ?? ""; - const nodeVersion = process.versions.node ?? ""; +async function printVersions(args) { + const apmVersion = require("../package.json").version ?? ""; + const npmVersion = require("npm/package.json").version ?? ""; + const nodeVersion = process.versions.node ?? ""; - return getPythonVersion(pythonVersion => git.getGitVersion(gitVersion => getAtomVersion(function(atomVersion) { + let pythonVersion = await getPythonVersion(); + let gitVersion = await git.getGitVersion(); + let atomVersion = await getAtomVersion(); let versions; if (args.json) { versions = { @@ -130,11 +132,13 @@ const printVersions = function(args, callback) { versions.visualStudio = config.getInstalledVisualStudioFlag(); } console.log(JSON.stringify(versions)); - } else { - if (pythonVersion == null) { pythonVersion = ''; } - if (gitVersion == null) { gitVersion = ''; } - if (atomVersion == null) { atomVersion = ''; } - versions = `\ + return; + } + + pythonVersion ??= ''; + gitVersion ??= ''; + atomVersion ??= ''; + versions = `\ ${'ppm'.red} ${apmVersion.red} ${'npm'.green} ${npmVersion.green} ${'node'.blue} ${nodeVersion.blue} ${process.arch.blue} @@ -143,55 +147,55 @@ ${'python'.yellow} ${pythonVersion.yellow} ${'git'.magenta} ${gitVersion.magenta}\ `; - if (config.isWin32()) { - const visualStudioVersion = config.getInstalledVisualStudioFlag() ?? ""; - versions += `\n${'visual studio'.cyan} ${visualStudioVersion.cyan}`; - } - - console.log(versions); + if (config.isWin32()) { + const visualStudioVersion = config.getInstalledVisualStudioFlag() ?? ""; + versions += `\n${'visual studio'.cyan} ${visualStudioVersion.cyan}`; } - return callback(); - }))); + + console.log(versions); }; -var getAtomVersion = callback => config.getResourcePath(function(resourcePath) { +async function getAtomVersion() { + const resourcePath = await config.getResourcePath(); const unknownVersion = 'unknown'; try { const { version } = require(path.join(resourcePath, "package.json")) ?? unknownVersion; - return callback(version); + return version; } catch (error) { - return callback(unknownVersion); + return unknownVersion; } -}); - -var getPythonVersion = function(callback) { - const npmOptions = { - userconfig: config.getUserConfigPath(), - globalconfig: config.getGlobalConfigPath() - }; - return npm.load(npmOptions, function() { - let python = npm.config.get("python") ?? process.env.PYTHON; - if (config.isWin32() && !python) { - let rootDir = process.env.SystemDrive != null ? process.env.SystemDrive : 'C:\\'; - if (rootDir[rootDir.length - 1] !== '\\') { rootDir += '\\'; } - const pythonExe = path.resolve(rootDir, 'Python27', 'python.exe'); - if (fs.isFileSync(pythonExe)) { python = pythonExe; } - } - - if (python == null) { python = 'python'; } +} - const spawned = spawn(python, ['--version']); - const outputChunks = []; - spawned.stderr.on('data', chunk => outputChunks.push(chunk)); - spawned.stdout.on('data', chunk => outputChunks.push(chunk)); - spawned.on('error', function() {}); - return spawned.on('close', function(code) { - let version, name; - if (code === 0) { - [name, version] = Buffer.concat(outputChunks).toString().split(' '); - version = version?.trim(); +function getPythonVersion() { + return new Promise((resolve, _reject) => { + const npmOptions = { + userconfig: config.getUserConfigPath(), + globalconfig: config.getGlobalConfigPath() + }; + npm.load(npmOptions, () => { + let python = npm.config.get("python") ?? process.env.PYTHON; + if (config.isWin32() && !python) { + let rootDir = process.env.SystemDrive ??= 'C:\\'; + if (rootDir[rootDir.length - 1] !== '\\') { rootDir += '\\'; } + const pythonExe = path.resolve(rootDir, 'Python27', 'python.exe'); + if (fs.isFileSync(pythonExe)) { python = pythonExe; } } - return callback(version); + + python ??= 'python'; + + const spawned = spawn(python, ['--version']); + const outputChunks = []; + spawned.stderr.on('data', chunk => outputChunks.push(chunk)); + spawned.stdout.on('data', chunk => outputChunks.push(chunk)); + spawned.on('error', () => {}); + return spawned.on('close', code => { + let version, name; + if (code === 0) { + [name, version] = Buffer.concat(outputChunks).toString().split(' '); + version = version?.trim(); + } + return resolve(version); + }); }); }); }; @@ -207,7 +211,7 @@ module.exports = { } let callbackCalled = false; - options.callback = function(error) { + const errorHandler = error => { if (callbackCalled) { return; } callbackCalled = true; if (error != null) { @@ -233,14 +237,14 @@ module.exports = { command } = options; if (args.version) { - return printVersions(args, options.callback); + return printVersions(args).then(errorHandler); } else if (args.help) { if ((Command = commands[options.command])) { showHelp(new Command().parseOptions?.(options.command)); } else { showHelp(options); } - return options.callback(); + return errorHandler(); } else if (command) { if (command === 'help') { if ((Command = commands[options.commandArgs])) { @@ -248,15 +252,16 @@ module.exports = { } else { showHelp(options); } - return options.callback(); + return errorHandler(); } else if ((Command = commands[command])) { - return new Command().run(options); + const command = new Command(); + return command.run(options).then(errorHandler); } else { - return options.callback(`Unrecognized command: ${command}`); + return errorHandler(`Unrecognized command: ${command}`); } } else { showHelp(options); - return options.callback(); + return errorHandler(); } } }; diff --git a/src/apm.js b/src/apm.js index dcd4493c..881eeba2 100644 --- a/src/apm.js +++ b/src/apm.js @@ -2,7 +2,6 @@ const child_process = require('child_process'); const fs = require('./fs'); const path = require('path'); const npm = require('npm'); -const semver = require('semver'); let asarPath = null; module.exports = { @@ -26,55 +25,57 @@ module.exports = { return path.join(this.getAtomDirectory(), '.apm'); }, - getResourcePath(callback) { - if (process.env.ATOM_RESOURCE_PATH) { - return process.nextTick(() => callback(process.env.ATOM_RESOURCE_PATH)); - } - - if (asarPath) { // already calculated - return process.nextTick(() => callback(asarPath)); - } - - let apmFolder = path.resolve(__dirname, '..'); - let appFolder = path.dirname(apmFolder); - if ((path.basename(apmFolder) === 'ppm') && (path.basename(appFolder) === 'app')) { - asarPath = `${appFolder}.asar`; - if (fs.existsSync(asarPath)) { - return process.nextTick(() => callback(asarPath)); + getResourcePath() { + return new Promise((resolve, _reject) => { + if (process.env.ATOM_RESOURCE_PATH) { + return void process.nextTick(() => resolve(process.env.ATOM_RESOURCE_PATH)); } - } - - apmFolder = path.resolve(__dirname, '..', '..', '..'); - appFolder = path.dirname(apmFolder); - if ((path.basename(apmFolder) === 'ppm') && (path.basename(appFolder) === 'app')) { - asarPath = `${appFolder}.asar`; - if (fs.existsSync(asarPath)) { - return process.nextTick(() => callback(asarPath)); + + if (asarPath) { // already calculated + return void process.nextTick(() => resolve(asarPath)); } - } - - switch (process.platform) { - case 'darwin': - return child_process.exec('mdfind "kMDItemCFBundleIdentifier == \'dev.pulsar-edit.pulsar\'"', function(error, stdout, stderr) { - let appLocation; - if (stdout == null) { stdout = ''; } - if (!error) { [appLocation] = stdout.split('\n'); } - if (!appLocation) { appLocation = '/Applications/Pulsar.app'; } - asarPath = `${appLocation}/Contents/Resources/app.asar`; - return process.nextTick(() => callback(asarPath)); - }); - case 'linux': - asarPath = '/opt/Pulsar/resources/app.asar'; - return process.nextTick(() => callback(asarPath)); - case 'win32': - asarPath = `/Users/${process.env.USERNAME}/AppData/Local/Programs/Pulsar/resources/app.asar`; - if (!fs.existsSync(asarPath)) { - asarPath = "/Program Files/Pulsar/resources/app.asar"; + + let apmFolder = path.resolve(__dirname, '..'); + let appFolder = path.dirname(apmFolder); + if ((path.basename(apmFolder) === 'ppm') && (path.basename(appFolder) === 'app')) { + asarPath = `${appFolder}.asar`; + if (fs.existsSync(asarPath)) { + return void process.nextTick(() => resolve(asarPath)); } - return process.nextTick(() => callback(asarPath)); - default: - return process.nextTick(() => callback('')); - } + } + + apmFolder = path.resolve(__dirname, '..', '..', '..'); + appFolder = path.dirname(apmFolder); + if ((path.basename(apmFolder) === 'ppm') && (path.basename(appFolder) === 'app')) { + asarPath = `${appFolder}.asar`; + if (fs.existsSync(asarPath)) { + return void process.nextTick(() => resolve(asarPath)); + } + } + + switch (process.platform) { + case 'darwin': + return child_process.exec('mdfind "kMDItemCFBundleIdentifier == \'dev.pulsar-edit.pulsar\'"', (error, stdout, _stderr) => { + let appLocation; + stdout ??= ''; + if (!error) { [appLocation] = stdout.split('\n'); } + appLocation ||= '/Applications/Pulsar.app'; + asarPath = `${appLocation}/Contents/Resources/app.asar`; + return void process.nextTick(() => resolve(asarPath)); + }); + case 'linux': + asarPath = '/opt/Pulsar/resources/app.asar'; + return void process.nextTick(() => resolve(asarPath)); + case 'win32': + asarPath = `/Users/${process.env.USERNAME}/AppData/Local/Programs/Pulsar/resources/app.asar`; + if (!fs.existsSync(asarPath)) { + asarPath = "/Program Files/Pulsar/resources/app.asar"; + } + return void process.nextTick(() => resolve(asarPath)); + default: + return void process.nextTick(() => resolve('')); + } + }); }, getReposDirectory() { @@ -142,27 +143,31 @@ module.exports = { visualStudioIsInstalled(version) { if (version < 2017) { return fs.existsSync(path.join(this.x86ProgramFilesDirectory(), `Microsoft Visual Studio ${version}`, "Common7", "IDE")); - } else { - return [ - path.join(this.x86ProgramFilesDirectory(), "Microsoft Visual Studio", `${version}`, "BuildTools", "Common7", "IDE"), - path.join(this.x86ProgramFilesDirectory(), "Microsoft Visual Studio", `${version}`, "Community", "Common7", "IDE"), - path.join(this.x86ProgramFilesDirectory(), "Microsoft Visual Studio", `${version}`, "Enterprise", "Common7", "IDE"), - path.join(this.x86ProgramFilesDirectory(), "Microsoft Visual Studio", `${version}`, "Professional", "Common7", "IDE"), - path.join(this.x86ProgramFilesDirectory(), "Microsoft Visual Studio", `${version}`, "WDExpress", "Common7", "IDE") - ].find(f => fs.existsSync(f)); } - }, - - loadNpm(callback) { - const npmOptions = { - userconfig: this.getUserConfigPath(), - globalconfig: this.getGlobalConfigPath() - }; - return npm.load(npmOptions, () => callback(null, npm)); - }, - getSetting(key, callback) { - return this.loadNpm(() => callback(npm.config.get(key))); + return [ + "BuildTools", + "Community", + "Enterprise", + "Professional", + "WDExpress" + ].map(releaseType => path.join(this.x86ProgramFilesDirectory(), "Microsoft Visual Studio", `${version}`, releaseType, "Common7", "IDE")) + .find(f => fs.existsSync(f)); + }, + + loadNpm() { + return new Promise((resolve, _reject) => { + const npmOptions = { + userconfig: this.getUserConfigPath(), + globalconfig: this.getGlobalConfigPath() + }; + npm.load(npmOptions, () => resolve(npm)); + }); + }, + + async getSetting(key) { + await this.loadNpm(); + return npm.config.get(key); }, setupApmRcFile() { diff --git a/src/auth.js b/src/auth.js index 215fa92e..270ebb82 100644 --- a/src/auth.js +++ b/src/auth.js @@ -18,34 +18,32 @@ const tokenName = 'pulsar-edit.dev Package API Token'; module.exports = { // Get the package API token from the keychain. - // - // callback - A function to call with an error as the first argument and a - // string token as the second argument. - getToken(callback) { - keytar.findPassword(tokenName) - .then(function(token) { - if (token) { - return callback(null, token); - } else { - return Promise.reject(); - }}).catch(function() { - let token; - if ((token = process.env.ATOM_ACCESS_TOKEN)) { - return callback(null, token); - } else { - return callback(`\ + // returns the token as string or throws an exception + async getToken() { + try { + const token = await keytar.findPassword(tokenName); + if (!token) { + throw 'Missing token in keytar.'; + } + + return token; + } catch { + const token = process.env.ATOM_ACCESS_TOKEN; + if (token) { + return token; + } + + throw `\ No package API token in keychain Run \`ppm login\` or set the \`ATOM_ACCESS_TOKEN\` environment variable.\ -` - ); - } - }); +`; + } }, // Save the given token to the keychain. // // token - A string token to save. - saveToken(token) { - return keytar.setPassword(tokenName, 'pulsar-edit.dev', token); + async saveToken(token) { + await keytar.setPassword(tokenName, 'pulsar-edit.dev', token); } }; diff --git a/src/ci.js b/src/ci.js index 0f8165d4..96305da1 100644 --- a/src/ci.js +++ b/src/ci.js @@ -37,7 +37,7 @@ but cannot be used to install new packages or dependencies.\ return options.boolean('verbose').default('verbose', false).describe('verbose', 'Show verbose debug information'); } - installModules(options, callback) { + installModules(options) { process.stdout.write('Installing locked modules'); if (options.argv.verbose) { process.stdout.write('\n'); @@ -60,23 +60,27 @@ but cannot be used to install new packages or dependencies.\ const installOptions = {env, streaming: options.argv.verbose}; - return this.fork(this.atomNpmPath, installArgs, installOptions, (...args) => { - return this.logCommandResults(callback, ...args); - }); + return new Promise((resolve, reject) => + void this.fork(this.atomNpmPath, installArgs, installOptions, (...args) => + void this.logCommandResults(...args).then(resolve, reject) + ) + ) } - run(options) { - const {callback} = options; + async run(options) { const opts = this.parseOptions(options.commandArgs); const commands = []; - commands.push(callback => { return config.loadNpm((error, npm) => { this.npm = npm; return callback(error); }); }); - commands.push(cb => this.loadInstalledAtomMetadata(cb)); - commands.push(cb => this.installModules(opts, cb)); - const iteratee = (item, next) => item(next); - return async.mapSeries(commands, iteratee, function(err) { - if (err) { return callback(err); } - return callback(null); + commands.push(async () => { + const npm = await config.loadNpm(); + this.npm = npm; }); + commands.push(async () => await this.loadInstalledAtomMetadata()); + commands.push(async () => this.installModules(opts)); + try { + await async.waterfall(commands); + } catch (error) { + return error; // errors as return values atm + } } }; diff --git a/src/clean.js b/src/clean.js index 844fc588..141175a7 100644 --- a/src/clean.js +++ b/src/clean.js @@ -33,10 +33,12 @@ as a dependency in the package.json file.\ return options.alias('h', 'help').describe('help', 'Print this usage message'); } - run(options) { + run(_options) { process.stdout.write("Removing extraneous modules "); - return this.fork(this.atomNpmPath, ['prune'], (...args) => { - return this.logCommandResults(options.callback, ...args); - }); + return new Promise((resolve, reject) => + void this.fork(this.atomNpmPath, ['prune'], (...args) => + void this.logCommandResults(...args).then(resolve, reject) + ) + ); } }; diff --git a/src/command.js b/src/command.js index 4972bc5f..034b186d 100644 --- a/src/command.js +++ b/src/command.js @@ -8,38 +8,34 @@ const git = require('./git'); module.exports = class Command { - constructor() { - this.logCommandResults = this.logCommandResults.bind(this); - this.logCommandResultsIfFail = this.logCommandResultsIfFail.bind(this); - } - spawn(command, args, ...remaining) { - let options; - if (remaining.length >= 2) { options = remaining.shift(); } - const callback = remaining.shift(); + spawn(command, args, optionsOrCallback, callbackOrMissing) { + const [callback, options] = callbackOrMissing == null + ? [optionsOrCallback] + : [callbackOrMissing, optionsOrCallback]; const spawned = child_process.spawn(command, args, options); const errorChunks = []; const outputChunks = []; - spawned.stdout.on('data', function(chunk) { - if ((options != null ? options.streaming : undefined)) { - return process.stdout.write(chunk); + spawned.stdout.on('data', chunk => { + if (options?.streaming) { + process.stdout.write(chunk); } else { - return outputChunks.push(chunk); + outputChunks.push(chunk); } }); - spawned.stderr.on('data', function(chunk) { - if ((options != null ? options.streaming : undefined)) { - return process.stderr.write(chunk); + spawned.stderr.on('data', chunk => { + if (options?.streaming) { + process.stderr.write(chunk); } else { - return errorChunks.push(chunk); + errorChunks.push(chunk); } }); - const onChildExit = function(errorOrExitCode) { + const onChildExit = errorOrExitCode => { spawned.removeListener('error', onChildExit); spawned.removeListener('close', onChildExit); return (typeof callback === 'function' ? callback(errorOrExitCode, Buffer.concat(errorChunks).toString(), Buffer.concat(outputChunks).toString()) : undefined); @@ -52,8 +48,7 @@ class Command { } fork(script, args, ...remaining) { - args.unshift(script); - return this.spawn(process.execPath, args, ...remaining); + return this.spawn(process.execPath, [script, ...args], ...remaining); } packageNamesFromArgv(argv) { @@ -61,47 +56,36 @@ class Command { } sanitizePackageNames(packageNames) { - if (packageNames == null) { packageNames = []; } + packageNames ??= []; packageNames = packageNames.map(packageName => packageName.trim()); return _.compact(_.uniq(packageNames)); } logSuccess() { - if (process.platform === 'win32') { - return process.stdout.write('done\n'.green); - } else { - return process.stdout.write('\u2713\n'.green); - } + process.stdout.write((process.platform === 'win32' ? 'done\n' : '\u2713\n').green); } logFailure() { - if (process.platform === 'win32') { - return process.stdout.write('failed\n'.red); - } else { - return process.stdout.write('\u2717\n'.red); - } + process.stdout.write((process.platform === 'win32' ? 'failed\n' : '\u2717\n').red); } - logCommandResults(callback, code, stderr, stdout) { - if (stderr == null) { stderr = ''; } - if (stdout == null) { stdout = ''; } - if (code === 0) { - this.logSuccess(); - return callback(); - } else { + async logCommandResults(code, stderr, stdout) { + stderr ??= ''; + stdout ??= ''; + if (code !== 0) { this.logFailure(); - return callback(`${stdout}\n${stderr}`.trim()); + throw `${stdout}\n${stderr}`.trim(); } + + this.logSuccess(); } - logCommandResultsIfFail(callback, code, stderr, stdout) { - if (stderr == null) { stderr = ''; } - if (stdout == null) { stdout = ''; } - if (code === 0) { - return callback(); - } else { + async logCommandResultsIfFail(code, stderr, stdout) { + stderr ??= ''; + stdout ??= ''; + if (code !== 0) { this.logFailure(); - return callback(`${stdout}\n${stderr}`.trim()); + throw `${stdout}\n${stderr}`.trim(); } } @@ -114,31 +98,30 @@ class Command { } } - loadInstalledAtomMetadata(callback) { - this.getResourcePath(resourcePath => { - let electronVersion; - try { - let version; - ({ version, electronVersion } = require(path.join(resourcePath, "package.json")) ?? {}); - version = this.normalizeVersion(version); - if (semver.valid(version)) { this.installedAtomVersion = version; } - } catch (error) {} - - this.electronVersion = process.env.ATOM_ELECTRON_VERSION ?? electronVersion; - if (this.electronVersion == null) { - throw new Error('Could not determine Electron version'); - } + async loadInstalledAtomMetadata() { + const resourcePath = await this.getResourcePath(); + let electronVersion; + try { + let version; + ({ version, electronVersion } = require(path.join(resourcePath, "package.json")) ?? {}); + version = this.normalizeVersion(version); + if (semver.valid(version)) { this.installedAtomVersion = version; } + } catch (error) {} - return callback(); - }); + this.electronVersion = process.env.ATOM_ELECTRON_VERSION ?? electronVersion; + if (this.electronVersion == null) { + throw new Error('Could not determine Electron version'); + } } - getResourcePath(callback) { - if (this.resourcePath) { - return process.nextTick(() => callback(this.resourcePath)); - } else { - return config.getResourcePath(resourcePath => { this.resourcePath = resourcePath; return callback(this.resourcePath); }); - } + getResourcePath() { + return new Promise((resolve, _reject) => { + if (this.resourcePath) { + process.nextTick(() => void resolve(this.resourcePath)); + } else { + config.getResourcePath().then(resourcePath => { this.resourcePath = resourcePath; resolve(this.resourcePath); }); + } + }); } addBuildEnvVars(env) { @@ -163,40 +146,36 @@ class Command { updateWindowsEnv(env) { env.USERPROFILE = env.HOME; - return git.addGitToEnv(env); + git.addGitToEnv(env); } addNodeBinToEnv(env) { const nodeBinFolder = path.resolve(__dirname, '..', 'bin'); const pathKey = config.isWin32() ? 'Path' : 'PATH'; - if (env[pathKey]) { - return env[pathKey] = `${nodeBinFolder}${path.delimiter}${env[pathKey]}`; - } else { - return env[pathKey]= nodeBinFolder; - } + env[pathKey] = env[pathKey] ? `${nodeBinFolder}${path.delimiter}${env[pathKey]}` : nodeBinFolder; } addProxyToEnv(env) { const httpProxy = this.npm.config.get('proxy'); if (httpProxy) { - if (env.HTTP_PROXY == null) { env.HTTP_PROXY = httpProxy; } - if (env.http_proxy == null) { env.http_proxy = httpProxy; } + env.HTTP_PROXY ??= httpProxy; + env.http_proxy ??= httpProxy; } const httpsProxy = this.npm.config.get('https-proxy'); if (httpsProxy) { - if (env.HTTPS_PROXY == null) { env.HTTPS_PROXY = httpsProxy; } - if (env.https_proxy == null) { env.https_proxy = httpsProxy; } + env.HTTPS_PROXY ??= httpsProxy; + env.https_proxy ??= httpsProxy; // node-gyp only checks HTTP_PROXY (as of node-gyp@4.0.0) - if (env.HTTP_PROXY == null) { env.HTTP_PROXY = httpsProxy; } - if (env.http_proxy == null) { env.http_proxy = httpsProxy; } + env.HTTP_PROXY ??= httpsProxy; + env.http_proxy ??= httpsProxy; } // node-gyp doesn't currently have an option for this so just set the // environment variable to bypass strict SSL // https://github.com/nodejs/node-gyp/issues/448 const useStrictSsl = this.npm.config.get("strict-ssl") ?? true; - if (!useStrictSsl) { return env.NODE_TLS_REJECT_UNAUTHORIZED = 0; } + if (!useStrictSsl) { env.NODE_TLS_REJECT_UNAUTHORIZED = 0; } } }; diff --git a/src/config.js b/src/config.js index 1b6de278..149e87c1 100644 --- a/src/config.js +++ b/src/config.js @@ -32,7 +32,6 @@ Usage: ppm config set } run(options) { - const {callback} = options; options = this.parseOptions(options.commandArgs); let configArgs = ['--globalconfig', apm.getGlobalConfigPath(), '--userconfig', apm.getUserConfigPath(), 'config']; @@ -41,16 +40,17 @@ Usage: ppm config set const env = _.extend({}, process.env, {HOME: this.atomNodeDirectory, RUSTUP_HOME: apm.getRustupHomeDirPath()}); const configOptions = {env}; - return this.fork(this.atomNpmPath, configArgs, configOptions, function(code, stderr, stdout) { - if (stderr == null) { stderr = ''; } - if (stdout == null) { stdout = ''; } - if (code === 0) { - if (stdout) { process.stdout.write(stdout); } - return callback(); - } else { + return new Promise((resolve, _reject) => + void this.fork(this.atomNpmPath, configArgs, configOptions, (code, stderr, stdout) => { + stderr ??= ''; + stdout ??= ''; + if (code === 0) { + if (stdout) { process.stdout.write(stdout); } + return void resolve(); + } if (stderr) { process.stdout.write(stderr); } - return callback(new Error(`npm config failed: ${code}`)); - } - }); + return void resolve(new Error(`npm config failed: ${code}`)); + }) + ); } } diff --git a/src/dedupe.js b/src/dedupe.js index 8c8c3c3e..dd83be5e 100644 --- a/src/dedupe.js +++ b/src/dedupe.js @@ -35,11 +35,13 @@ This command is experimental.\ return options.alias('h', 'help').describe('help', 'Print this usage message'); } - dedupeModules(options, callback) { + dedupeModules(options) { process.stdout.write('Deduping modules '); - this.forkDedupeCommand(options, (...args) => { - this.logCommandResults(callback, ...args); + return new Promise((resolve, reject) => { + this.forkDedupeCommand(options, (...args) => { + this.logCommandResults(...args).then(resolve, reject); + }); }); } @@ -67,16 +69,20 @@ This command is experimental.\ return fs.makeTreeSync(this.atomNodeDirectory); } - run(options) { - const {callback, cwd} = options; + async run(options) { + const {cwd} = options; options = this.parseOptions(options.commandArgs); options.cwd = cwd; this.createAtomDirectories(); const commands = []; - commands.push(callback => this.loadInstalledAtomMetadata(callback)); - commands.push(callback => this.dedupeModules(options, callback)); - return async.waterfall(commands, callback); + commands.push(async () => await this.loadInstalledAtomMetadata()); + commands.push(async () => await this.dedupeModules(options)); + try { + await async.waterfall(commands); + } catch (error) { + return error; //errors as return values atm + } } } diff --git a/src/develop.js b/src/develop.js index 58a46804..5d8b20aa 100644 --- a/src/develop.js +++ b/src/develop.js @@ -44,69 +44,68 @@ cmd-shift-o to run the package out of the newly cloned repository.\ return options.alias('h', 'help').describe('help', 'Print this usage message'); } - getRepositoryUrl(packageName, callback) { - const requestSettings = { - url: `${config.getAtomPackagesUrl()}/${packageName}`, - json: true - }; - return request.get(requestSettings, function(error, response, body) { - if (body == null) { body = {}; } - if (error != null) { - return callback(`Request for package information failed: ${error.message}`); - } else if (response.statusCode === 200) { - let repositoryUrl; - if ((repositoryUrl = body.repository.url)) { - return callback(null, repositoryUrl); - } else { - return callback(`No repository URL found for package: ${packageName}`); + getRepositoryUrl(packageName) { + return new Promise((resolve, reject) => { + const requestSettings = { + url: `${config.getAtomPackagesUrl()}/${packageName}`, + json: true + }; + return request.get(requestSettings, (error, response, body) => { + body ??= {}; + if (error != null) { + return void reject(`Request for package information failed: ${error.message}`); + } + + if (response.statusCode === 200) { + const repositoryUrl = body.repository.url; + if (repositoryUrl) { + return void resolve(repositoryUrl); + } + + return void reject(`No repository URL found for package: ${packageName}`); } - } else { + const message = request.getErrorMessage(body, error); - return callback(`Request for package information failed: ${message}`); - } + return void reject(`Request for package information failed: ${message}`); + }); }); } - cloneRepository(repoUrl, packageDirectory, options, callback) { - if (callback == null) { callback = function() {}; } - return config.getSetting('git', command => { - if (command == null) { command = 'git'; } - const args = ['clone', '--recursive', repoUrl, packageDirectory]; - if (!options.argv.json) { process.stdout.write(`Cloning ${repoUrl} `); } - git.addGitToEnv(process.env); - return this.spawn(command, args, (...args) => { + async cloneRepository(repoUrl, packageDirectory, options) { + const command = await config.getSetting('git') ?? 'git'; + const args = ['clone', '--recursive', repoUrl, packageDirectory]; + if (!options.argv.json) { process.stdout.write(`Cloning ${repoUrl} `); } + git.addGitToEnv(process.env); + return new Promise((resolve, reject) => { + this.spawn(command, args, (...args) => { if (options.argv.json) { - return this.logCommandResultsIfFail(callback, ...args); - } else { - return this.logCommandResults(callback, ...args); + return void this.logCommandResultsIfFail(...args).then(resolve, reject); } + + this.logCommandResults(...args).then(resolve, reject); }); }); } - installDependencies(packageDirectory, options, callback) { - if (callback == null) { callback = function() {}; } - process.chdir(packageDirectory); - const installOptions = _.clone(options); - installOptions.callback = callback; - - return new Install().run(installOptions); + installDependencies(packageDirectory, options) { + process.chdir(packageDirectory); + const installOptions = _.clone(options); + + return new Install().run(installOptions); } - linkPackage(packageDirectory, options, callback) { + linkPackage(packageDirectory, options) { const linkOptions = _.clone(options); - if (callback) { - linkOptions.callback = callback; - } + linkOptions.commandArgs = [packageDirectory, '--dev']; return new Link().run(linkOptions); } - run(options) { + async run(options) { const packageName = options.commandArgs.shift(); if (!((packageName != null ? packageName.length : undefined) > 0)) { - return options.callback("Missing required package name"); + return Promise.resolve("Missing required package name"); } let packageDirectory = options.commandArgs.shift() ?? path.join(config.getReposDirectory(), packageName); @@ -114,21 +113,18 @@ cmd-shift-o to run the package out of the newly cloned repository.\ if (fs.existsSync(packageDirectory)) { return this.linkPackage(packageDirectory, options); - } else { - return this.getRepositoryUrl(packageName, (error, repoUrl) => { - if (error != null) { - return options.callback(error); - } else { - const tasks = []; - tasks.push(callback => this.cloneRepository(repoUrl, packageDirectory, options, callback)); - - tasks.push(callback => this.installDependencies(packageDirectory, options, callback)); + } - tasks.push(callback => this.linkPackage(packageDirectory, options, callback)); + try { + const repoUrl = await this.getRepositoryUrl(packageName); + const tasks = []; + tasks.push(async () => await this.cloneRepository(repoUrl, packageDirectory, options)); + tasks.push(async () => await this.installDependencies(packageDirectory, options)); + tasks.push(async () => await this.linkPackage(packageDirectory, options)); - return async.waterfall(tasks, options.callback); - } - }); + await async.waterfall(tasks); + } catch (error) { + return error; //errors as return values atm } } } diff --git a/src/disable.js b/src/disable.js index fc04341d..428313cd 100644 --- a/src/disable.js +++ b/src/disable.js @@ -24,7 +24,7 @@ Disables the named package(s).\ return options.alias('h', 'help').describe('help', 'Print this usage message'); } - getInstalledPackages(callback) { + async getInstalledPackages() { const options = { argv: { theme: false, @@ -33,63 +33,59 @@ Disables the named package(s).\ }; const lister = new List(); - return lister.listBundledPackages(options, (error, core_packages) => lister.listDevPackages(options, (error, dev_packages) => lister.listUserPackages(options, (error, user_packages) => callback(null, core_packages.concat(dev_packages, user_packages))))); + const corePackages = await lister.listBundledPackages(options); + const devPackages = lister.listDevPackages(options); + const userPackages = lister.listUserPackages(options); + return corePackages.concat(devPackages, userPackages); } - run(options) { - let settings; - const {callback} = options; - options = this.parseOptions(options.commandArgs); + async run(options) { + options = this.parseOptions(options.commandArgs); - let packageNames = this.packageNamesFromArgv(options.argv); + let packageNames = this.packageNamesFromArgv(options.argv); - const configFilePath = CSON.resolve(path.join(config.getAtomDirectory(), 'config')); - if (!configFilePath) { - callback("Could not find config.cson. Run Atom first?"); - return; - } - - try { - settings = CSON.readFileSync(configFilePath); - } catch (error) { - callback(`Failed to load \`${configFilePath}\`: ${error.message}`); - return; - } - - return this.getInstalledPackages((error, installedPackages) => { - if (error) { return callback(error); } - - const installedPackageNames = (Array.from(installedPackages).map((pkg) => pkg.name)); - - // uninstalledPackages = (name for name in packageNames when !installedPackageNames[name]) - const uninstalledPackageNames = _.difference(packageNames, installedPackageNames); - if (uninstalledPackageNames.length > 0) { - console.log(`Not Installed:\n ${uninstalledPackageNames.join('\n ')}`); + const configFilePath = CSON.resolve(path.join(config.getAtomDirectory(), 'config')); + if (!configFilePath) { + return 'Could not find config.cson. Run Pulsar first?'; //errors as return values atm } - // only installed packages can be disabled - packageNames = _.difference(packageNames, uninstalledPackageNames); - - if (packageNames.length === 0) { - callback("Please specify a package to disable"); - return; + let settings; + try { + settings = CSON.readFileSync(configFilePath); + } catch (error) { + return `Failed to load \`${configFilePath}\`: ${error.message}`; //errors as return values atm } - const keyPath = '*.core.disabledPackages'; - const disabledPackages = _.valueForKeyPath(settings, keyPath) ?? []; - const result = _.union(disabledPackages, packageNames); - _.setValueForKeyPath(settings, keyPath, result); - try { - CSON.writeFileSync(configFilePath, settings); + const installedPackages = await this.getInstalledPackages(); + const installedPackageNames = Array.from(installedPackages).map((pkg) => pkg.name); + const uninstalledPackageNames = _.difference(packageNames, installedPackageNames); + if (uninstalledPackageNames.length > 0) { + console.log(`Not Installed:\n ${uninstalledPackageNames.join('\n ')}`); + } + + // only installed packages can be disabled + packageNames = _.difference(packageNames, uninstalledPackageNames); + + if (packageNames.length === 0) { + return "Please specify a package to disable"; //errors as return values atm + } + + const keyPath = '*.core.disabledPackages'; + const disabledPackages = _.valueForKeyPath(settings, keyPath) ?? []; + const result = _.union(disabledPackages, packageNames); + _.setValueForKeyPath(settings, keyPath, result); + + try { + CSON.writeFileSync(configFilePath, settings); + } catch (error) { + return `Failed to save \`${configFilePath}\`: ${error.message}`; //errors as return values atm + } + + console.log(`Disabled:\n ${packageNames.join('\n ')}`); + this.logSuccess(); } catch (error) { - callback(`Failed to save \`${configFilePath}\`: ${error.message}`); - return; + return error; //errors as return values atm } - - console.log(`Disabled:\n ${packageNames.join('\n ')}`); - this.logSuccess(); - return callback(); - }); } } diff --git a/src/docs.js b/src/docs.js index 3d42b900..34dcc794 100644 --- a/src/docs.js +++ b/src/docs.js @@ -26,30 +26,30 @@ Open a package's homepage in the default browser.\ return open(repositoryUrl); } - run(options) { - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); const [packageName] = options.argv._; if (!packageName) { - callback("Missing required package name"); - return; + return "Missing required package name"; //error as return value } - this.getPackage(packageName, options, (error, pack) => { - let repository; - if (error != null) { return callback(error); } - - if (repository = this.getRepository(pack)) { - if (options.argv.print) { - console.log(repository); - } else { - this.openRepositoryUrl(repository); - } - return callback(); - } else { - return callback(`Package \"${packageName}\" does not contain a repository URL`); - } - }); + let pack; + try { + pack = await this.getPackage(packageName, options); + } catch (error) { + return error; //error as return value + } + + const repository = this.getRepository(pack); + if (!repository) { + return `Package \"${packageName}\" does not contain a repository URL`; //error as return value + } + + if (options.argv.print) { + console.log(repository); + } else { + this.openRepositoryUrl(repository); + } } } diff --git a/src/enable.js b/src/enable.js index 3471f991..57a38c75 100644 --- a/src/enable.js +++ b/src/enable.js @@ -23,23 +23,20 @@ Enables the named package(s).\ return options.alias('h', 'help').describe('help', 'Print this usage message'); } - run(options) { - let error, settings; - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); let packageNames = this.packageNamesFromArgv(options.argv); const configFilePath = CSON.resolve(path.join(config.getAtomDirectory(), 'config')); if (!configFilePath) { - callback("Could not find config.cson. Run Atom first?"); - return; + return "Could not find config.cson. Run Pulsar first?"; //errors as retval atm } + let settings; try { settings = CSON.readFileSync(configFilePath); } catch (error) { - callback(`Failed to load \`${configFilePath}\`: ${error.message}`); - return; + return `Failed to load \`${configFilePath}\`: ${error.message}`; //errors as retval atm } const keyPath = '*.core.disabledPackages'; @@ -54,8 +51,7 @@ Enables the named package(s).\ packageNames = _.difference(packageNames, errorPackages); if (packageNames.length === 0) { - callback("Please specify a package to enable"); - return; + return "Please specify a package to enable"; //errors as retval atm } const result = _.difference(disabledPackages, packageNames); @@ -64,12 +60,10 @@ Enables the named package(s).\ try { CSON.writeFileSync(configFilePath, settings); } catch (error) { - callback(`Failed to save \`${configFilePath}\`: ${error.message}`); - return; + return `Failed to save \`${configFilePath}\`: ${error.message}`; //errors as retval atm } console.log(`Enabled:\n ${packageNames.join('\n ')}`); this.logSuccess(); - return callback(); } } diff --git a/src/featured.js b/src/featured.js index 11b12a0b..27c1a578 100644 --- a/src/featured.js +++ b/src/featured.js @@ -28,8 +28,7 @@ List the Pulsar packages and themes that are currently featured.\ return options.boolean('json').describe('json', 'Output featured packages as JSON array'); } - getFeaturedPackagesByType(atomVersion, packageType, callback) { - if (_.isFunction(atomVersion)) { [callback, atomVersion] = [atomVersion, null]; } + getFeaturedPackagesByType(atomVersion, packageType) { const requestSettings = { url: `${config.getAtomApiUrl()}/${packageType}/featured`, @@ -37,68 +36,57 @@ List the Pulsar packages and themes that are currently featured.\ }; if (atomVersion) { requestSettings.qs = {engine: atomVersion}; } - return request.get(requestSettings, function(error, response, body) { - if (body == null) { body = []; } + return new Promise((resolve, reject) => void request.get(requestSettings, function(error, response, body) { + body ??= []; if (error != null) { - return callback(error); - } else if (response.statusCode === 200) { + return void reject(error); + } + if (response.statusCode === 200) { let packages = body.filter(pack => (pack != null ? pack.releases : undefined) != null); packages = packages.map(({readme, metadata, downloads, stargazers_count}) => _.extend({}, metadata, {readme, downloads, stargazers_count})); packages = _.sortBy(packages, 'name'); - return callback(null, packages); - } else { - const message = request.getErrorMessage(body, error); - return callback(`Requesting packages failed: ${message}`); + return void resolve(packages); } - }); + + const message = request.getErrorMessage(body, error); + reject(`Requesting packages failed: ${message}`); + })); } - getAllFeaturedPackages(atomVersion, callback) { - this.getFeaturedPackagesByType(atomVersion, 'packages', (error, packages) => { - if (error != null) { return callback(error); } - - this.getFeaturedPackagesByType(atomVersion, 'themes', function(error, themes) { - if (error != null) { return callback(error); } - return callback(null, packages.concat(themes)); - }); - }); + async getAllFeaturedPackages(atomVersion) { + const packages = await this.getFeaturedPackagesByType(atomVersion, 'packages'); + const themes = await this.getFeaturedPackagesByType(atomVersion, 'themes'); + return packages.concat(themes); } - run(options) { - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); - const listCallback = function(error, packages) { - if (error != null) { return callback(error); } - + try { + const packages = options.argv.themes ? await this.getFeaturedPackagesByType(options.argv.compatible, 'themes') : await this.getAllFeaturedPackages(options.argv.compatible); if (options.argv.json) { console.log(JSON.stringify(packages)); + return; + } + if (options.argv.themes) { + console.log(`${'Featured Pulsar Themes'.cyan} (${packages.length})`); } else { - if (options.argv.themes) { - console.log(`${'Featured Pulsar Themes'.cyan} (${packages.length})`); - } else { - console.log(`${'Featured Pulsar Packages'.cyan} (${packages.length})`); - } - - tree(packages, function({name, version, description, downloads, stargazers_count}) { - let label = name.yellow; - if (description) { label += ` ${description.replace(/\s+/g, ' ')}`; } - if ((downloads >= 0) && (stargazers_count >= 0)) { label += ` (${_.pluralize(downloads, 'download')}, ${_.pluralize(stargazers_count, 'star')})`.grey; } - return label; - }); - - console.log(); - console.log(`Use \`ppm install\` to install them or visit ${'https://web.pulsar-edit.dev/'.underline} to read more about them.`); - console.log(); + console.log(`${'Featured Pulsar Packages'.cyan} (${packages.length})`); } - return callback(); - }; + tree(packages, ({name, version, description, downloads, stargazers_count}) => { + let label = name.yellow; + if (description) { label += ` ${description.replace(/\s+/g, ' ')}`; } + if ((downloads >= 0) && (stargazers_count >= 0)) { label += ` (${_.pluralize(downloads, 'download')}, ${_.pluralize(stargazers_count, 'star')})`.grey; } + return label; + }); - if (options.argv.themes) { - return this.getFeaturedPackagesByType(options.argv.compatible, 'themes', listCallback); - } else { - return this.getAllFeaturedPackages(options.argv.compatible, listCallback); + console.log(); + console.log(`Use \`ppm install\` to install them or visit ${'https://web.pulsar-edit.dev/'.underline} to read more about them.`); + console.log(); + } catch (error) { + return error; //Need to provide all data as the value of the Promise at the moment } + } } diff --git a/src/fs.js b/src/fs.js index cacd5f34..fdb29f30 100644 --- a/src/fs.js +++ b/src/fs.js @@ -23,34 +23,36 @@ const fsAdditions = { return wrench.readdirSyncRecursive(directoryPath); }, - cp(sourcePath, destinationPath, callback) { - return rm(destinationPath, function(error) { - if (error != null) { - return callback(error); - } else { - return ncp(sourcePath, destinationPath, callback); - } + cp(sourcePath, destinationPath) { + return new Promise((resolve, reject) => { + rm(destinationPath, error => { + if (error != null) { + return reject(error); + } + ncp(sourcePath, destinationPath, (error, value) => void (error != null ? reject(error) : resolve(value))); + }); }); }, - mv(sourcePath, destinationPath, callback) { - return rm(destinationPath, function(error) { - if (error != null) { - return callback(error); - } else { + mv(sourcePath, destinationPath) { + return new Promise((resolve, reject) => { + rm(destinationPath, error => { + if (error != null) { + return reject(error); + } wrench.mkdirSyncRecursive(path.dirname(destinationPath), 0o755); - return fs.rename(sourcePath, destinationPath, callback); - } + fs.rename(sourcePath, destinationPath, (error, value) => void (error != null ? reject(error) : resolve(value))); + }); }); } }; module.exports = new Proxy({}, { - get(target, key) { + get(_target, key) { return fsAdditions[key] || fs[key]; }, - set(target, key, value) { + set(_target, key, value) { return fsAdditions[key] = value; } }); diff --git a/src/git.js b/src/git.js index 69023421..c938e050 100644 --- a/src/git.js +++ b/src/git.js @@ -6,13 +6,13 @@ const npm = require('npm'); const config = require('./apm'); const fs = require('./fs'); -const addPortableGitToEnv = function(env) { - let children; +function addPortableGitToEnv(env) { const localAppData = env.LOCALAPPDATA; if (!localAppData) { return; } const githubPath = path.join(localAppData, 'GitHub'); + let children; try { children = fs.readdirSync(githubPath); } catch (error) { @@ -24,17 +24,16 @@ const addPortableGitToEnv = function(env) { const cmdPath = path.join(githubPath, child, 'cmd'); const binPath = path.join(githubPath, child, 'bin'); if (env.Path) { - env.Path += `${path.delimiter}${cmdPath}${path.delimiter}${binPath}`; - } else { - env.Path = `${cmdPath}${path.delimiter}${binPath}`; + env.Path += path.delimiter; } + env.Path += `${cmdPath}${path.delimiter}${binPath}`; break; } } -}; +} -const addGitBashToEnv = function(env) { +function addGitBashToEnv(env) { let gitPath; if (env.ProgramFiles) { gitPath = path.join(env.ProgramFiles, 'Git'); @@ -51,40 +50,40 @@ const addGitBashToEnv = function(env) { const cmdPath = path.join(gitPath, 'cmd'); const binPath = path.join(gitPath, 'bin'); if (env.Path) { - return env.Path += `${path.delimiter}${cmdPath}${path.delimiter}${binPath}`; - } else { - return env.Path = `${cmdPath}${path.delimiter}${binPath}`; + env.Path += path.delimiter; } -}; + env.Path += `${cmdPath}${path.delimiter}${binPath}`; +} -exports.addGitToEnv = function(env) { +exports.addGitToEnv = env => { if (process.platform !== 'win32') { return; } addPortableGitToEnv(env); addGitBashToEnv(env); }; -exports.getGitVersion = function(callback) { +exports.getGitVersion = () => { const npmOptions = { userconfig: config.getUserConfigPath(), globalconfig: config.getGlobalConfigPath() }; - npm.load(npmOptions, function() { - let left; - const git = (left = npm.config.get('git')) != null ? left : 'git'; - exports.addGitToEnv(process.env); - const spawned = spawn(git, ['--version']); - const outputChunks = []; - spawned.stderr.on('data', chunk => outputChunks.push(chunk)); - spawned.stdout.on('data', chunk => outputChunks.push(chunk)); - spawned.on('error', function() {}); - return spawned.on('close', function(code) { - let version; - if (code === 0) { - let gitName, versionName; - [gitName, versionName, version] = Buffer.concat(outputChunks).toString().split(' '); - version = version != null ? version.trim() : undefined; - } - return callback(version); + return new Promise((resolve, _reject) => { + npm.load(npmOptions, () => { + const git = npm.config.get('git') ?? 'git'; + exports.addGitToEnv(process.env); + const spawned = spawn(git, ['--version']); + const outputChunks = []; + spawned.stderr.on('data', chunk => void outputChunks.push(chunk)); + spawned.stdout.on('data', chunk => void outputChunks.push(chunk)); + spawned.on('error', () => {}); + spawned.on('close', code => { + let version; + if (code === 0) { + let _gitName, _versionName; + [_gitName, _versionName, version] = Buffer.concat(outputChunks).toString().split(' '); + version = version?.trim(); + } + resolve(version); + }); }); }); }; diff --git a/src/init.js b/src/init.js index 7f0d13bc..ed078517 100644 --- a/src/init.js +++ b/src/init.js @@ -46,122 +46,106 @@ on the option selected.\ return options.string('template').describe('template', 'Path to the package or theme template'); } - run(options) { + async run(options) { let templatePath; - const {callback} = options; options = this.parseOptions(options.commandArgs); - if ((options.argv.package != null ? options.argv.package.length : undefined) > 0) { + if (options.argv.package?.length > 0) { if (options.argv.convert) { - return this.convertPackage(options.argv.convert, options.argv.package, callback); - } else { - const packagePath = path.resolve(options.argv.package); - const syntax = options.argv.syntax || this.supportedSyntaxes[0]; - if (!Array.from(this.supportedSyntaxes).includes(syntax)) { - return callback(`You must specify one of ${this.supportedSyntaxes.join(', ')} after the --syntax argument`); - } - templatePath = this.getTemplatePath(options.argv, `package-${syntax}`); - this.generateFromTemplate(packagePath, templatePath); - return callback(); + return this.convertPackage(options.argv.convert, options.argv.package).catch(error => error); // rewire the error as a value for te time being } - } else if ((options.argv.theme != null ? options.argv.theme.length : undefined) > 0) { + const packagePath = path.resolve(options.argv.package); + const syntax = options.argv.syntax || this.supportedSyntaxes[0]; + if (!Array.from(this.supportedSyntaxes).includes(syntax)) { + return `You must specify one of ${this.supportedSyntaxes.join(', ')} after the --syntax argument`; // expose the error value as a value for now + } + templatePath = this.getTemplatePath(options.argv, `package-${syntax}`); + this.generateFromTemplate(packagePath, templatePath); + return; + } + if (options.argv.theme?.length > 0) { if (options.argv.convert) { - return this.convertTheme(options.argv.convert, options.argv.theme, callback); - } else { - const themePath = path.resolve(options.argv.theme); - templatePath = this.getTemplatePath(options.argv, 'theme'); - this.generateFromTemplate(themePath, templatePath); - return callback(); + return this.convertTheme(options.argv.convert, options.argv.theme).catch(error => error); // rewiring errors... } - } else if ((options.argv.language != null ? options.argv.language.length : undefined) > 0) { + const themePath = path.resolve(options.argv.theme); + templatePath = this.getTemplatePath(options.argv, 'theme'); + this.generateFromTemplate(themePath, templatePath); + return; + } + if (options.argv.language?.length > 0) { let languagePath = path.resolve(options.argv.language); const languageName = path.basename(languagePath).replace(/^language-/, ''); languagePath = path.join(path.dirname(languagePath), `language-${languageName}`); templatePath = this.getTemplatePath(options.argv, 'language'); this.generateFromTemplate(languagePath, templatePath, languageName); - return callback(); - } else if (options.argv.package != null) { - return callback('You must specify a path after the --package argument'); - } else if (options.argv.theme != null) { - return callback('You must specify a path after the --theme argument'); - } else { - return callback('You must specify either --package, --theme or --language to `ppm init`'); + return; } + if (options.argv.package != null) { + return 'You must specify a path after the --package argument'; // errors as values... + } + if (options.argv.theme != null) { + return 'You must specify a path after the --theme argument'; // errors as values... + } + return 'You must specify either --package, --theme or --language to `ppm init`'; // errors as values... } - convertPackage(sourcePath, destinationPath, callback) { + async convertPackage(sourcePath, destinationPath) { if (!destinationPath) { - callback("Specify directory to create package in using --package"); - return; + throw "Specify directory to create package in using --package"; } const PackageConverter = require('./package-converter'); const converter = new PackageConverter(sourcePath, destinationPath); - return converter.convert(error => { - if (error != null) { - return callback(error); - } else { - destinationPath = path.resolve(destinationPath); - const templatePath = path.resolve(__dirname, '..', 'templates', 'bundle'); - this.generateFromTemplate(destinationPath, templatePath); - return callback(); - } - }); + await converter.convert(); + destinationPath = path.resolve(destinationPath); + const templatePath = path.resolve(__dirname, '..', 'templates', 'bundle'); + this.generateFromTemplate(destinationPath, templatePath); } - convertTheme(sourcePath, destinationPath, callback) { + async convertTheme(sourcePath, destinationPath) { if (!destinationPath) { - callback("Specify directory to create theme in using --theme"); - return; + throw "Specify directory to create theme in using --theme"; } const ThemeConverter = require('./theme-converter'); const converter = new ThemeConverter(sourcePath, destinationPath); - converter.convert(error => { - if (error != null) { - return callback(error); - } else { - destinationPath = path.resolve(destinationPath); - const templatePath = path.resolve(__dirname, '..', 'templates', 'theme'); - this.generateFromTemplate(destinationPath, templatePath); - fs.removeSync(path.join(destinationPath, 'styles', 'colors.less')); - fs.removeSync(path.join(destinationPath, 'LICENSE.md')); - return callback(); - } - }); + await converter.convert(); + destinationPath = path.resolve(destinationPath); + const templatePath = path.resolve(__dirname, '..', 'templates', 'theme'); + this.generateFromTemplate(destinationPath, templatePath); + fs.removeSync(path.join(destinationPath, 'styles', 'colors.less')); + fs.removeSync(path.join(destinationPath, 'LICENSE.md')); } generateFromTemplate(packagePath, templatePath, packageName) { - if (packageName == null) { packageName = path.basename(packagePath); } + packageName ??= path.basename(packagePath); const packageAuthor = process.env.GITHUB_USER || 'atom'; fs.makeTreeSync(packagePath); - return (() => { - const result = []; - for (let childPath of Array.from(fs.listRecursive(templatePath))) { - const templateChildPath = path.resolve(templatePath, childPath); - let relativePath = templateChildPath.replace(templatePath, ""); - relativePath = relativePath.replace(/^\//, ''); - relativePath = relativePath.replace(/\.template$/, ''); - relativePath = this.replacePackageNamePlaceholders(relativePath, packageName); - - const sourcePath = path.join(packagePath, relativePath); - if (fs.existsSync(sourcePath)) { continue; } - if (fs.isDirectorySync(templateChildPath)) { - result.push(fs.makeTreeSync(sourcePath)); - } else if (fs.isFileSync(templateChildPath)) { - fs.makeTreeSync(path.dirname(sourcePath)); - let contents = fs.readFileSync(templateChildPath).toString(); - contents = this.replacePackageNamePlaceholders(contents, packageName); - contents = this.replacePackageAuthorPlaceholders(contents, packageAuthor); - contents = this.replaceCurrentYearPlaceholders(contents); - result.push(fs.writeFileSync(sourcePath, contents)); - } else { - result.push(undefined); - } + const result = []; + for (let childPath of Array.from(fs.listRecursive(templatePath))) { + const templateChildPath = path.resolve(templatePath, childPath); + let relativePath = templateChildPath.replace(templatePath, ""); + relativePath = relativePath.replace(/^\//, ''); + relativePath = relativePath.replace(/\.template$/, ''); + relativePath = this.replacePackageNamePlaceholders(relativePath, packageName); + + const sourcePath = path.join(packagePath, relativePath); + if (fs.existsSync(sourcePath)) { continue; } + if (fs.isDirectorySync(templateChildPath)) { + result.push(fs.makeTreeSync(sourcePath)); + } else if (fs.isFileSync(templateChildPath)) { + fs.makeTreeSync(path.dirname(sourcePath)); + let contents = fs.readFileSync(templateChildPath).toString(); + contents = this.replacePackageNamePlaceholders(contents, packageName); + contents = this.replacePackageAuthorPlaceholders(contents, packageAuthor); + contents = this.replaceCurrentYearPlaceholders(contents); + result.push(fs.writeFileSync(sourcePath, contents)); + } else { + result.push(undefined); } - return result; - })(); + } + return result; } replacePackageAuthorPlaceholders(string, packageAuthor) { @@ -170,18 +154,19 @@ on the option selected.\ replacePackageNamePlaceholders(string, packageName) { const placeholderRegex = /__(?:(package-name)|([pP]ackageName)|(package_name))__/g; - return string = string.replace(placeholderRegex, (match, dash, camel, underscore) => { + return string = string.replace(placeholderRegex, (_match, dash, camel, underscore) => { if (dash) { return this.dasherize(packageName); - } else if (camel) { + } + if (camel) { if (/[a-z]/.test(camel[0])) { packageName = packageName[0].toLowerCase() + packageName.slice(1); } else if (/[A-Z]/.test(camel[0])) { packageName = packageName[0].toUpperCase() + packageName.slice(1); } return this.camelize(packageName); - - } else if (underscore) { + } + if (underscore) { return this.underscore(packageName); } }); @@ -192,22 +177,12 @@ on the option selected.\ } getTemplatePath(argv, templateType) { - if (argv.template != null) { - return path.resolve(argv.template); - } else { - return path.resolve(__dirname, '..', 'templates', templateType); - } + return argv.template != null ? path.resolve(argv.template) : path.resolve(__dirname, '..', 'templates', templateType); } dasherize(string) { string = string[0].toLowerCase() + string.slice(1); - return string.replace(/([A-Z])|(_)/g, function(m, letter, underscore) { - if (letter) { - return "-" + letter.toLowerCase(); - } else { - return "-"; - } - }); + return string.replace(/([A-Z])|(_)/g, (_m, letter, _underscore) => letter ? "-" + letter.toLowerCase() : "-"); } camelize(string) { @@ -216,12 +191,6 @@ on the option selected.\ underscore(string) { string = string[0].toLowerCase() + string.slice(1); - return string.replace(/([A-Z])|(-)/g, function(m, letter, dash) { - if (letter) { - return "_" + letter.toLowerCase(); - } else { - return "_"; - } - }); + return string.replace(/([A-Z])|(-)/g, (_m, letter, _dash) => letter ? "_" + letter.toLowerCase() : "_"); } } diff --git a/src/install.js b/src/install.js index 4653a8e5..2cf68a43 100644 --- a/src/install.js +++ b/src/install.js @@ -67,9 +67,9 @@ package names to install with optional versions using the return options.boolean('production').describe('production', 'Do not install dev dependencies'); } - installModule(options, pack, moduleURI, callback) { + installModule(options, pack, moduleURI) { let installDirectory, nodeModulesDirectory; - const installGlobally = options.installGlobally != null ? options.installGlobally : true; + const installGlobally = options.installGlobally ?? true; const installArgs = ['--globalconfig', config.getGlobalConfigPath(), '--userconfig', config.getUserConfigPath(), 'install']; installArgs.push(moduleURI); @@ -95,44 +95,44 @@ package names to install with optional versions using the installOptions.cwd = installDirectory; } - return this.fork(this.atomNpmPath, installArgs, installOptions, (code, stderr, stdout) => { - if (stderr == null) { stderr = ''; } - if (stdout == null) { stdout = ''; } - if (code === 0) { - let child, destination; - if (installGlobally) { - const commands = []; - const children = fs.readdirSync(nodeModulesDirectory) - .filter(dir => dir !== ".bin"); - assert.equal(children.length, 1, "Expected there to only be one child in node_modules"); - child = children[0]; - const source = path.join(nodeModulesDirectory, child); - destination = path.join(this.atomPackagesDirectory, child); - commands.push(next => fs.cp(source, destination, next)); - commands.push(next => this.buildModuleCache(pack.name, next)); - commands.push(next => this.warmCompileCache(pack.name, next)); - - return async.waterfall(commands, error => { - if (error != null) { - this.logFailure(); - } else { - if (!options.argv.json) { this.logSuccess(); } - } - return callback(error, {name: child, installPath: destination}); - }); - } else { - return callback(null, {name: child, installPath: destination}); + return new Promise((resolve, reject) => { + this.fork(this.atomNpmPath, installArgs, installOptions, (code, stderr, stdout) => { + stderr ??= ''; + stdout ??= ''; + if (code !== 0) { + if (installGlobally) { + fs.removeSync(installDirectory); + this.logFailure(); + } + + let error = `${stdout}\n${stderr}`; + if (error.indexOf('code ENOGIT') !== -1) { error = this.getGitErrorMessage(pack); } + return void reject(error); } - } else { - if (installGlobally) { - fs.removeSync(installDirectory); - this.logFailure(); + + if (!installGlobally) { + return void resolve({name: undefined, installPath: undefined}); } - let error = `${stdout}\n${stderr}`; - if (error.indexOf('code ENOGIT') !== -1) { error = this.getGitErrorMessage(pack); } - return callback(error); - } + const commands = []; + const children = fs.readdirSync(nodeModulesDirectory) + .filter(dir => dir !== ".bin"); + assert.equal(children.length, 1, "Expected there to only be one child in node_modules"); + const child = children[0]; + const source = path.join(nodeModulesDirectory, child); + const destination = path.join(this.atomPackagesDirectory, child); + commands.push(async () => await fs.cp(source, destination)); + commands.push(async () => await this.buildModuleCache(pack.name)); + commands.push(async () => await this.warmCompileCache(pack.name)); + + async.waterfall(commands).then(() => { + if (!options.argv.json) { this.logSuccess(); } + resolve({name: child, installPath: destination}); + }, error => { + this.logFailure(); + reject(error); + }); + }); }); } @@ -171,15 +171,17 @@ Run ppm -v after installing Git to see what version has been detected.\ return message; } - installModules(options, callback) { + installModules(options) { if (!options.argv.json) { process.stdout.write('Installing modules '); } - return this.forkInstallCommand(options, (...args) => { - if (options.argv.json) { - return this.logCommandResultsIfFail(callback, ...args); - } else { - return this.logCommandResults(callback, ...args); - } + return new Promise((resolve, reject) => { + this.forkInstallCommand(options, (...args) => { + if (options.argv.json) { + return void this.logCommandResultsIfFail(...args).then(resolve, reject); + } + + return this.logCommandResults(...args).then(resolve, reject); + }); }); } @@ -205,31 +207,33 @@ Run ppm -v after installing Git to see what version has been detected.\ // Request package information from the package API for a given package name. // // packageName - The string name of the package to request. - // callback - The function to invoke when the request completes with an error - // as the first argument and an object as the second. - requestPackage(packageName, callback) { + // + // return value - A Promise that rejects with an appropriate error or resolves to the response body + requestPackage(packageName) { const requestSettings = { url: `${config.getAtomPackagesUrl()}/${packageName}`, json: true, retries: 4 }; - return request.get(requestSettings, function(error, response, body) { - let message; - if (body == null) { body = {}; } - if (error != null) { - message = `Request for package information failed: ${error.message}`; - if (error.status) { message += ` (${error.status})`; } - return callback(message); - } else if (response.statusCode !== 200) { - message = request.getErrorMessage(body, error); - return callback(`Request for package information failed: ${message}`); - } else { - if (body.releases.latest) { - return callback(null, body); - } else { - return callback(`No releases available for ${packageName}`); + return new Promise((resolve, reject) => { + request.get(requestSettings, (error, response, body) => { + let message; + body ??= {}; + if (error != null) { + message = `Request for package information failed: ${error.message}`; + if (error.status) { message += ` (${error.status})`; } + return void reject(message); } - } + if (response.statusCode !== 200) { + message = request.getErrorMessage(body, error); + return void reject(`Request for package information failed: ${message}`); + } + if (!body.releases.latest) { + return void reject(`No releases available for ${packageName}`); + } + + resolve(body); + }); }); } @@ -253,17 +257,17 @@ Run ppm -v after installing Git to see what version has been detected.\ // key is also supported. The version defaults to the latest if // unspecified. // options - The installation options object. - // callback - The function to invoke when installation completes with an - // error as the first argument. - installRegisteredPackage(metadata, options, callback) { + // + // return value - A Promise; it either rejects with an error, or resolves to an object representing + // data from the installed package.js. + async installRegisteredPackage(metadata, options) { const packageName = metadata.name; let packageVersion = metadata.version; - const installGlobally = options.installGlobally != null ? options.installGlobally : true; + const installGlobally = options.installGlobally ?? true; if (!installGlobally) { if (packageVersion && this.isPackageInstalled(packageName, packageVersion)) { - callback(null, {}); - return; + return {}; } } @@ -276,55 +280,50 @@ Run ppm -v after installing Git to see what version has been detected.\ } } - return this.requestPackage(packageName, (error, pack) => { - if (error != null) { - this.logFailure(); - return callback(error); - } else { - if (packageVersion == null) { packageVersion = this.getLatestCompatibleVersion(pack); } - if (!packageVersion) { - this.logFailure(); - callback(`No available version compatible with the installed Atom version: ${this.installedAtomVersion}`); - return; + const commands = []; + try { + const pack = await this.requestPackage(packageName); + packageVersion ??= this.getLatestCompatibleVersion(pack); + if (!packageVersion) { + throw `No available version compatible with the installed Pulsar version: ${this.installedAtomVersion}`; + } + const {tarball} = pack.versions[packageVersion]?.dist ?? {}; + if (!tarball) { + throw `Package version: ${packageVersion} not found`; + } + commands.push(async () => await this.installModule(options, pack, tarball)); + if (installGlobally && (packageName.localeCompare(pack.name, 'en', {sensitivity: 'accent'}) !== 0)) { + commands.push(async newPack => { // package was renamed; delete old package folder + fs.removeSync(path.join(this.atomPackagesDirectory, packageName)); + return newPack; + }); + } + commands.push(async ({installPath}) => { + if (installPath == null) { + return {}; } - const {tarball} = pack.versions[packageVersion]?.dist != null ? pack.versions[packageVersion]?.dist : {}; - if (!tarball) { - this.logFailure(); - callback(`Package version: ${packageVersion} not found`); - return; - } + metadata = JSON.parse(fs.readFileSync(path.join(installPath, 'package.json'), 'utf8')); + const json = {installPath, metadata}; + return json; + }); // installed locally, no install path data + } catch (error) { + this.logFailure(); + throw error; + } - const commands = []; - commands.push(next => this.installModule(options, pack, tarball, next)); - if (installGlobally && (packageName.localeCompare(pack.name, 'en', {sensitivity: 'accent'}) !== 0)) { - commands.push((newPack, next) => { // package was renamed; delete old package folder - fs.removeSync(path.join(this.atomPackagesDirectory, packageName)); - return next(null, newPack); - }); - } - commands.push(function({installPath}, next) { - if (installPath != null) { - metadata = JSON.parse(fs.readFileSync(path.join(installPath, 'package.json'), 'utf8')); - const json = {installPath, metadata}; - return next(null, json); - } else { - return next(null, {}); - } - }); // installed locally, no install path data - - return async.waterfall(commands, (error, json) => { - if (!installGlobally) { - if (error != null) { - this.logFailure(); - } else { - if (!options.argv.json) { this.logSuccess(); } - } - } - return callback(error, json); - }); + try { + const json = await async.waterfall(commands); + if (!installGlobally) { + if (!options.argv.json) { this.logSuccess(); } } - }); + return json; + } catch (error) { + if (!installGlobally) { + this.logFailure(); + } + throw error; + } } // Install the package with the given name and local path @@ -332,68 +331,69 @@ Run ppm -v after installing Git to see what version has been detected.\ // packageName - The name of the package // packagePath - The local path of the package in the form "file:./packages/package-name" // options - The installation options object. - // callback - The function to invoke when installation completes with an - // error as the first argument. - installLocalPackage(packageName, packagePath, options, callback) { - if (!options.argv.json) { - process.stdout.write(`Installing ${packageName} from ${packagePath.slice('file:'.length)} `); - const commands = []; - commands.push(next => { - return this.installModule(options, {name: packageName}, packagePath, next); - }); - commands.push(function({installPath}, next) { - if (installPath != null) { - const metadata = JSON.parse(fs.readFileSync(path.join(installPath, 'package.json'), 'utf8')); - const json = {installPath, metadata}; - return next(null, json); - } else { - return next(null, {}); - } - }); // installed locally, no install path data + // + // return value - A Promise that resolves to the object representing the installed package.json + // or rejects with an error. + async installLocalPackage(packageName, packagePath, options) { + if (options.argv.json) { + return; + } - return async.waterfall(commands, (error, json) => { - if (error != null) { - this.logFailure(); - } else { - if (!options.argv.json) { this.logSuccess(); } - } - return callback(error, json); - }); + process.stdout.write(`Installing ${packageName} from ${packagePath.slice('file:'.length)} `); + const commands = []; + commands.push(next => { + return this.installModule(options, {name: packageName}, packagePath).then(value => void next(null, value), next); + }); + commands.push(({installPath}, next) => { + if (installPath != null) { + const metadata = JSON.parse(fs.readFileSync(path.join(installPath, 'package.json'), 'utf8')); + const json = {installPath, metadata}; + return next(null, json); + } else { + return next(null, {}); + } + }); // installed locally, no install path data + + try { + const json = await async.waterfall(commands); + if (!options.argv.json) { this.logSuccess(); } + return json; + } catch (error) { + this.logFailure(); + throw error; } } // Install all the package dependencies found in the package.json file. // // options - The installation options - // callback - The callback function to invoke when done with an error as the - // first argument. - installPackageDependencies(options, callback) { + // + // return value - A Promise that rejects with an error or resolves without a value + async installPackageDependencies(options) { options = _.extend({}, options, {installGlobally: false}); const commands = []; const object = this.getPackageDependencies(options.cwd); for (let name in object) { const version = object[name]; - ((name, version) => { - return commands.push(next => { + commands.push(async () => { if (this.repoLocalPackagePathRegex.test(version)) { - return this.installLocalPackage(name, version, options, next); + await this.installLocalPackage(name, version, options); } else { - return this.installRegisteredPackage({name, version}, options, next); + await this.installRegisteredPackage({name, version}, options); } - }); - })(name, version); + }); } - return async.series(commands, callback); + await async.series(commands); } - installDependencies(options, callback) { + async installDependencies(options) { options.installGlobally = false; const commands = []; - commands.push(callback => this.installModules(options, callback)); - commands.push(callback => this.installPackageDependencies(options, callback)); + commands.push(async () => void await this.installModules(options)); + commands.push(async () => void await this.installPackageDependencies(options)); - return async.waterfall(commands, callback); + await async.waterfall(commands); } // Get all package dependency names and versions from the package.json file. @@ -445,7 +445,7 @@ Run ppm -v after installing Git to see what version has been detected.\ // Compile a sample native module to see if a useable native build toolchain // is instlalled and successfully detected. This will include both Python // and a compiler. - checkNativeBuildTools(callback) { + checkNativeBuildTools() { process.stdout.write('Checking for native build tools '); const buildArgs = ['--globalconfig', config.getGlobalConfigPath(), '--userconfig', config.getUserConfigPath(), 'build']; @@ -462,9 +462,11 @@ Run ppm -v after installing Git to see what version has been detected.\ fs.removeSync(path.resolve(__dirname, '..', 'native-module', 'build')); - return this.fork(this.atomNpmPath, buildArgs, buildOptions, (...args) => { - return this.logCommandResults(callback, ...args); - }); + return new Promise((resolve, reject) => + void this.fork(this.atomNpmPath, buildArgs, buildOptions, (...args) => + void this.logCommandResults(...args).then(resolve, reject) + ) + ); } packageNamesFromPath(filePath) { @@ -478,45 +480,41 @@ Run ppm -v after installing Git to see what version has been detected.\ return this.sanitizePackageNames(packages.split(/\s/)); } - buildModuleCache(packageName, callback) { + async buildModuleCache(packageName) { const packageDirectory = path.join(this.atomPackagesDirectory, packageName); const rebuildCacheCommand = new RebuildModuleCache(); - return rebuildCacheCommand.rebuild(packageDirectory, () => // Ignore cache errors and just finish the install - callback()); + await rebuildCacheCommand.rebuild(packageDirectory).catch(_ => {}); // Ignore cache errors and just finish the install } - warmCompileCache(packageName, callback) { + async warmCompileCache(packageName) { const packageDirectory = path.join(this.atomPackagesDirectory, packageName); - return this.getResourcePath(resourcePath => { - try { - const CompileCache = require(path.join(resourcePath, 'src', 'compile-cache')); + const resourcePath = await this.getResourcePath(); + try { + const CompileCache = require(path.join(resourcePath, 'src', 'compile-cache')); - const onDirectory = directoryPath => path.basename(directoryPath) !== 'node_modules'; + const onDirectory = directoryPath => path.basename(directoryPath) !== 'node_modules'; - const onFile = filePath => { - try { - return CompileCache.addPathToCache(filePath, this.atomDirectory); - } catch (error) {} - }; + const onFile = filePath => { + try { + return CompileCache.addPathToCache(filePath, this.atomDirectory); + } catch (error) {} + }; - fs.traverseTreeSync(packageDirectory, onFile, onDirectory); - } catch (error) {} - return callback(null); - }); + fs.traverseTreeSync(packageDirectory, onFile, onDirectory); + } catch (error) {} } - isBundledPackage(packageName, callback) { - return this.getResourcePath(function(resourcePath) { - let atomMetadata; - try { - atomMetadata = JSON.parse(fs.readFileSync(path.join(resourcePath, 'package.json'))); - } catch (error) { - return callback(false); - } + async isBundledPackage(packageName) { + const resourcePath = await this.getResourcePath(); + let atomMetadata; + try { + atomMetadata = JSON.parse(fs.readFileSync(path.join(resourcePath, 'package.json'))); + } catch (error) { + return false; + } - return callback(atomMetadata?.packageDependencies?.hasOwnProperty(packageName)); - }); + return atomMetadata?.packageDependencies?.hasOwnProperty(packageName); } getLatestCompatibleVersion(pack) { @@ -554,72 +552,46 @@ Run ppm -v after installing Git to see what version has been detected.\ return hostedGitInfo.fromUrl(name); } - installGitPackage(packageUrl, options, callback, version) { + async installGitPackage(packageUrl, options, version) { const tasks = []; const cloneDir = temp.mkdirSync("atom-git-package-clone-"); - tasks.push((data, next) => { - const urls = this.getNormalizedGitUrls(packageUrl); - return this.cloneFirstValidGitUrl(urls, cloneDir, options, err => next(err, data)); - }); + const urls = this.getNormalizedGitUrls(packageUrl); + await this.cloneFirstValidGitUrl(urls, cloneDir, options); - tasks.push((data, next) => { - if (version) { - let error; - const repo = Git.open(cloneDir); - data.sha = version; - const checked = repo.checkoutRef(`refs/tags/${version}`, false) || - repo.checkoutReference(version, false); - if (!checked) { error = `Can't find the branch, tag, or commit referenced by ${version}`; } - return next(error, data); - } else { - return this.getRepositoryHeadSha(cloneDir, function(err, sha) { - data.sha = sha; - return next(err, data); - }); - } - }); - - tasks.push((data, next) => { - return this.installGitPackageDependencies(cloneDir, options, err => next(err, data)); - }); - - tasks.push(function(data, next) { - const metadataFilePath = CSON.resolve(path.join(cloneDir, 'package')); - return CSON.readFile(metadataFilePath, function(err, metadata) { - data.metadataFilePath = metadataFilePath; - data.metadata = metadata; - return next(err, data); - }); - }); + const data = {}; + if (version) { + const repo = Git.open(cloneDir); + data.sha = version; + const checked = repo.checkoutRef(`refs/tags/${version}`, false) || repo.checkoutReference(version, false); + if (!checked) { throw `Can't find the branch, tag, or commit referenced by ${version}`; } + } else { + const sha = this.getRepositoryHeadSha(cloneDir); + data.sha = sha; + } - tasks.push(function(data, next) { - data.metadata.apmInstallSource = { - type: "git", - source: packageUrl, - sha: data.sha - }; - return CSON.writeFile(data.metadataFilePath, data.metadata, err => next(err, data)); - }); + await this.installGitPackageDependencies(cloneDir, options); - tasks.push((data, next) => { - const {name} = data.metadata; - const targetDir = path.join(this.atomPackagesDirectory, name); - if (!options.argv.json) { process.stdout.write(`Moving ${name} to ${targetDir} `); } - return fs.cp(cloneDir, targetDir, err => { - if (err) { - return next(err); - } else { - if (!options.argv.json) { this.logSuccess(); } - const json = {installPath: targetDir, metadata: data.metadata}; - return next(null, json); - } - }); - }); + const metadataFilePath = CSON.resolve(path.join(cloneDir, 'package')); + const metadata = CSON.readFileSync(metadataFilePath); + data.metadataFilePath = metadataFilePath; + data.metadata = metadata; - const iteratee = (currentData, task, next) => task(currentData, next); - return async.reduce(tasks, {}, iteratee, callback); + data.metadata.apmInstallSource = { + type: "git", + source: packageUrl, + sha: data.sha + }; + CSON.writeFileSync(data.metadataFilePath, data.metadata); + + const {name} = data.metadata; + const targetDir = path.join(this.atomPackagesDirectory, name); + if (!options.argv.json) { process.stdout.write(`Moving ${name} to ${targetDir} `); } + await fs.cp(cloneDir, targetDir); + if (!options.argv.json) { this.logSuccess(); } + const json = {installPath: targetDir, metadata: data.metadata}; + return json; } getNormalizedGitUrls(packageUrl) { @@ -639,59 +611,56 @@ Run ppm -v after installing Git to see what version has been detected.\ } } - cloneFirstValidGitUrl(urls, cloneDir, options, callback) { - return async.detectSeries(urls, (url, next) => { - return this.cloneNormalizedUrl(url, cloneDir, options, error => next(null, !error)); - } - , function(err, result) { - if (err || !result) { - const invalidUrls = `Couldn't clone ${urls.join(' or ')}`; - const invalidUrlsError = new Error(invalidUrls); - return callback(invalidUrlsError); - } else { - return callback(); + async cloneFirstValidGitUrl(urls, cloneDir, options) { + try { + const result = await async.detectSeries(urls, async url => + await this.cloneNormalizedUrl(url, cloneDir, options).then(() => true, () => false) + ); + if (!result) { + throw 'Missing result.'; } - }); + } catch (error) { + const invalidUrls = `Couldn't clone ${urls.join(' or ')}`; + const invalidUrlsError = new Error(invalidUrls); + throw invalidUrlsError; + } } - cloneNormalizedUrl(url, cloneDir, options, callback) { + async cloneNormalizedUrl(url, cloneDir, options) { // Require here to avoid circular dependency const Develop = require('./develop'); const develop = new Develop(); - return develop.cloneRepository(url, cloneDir, options, err => callback(err)); + await develop.cloneRepository(url, cloneDir, options); } - installGitPackageDependencies(directory, options, callback) { + async installGitPackageDependencies(directory, options) { options.cwd = directory; - return this.installDependencies(options, callback); + await this.installDependencies(options); } - getRepositoryHeadSha(repoDir, callback) { - try { - const repo = Git.open(repoDir); - const sha = repo.getReferenceTarget("HEAD"); - return callback(null, sha); - } catch (err) { - return callback(err); - } + getRepositoryHeadSha(repoDir) { + const repo = Git.open(repoDir); + const sha = repo.getReferenceTarget("HEAD"); + return sha; } - run(options) { + async run(options) { let packageNames; - const {callback} = options; options = this.parseOptions(options.commandArgs); const packagesFilePath = options.argv['packages-file']; this.createAtomDirectories(); if (options.argv.check) { - config.loadNpm((error, npm) => { + try { + const npm = await config.loadNpm(); this.npm = npm; - return this.loadInstalledAtomMetadata(() => { - return this.checkNativeBuildTools(callback); - }); - }); + await this.loadInstalledAtomMetadata(); + await this.checkNativeBuildTools(); + } catch (error) { + return error; //errors as return values atm + } return; } @@ -700,41 +669,42 @@ Run ppm -v after installing Git to see what version has been detected.\ process.env.NODE_DEBUG = 'request'; } - const installPackage = (name, nextInstallStep) => { + const installPackage = async name => { const gitPackageInfo = this.getHostedGitInfo(name); if (gitPackageInfo || (name.indexOf('file://') === 0)) { - return this.installGitPackage(name, options, nextInstallStep, options.argv.branch || options.argv.tag); - } else if (name === '.') { - return this.installDependencies(options, nextInstallStep); - } else { // is registered package - let version; - const atIndex = name.indexOf('@'); - if (atIndex > 0) { - version = name.substring(atIndex + 1); - name = name.substring(0, atIndex); - } + return await this.installGitPackage(name, options, options.argv.branch || options.argv.tag); + } + if (name === '.') { + await this.installDependencies(options); + return; + } + + // is registered package + let version; + const atIndex = name.indexOf('@'); + if (atIndex > 0) { + version = name.substring(atIndex + 1); + name = name.substring(0, atIndex); + } - return this.isBundledPackage(name, isBundledPackage => { - if (isBundledPackage) { - console.error(`\ + const isBundledPackage = await this.isBundledPackage(name); + if (isBundledPackage) { + console.error(`\ The ${name} package is bundled with Pulsar and should not be explicitly installed. You can run \`ppm uninstall ${name}\` to uninstall it and then the version bundled with Pulsar will be used.\ `.yellow - ); - } - return this.installRegisteredPackage({name, version}, options, nextInstallStep); - }); + ); } + return await this.installRegisteredPackage({name, version}, options); }; if (packagesFilePath) { try { packageNames = this.packageNamesFromPath(packagesFilePath); - } catch (error1) { - const error = error1; - return callback(error); + } catch (error) { + return error; //errors as return values atm } } else { packageNames = this.packageNamesFromArgv(options.argv); @@ -742,16 +712,24 @@ with Pulsar will be used.\ } const commands = []; - commands.push(callback => { return config.loadNpm((error, npm) => { this.npm = npm; return callback(error); }); }); - commands.push(callback => this.loadInstalledAtomMetadata(() => callback())); - packageNames.forEach(packageName => commands.push(callback => installPackage(packageName, callback))); - const iteratee = (item, next) => item(next); - return async.mapSeries(commands, iteratee, function(err, installedPackagesInfo) { - if (err) { return callback(err); } + commands.push(async () => { + const npm = await config.loadNpm(); + this.npm = npm; + }); + commands.push(async () => { + await this.loadInstalledAtomMetadata(); + }); + packageNames.forEach(packageName => + void commands.push(async () => await installPackage(packageName)) + ); + const iteratee = async fn => await fn(); + try { + let installedPackagesInfo = await async.mapSeries(commands, iteratee); installedPackagesInfo = _.compact(installedPackagesInfo); installedPackagesInfo = installedPackagesInfo.filter((item, idx) => packageNames[idx] !== "."); if (options.argv.json) { console.log(JSON.stringify(installedPackagesInfo, null, " ")); } - return callback(null); - }); + } catch (error) { + return error; //errors as return values atm + } } } diff --git a/src/link.js b/src/link.js index 9a5f09a2..5b3492e6 100644 --- a/src/link.js +++ b/src/link.js @@ -28,9 +28,7 @@ Run \`ppm links\` to view all the currently linked packages.\ return options.alias('d', 'dev').boolean('dev').describe('dev', 'Link to ~/.pulsar/dev/packages'); } - run(options) { - let targetPath; - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); const packagePath = options.argv._[0]?.toString() ?? "."; @@ -38,19 +36,16 @@ Run \`ppm links\` to view all the currently linked packages.\ let packageName = options.argv.name; try { - if (!packageName) { packageName = CSON.readFileSync(CSON.resolve(path.join(linkPath, 'package'))).name; } + packageName ||= CSON.readFileSync(CSON.resolve(path.join(linkPath, 'package'))).name; } catch (error) {} - if (!packageName) { packageName = path.basename(linkPath); } + packageName ||= path.basename(linkPath); - if (options.argv.dev) { - targetPath = path.join(config.getAtomDirectory(), 'dev', 'packages', packageName); - } else { - targetPath = path.join(config.getAtomDirectory(), 'packages', packageName); - } + const targetPath = options.argv.dev + ? path.join(config.getAtomDirectory(), 'dev', 'packages', packageName) + : path.join(config.getAtomDirectory(), 'packages', packageName); if (!fs.existsSync(linkPath)) { - callback(`Package directory does not exist: ${linkPath}`); - return; + return `Package directory does not exist: ${linkPath}`; // error as value for now } try { @@ -58,9 +53,8 @@ Run \`ppm links\` to view all the currently linked packages.\ fs.makeTreeSync(path.dirname(targetPath)); fs.symlinkSync(linkPath, targetPath, 'junction'); console.log(`${targetPath} -> ${linkPath}`); - return callback(); } catch (error) { - return callback(`Linking ${targetPath} to ${linkPath} failed: ${error.message}`); + return `Linking ${targetPath} to ${linkPath} failed: ${error.message}`; // error as value for now } } } diff --git a/src/links.js b/src/links.js index e1d32094..eb520d54 100644 --- a/src/links.js +++ b/src/links.js @@ -58,11 +58,8 @@ List all of the symlinked atom packages in ~/.atom/packages and }); } - run(options) { - const {callback} = options; - + async run(_options) { this.logLinks(this.devPackagesPath); this.logLinks(this.packagesPath); - return callback(); } } diff --git a/src/list.js b/src/list.js index d606ae29..62110157 100644 --- a/src/list.js +++ b/src/list.js @@ -129,17 +129,18 @@ List all the installed packages and also the packages bundled with Atom.\ return packages; } - listUserPackages(options, callback) { - const userPackages = this.listPackages(this.userPackagesDirectory, options) + listUserPackages(options) { + const userPackages = this + .listPackages(this.userPackagesDirectory, options) .filter(pack => !pack.apmInstallSource); if (!options.argv.bare && !options.argv.json) { console.log(`Community Packages (${userPackages.length})`.cyan, `${this.userPackagesDirectory}`); } - return callback?.(null, userPackages); + return userPackages; } - listDevPackages(options, callback) { - if (!options.argv.dev) { return callback?.(null, []); } + listDevPackages(options) { + if (!options.argv.dev) { return []; } const devPackages = this.listPackages(this.devPackagesDirectory, options); if (devPackages.length > 0) { @@ -147,70 +148,52 @@ List all the installed packages and also the packages bundled with Atom.\ console.log(`Dev Packages (${devPackages.length})`.cyan, `${this.devPackagesDirectory}`); } } - return callback?.(null, devPackages); + return devPackages; } - listGitPackages(options, callback) { - const gitPackages = this.listPackages(this.userPackagesDirectory, options) + listGitPackages(options) { + const gitPackages = this + .listPackages(this.userPackagesDirectory, options) .filter(pack => pack.apmInstallSource?.type === 'git'); if (gitPackages.length > 0) { if (!options.argv.bare && !options.argv.json) { console.log(`Git Packages (${gitPackages.length})`.cyan, `${this.userPackagesDirectory}`); } } - return callback?.(null, gitPackages); + return gitPackages; } - listBundledPackages(options, callback) { - return config.getResourcePath(resourcePath => { - let _atomPackages; - let metadata; - try { - const metadataPath = path.join(resourcePath, 'package.json'); - ({_atomPackages} = JSON.parse(fs.readFileSync(metadataPath))); - } catch (error) {} - if (_atomPackages == null) { _atomPackages = {}; } - let packages = ((() => { - const result = []; - for (let packageName in _atomPackages) { - ({metadata} = _atomPackages[packageName]); - result.push(metadata); - } - return result; - })()); + async listBundledPackages(options) { + const resourcePath = await config.getResourcePath(); + let atomPackages; + try { + const metadataPath = path.join(resourcePath, 'package.json'); + ({_atomPackages: atomPackages} = JSON.parse(fs.readFileSync(metadataPath))); + } catch (error) {} + atomPackages ??= {}; + const packagesMeta = Object.values(atomPackages) + .map(packageValue => packageValue.metadata) + .filter(metadata => this.isPackageVisible(options, metadata)); - packages = packages.filter(metadata => { - return this.isPackageVisible(options, metadata); - }); - - if (!options.argv.bare && !options.argv.json) { - if (options.argv.themes) { - console.log(`${'Built-in Atom Themes'.cyan} (${packages.length})`); - } else { - console.log(`${'Built-in Atom Packages'.cyan} (${packages.length})`); - } - } + if (!options.argv.bare && !options.argv.json) { + console.log(`${`Built-in Atom ${options.argv.themes ? 'Themes' : 'Packages'}`.cyan} (${packagesMeta.length})`); + } - return callback?.(null, packages); - }); + return packagesMeta; } listInstalledPackages(options) { - this.listDevPackages(options, (error, packages) => { - if (packages.length > 0) { this.logPackages(packages, options); } + const devPackages = this.listDevPackages(options); + if (devPackages.length > 0) { this.logPackages(devPackages, options); } - this.listUserPackages(options, (error, packages) => { - this.logPackages(packages, options); + const userPackages = this.listUserPackages(options); + this.logPackages(userPackages, options); - this.listGitPackages(options, (error, packages) => { - if (packages.length > 0) { return this.logPackages(packages, options); } - }); - }); - }); + const gitPackages = this.listGitPackages(options) + if (gitPackages.length > 0) { this.logPackages(gitPackages, options); } } - listPackagesAsJson(options, callback) { - if (callback == null) { callback = function() {}; } + async listPackagesAsJson(options) { const output = { core: [], dev: [], @@ -218,41 +201,28 @@ List all the installed packages and also the packages bundled with Atom.\ user: [] }; - this.listBundledPackages(options, (error, packages) => { - if (error) { return callback(error); } - output.core = packages; - this.listDevPackages(options, (error, packages) => { - if (error) { return callback(error); } - output.dev = packages; - this.listUserPackages(options, (error, packages) => { - if (error) { return callback(error); } - output.user = packages; - this.listGitPackages(options, function(error, packages) { - if (error) { return callback(error); } - output.git = packages; - console.log(JSON.stringify(output)); - return callback(); - }); - }); - }); - }); + output.core = await this.listBundledPackages(options); + output.dev = this.listDevPackages(options); + output.user = this.listUserPackages(options); + output.git = this.listGitPackages(options); + + console.log(JSON.stringify(output)); } - run(options) { - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); if (options.argv.json) { - return this.listPackagesAsJson(options, callback); - } else if (options.argv.installed) { + await this.listPackagesAsJson(options); + return; + } + if (options.argv.installed) { this.listInstalledPackages(options); - return callback(); - } else { - this.listBundledPackages(options, (error, packages) => { - this.logPackages(packages, options); - this.listInstalledPackages(options); - return callback(); - }); + return; } + + const bundledPackages = await this.listBundledPackages(options); + this.logPackages(bundledPackages, options); + this.listInstalledPackages(options); } } diff --git a/src/login.js b/src/login.js index 544dbe04..b6ecb687 100644 --- a/src/login.js +++ b/src/login.js @@ -1,7 +1,6 @@ const _ = require('underscore-plus'); const yargs = require('yargs'); -const Q = require('q'); const read = require('read'); const open = require('open'); @@ -12,21 +11,13 @@ module.exports = class Login extends Command { static commandNames = [ "login" ]; - constructor(...args) { - super(...args); - this.welcomeMessage = this.welcomeMessage.bind(this); - this.getToken = this.getToken.bind(this); - this.saveToken = this.saveToken.bind(this); - } - - static getTokenOrLogin(callback) { - return auth.getToken(function(error, token) { - if (error != null) { - return new Login().run({callback, commandArgs: []}); - } else { - return callback(null, token); - } - }); + static async getTokenOrLogin() { + try { + const token = await auth.getToken(); + return token; + } catch (error) { + return await new Login().obtainToken(); + } } parseOptions(argv) { @@ -43,25 +34,37 @@ be used to identify you when publishing packages.\ return options.string('token').describe('token', 'Package API token'); } - run(options) { - const {callback} = options; + async obtainToken(offeredToken) { + let token = offeredToken; + if (token == null) { + await this.welcomeMessage(); + await this.openURL(); + token = await this.getToken(); + } + await this.saveToken(token); + return token; + } + + async run(options) { options = this.parseOptions(options.commandArgs); - return Q({token: options.argv.token}) - .then(this.welcomeMessage) - .then(this.openURL) - .then(this.getToken) - .then(this.saveToken) - .then(token => callback(null, token)) - .catch(callback); + try { + await this.obtainToken(options.argv.token); + } catch (error) { + return error; //errors as return values atm + } } prompt(options) { - const readPromise = Q.denodeify(read); - return readPromise(options); + return new Promise((resolve, reject) => + void read(options, (error, answer) => + error != null + ? void reject(error) + : void resolve(answer) + ) + ); } - welcomeMessage(state) { - if (state.token) { return Q(state); } + async welcomeMessage() { const welcome = `\ Welcome to Pulsar! @@ -74,31 +77,23 @@ copy the token and paste it below when prompted. `; console.log(welcome); - return this.prompt({prompt: "Press [Enter] to open your account page."}); + await this.prompt({prompt: "Press [Enter] to open your account page."}); } - openURL(state) { - if (state.token) { return Q(state); } - - return open('https://web.pulsar-edit.dev/users'); + async openURL() { + await open('https://web.pulsar-edit.dev/users'); } - getToken(state) { - if (state.token) { return Q(state); } - - return this.prompt({prompt: 'Token>', edit: true}) - .spread(function(token) { - state.token = token; - return Q(state); - }); + async getToken() { + const token = await this.prompt({prompt: 'Token>', edit: true}); + return token; } - saveToken({token}) { + async saveToken(token) { if (!token) { throw new Error("Token is required"); } process.stdout.write('Saving token to Keychain '); - auth.saveToken(token); + await auth.saveToken(token); this.logSuccess(); - return Q(token); } } diff --git a/src/package-converter.js b/src/package-converter.js index 8f272185..9a1030fd 100644 --- a/src/package-converter.js +++ b/src/package-converter.js @@ -36,13 +36,14 @@ class PackageConverter { }; } - convert(callback) { + async convert() { const {protocol} = url.parse(this.sourcePath); if ((protocol === 'http:') || (protocol === 'https:')) { - return this.downloadBundle(callback); - } else { - return this.copyDirectories(this.sourcePath, callback); + await this.downloadBundle(); + return; } + + await this.copyDirectories(this.sourcePath); } getDownloadUrl() { @@ -51,42 +52,43 @@ class PackageConverter { return downloadUrl += '/archive/master.tar.gz'; } - downloadBundle(callback) { + async downloadBundle() { const tempPath = temp.mkdirSync('atom-bundle-'); const requestOptions = {url: this.getDownloadUrl()}; - return request.createReadStream(requestOptions, readStream => { - readStream.on('response', function({headers, statusCode}) { + const readStream = await request.createReadStream(requestOptions); + return new Promise((resolve, reject) => { + readStream.on('response', ({headers, statusCode}) => { if (statusCode !== 200) { - return callback(`Download failed (${headers.status})`); + reject(`Download failed (${headers.status})`); } }); - - return readStream.pipe(zlib.createGunzip()).pipe(tar.extract({cwd: tempPath})) - .on('error', error => callback(error)) - .on('end', () => { + + readStream.pipe(zlib.createGunzip()).pipe(tar.extract({cwd: tempPath})) + .on('error', error => reject(error)) + .on('end', async () => { const sourcePath = path.join(tempPath, fs.readdirSync(tempPath)[0]); - return this.copyDirectories(sourcePath, callback); + await this.copyDirectories(sourcePath); + resolve(); }); }); } - copyDirectories(sourcePath, callback) { + async copyDirectories(sourcePath) { let packageName; sourcePath = path.resolve(sourcePath); try { packageName = JSON.parse(fs.readFileSync(path.join(sourcePath, 'package.json')))?.packageName; } catch (error) {} - if (packageName == null) { packageName = path.basename(this.destinationPath); } + packageName ??= path.basename(this.destinationPath); - this.convertSnippets(packageName, sourcePath); - this.convertPreferences(packageName, sourcePath); + await this.convertSnippets(packageName, sourcePath); + await this.convertPreferences(packageName, sourcePath); this.convertGrammars(sourcePath); - return callback(); } filterObject(object) { delete object.uuid; - return delete object.keyEquivalent; + delete object.keyEquivalent; } convertSettings(settings) { @@ -118,57 +120,55 @@ class PackageConverter { } writeFileSync(filePath, object) { - if (object == null) { object = {}; } + object ??= {}; this.filterObject(object); if (Object.keys(object).length > 0) { - return CSON.writeFileSync(filePath, object); + CSON.writeFileSync(filePath, object); } } convertFile(sourcePath, destinationDir) { - let contents; const extension = path.extname(sourcePath); let destinationName = `${path.basename(sourcePath, extension)}.cson`; destinationName = destinationName.toLowerCase(); const destinationPath = path.join(destinationDir, destinationName); + let contents; if (_.contains(this.plistExtensions, path.extname(sourcePath))) { contents = plist.parseFileSync(sourcePath); } else if (_.contains(['.json', '.cson'], path.extname(sourcePath))) { contents = CSON.readFileSync(sourcePath); } - return this.writeFileSync(destinationPath, contents); + this.writeFileSync(destinationPath, contents); } normalizeFilenames(directoryPath) { if (!fs.isDirectorySync(directoryPath)) { return; } - return (() => { - const result = []; - for (let child of Array.from(fs.readdirSync(directoryPath))) { - const childPath = path.join(directoryPath, child); - - // Invalid characters taken from http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx - let convertedFileName = child.replace(/[|?*<>:"\\\/]+/g, '-'); - if (child === convertedFileName) { continue; } - - convertedFileName = convertedFileName.replace(/[\s-]+/g, '-'); - let convertedPath = path.join(directoryPath, convertedFileName); - let suffix = 1; - while (fs.existsSync(convertedPath) || fs.existsSync(convertedPath.toLowerCase())) { - const extension = path.extname(convertedFileName); - convertedFileName = `${path.basename(convertedFileName, extension)}-${suffix}${extension}`; - convertedPath = path.join(directoryPath, convertedFileName); - suffix++; - } - result.push(fs.renameSync(childPath, convertedPath)); + const result = []; + for (let child of Array.from(fs.readdirSync(directoryPath))) { + const childPath = path.join(directoryPath, child); + + // Invalid characters taken from http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx + let convertedFileName = child.replace(/[|?*<>:"\\\/]+/g, '-'); + if (child === convertedFileName) { continue; } + + convertedFileName = convertedFileName.replace(/[\s-]+/g, '-'); + let convertedPath = path.join(directoryPath, convertedFileName); + let suffix = 1; + while (fs.existsSync(convertedPath) || fs.existsSync(convertedPath.toLowerCase())) { + const extension = path.extname(convertedFileName); + convertedFileName = `${path.basename(convertedFileName, extension)}-${suffix}${extension}`; + convertedPath = path.join(directoryPath, convertedFileName); + suffix++; } - return result; - })(); + result.push(fs.renameSync(childPath, convertedPath)); + } + return result; } - convertSnippets(packageName, source) { + async convertSnippets(packageName, source) { let sourceSnippets = path.join(source, 'snippets'); if (!fs.isDirectorySync(sourceSnippets)) { sourceSnippets = path.join(source, 'Snippets'); @@ -176,10 +176,8 @@ class PackageConverter { if (!fs.isDirectorySync(sourceSnippets)) { return; } const snippetsBySelector = {}; - const destination = path.join(this.destinationPath, 'snippets'); for (let child of Array.from(fs.readdirSync(sourceSnippets))) { - var left, selector; - const snippet = (left = this.readFileSync(path.join(sourceSnippets, child))) != null ? left : {}; + const snippet = this.readFileSync(path.join(sourceSnippets, child)) ?? {}; let {scope, name, content, tabTrigger} = snippet; if (!tabTrigger || !content) { continue; } @@ -198,26 +196,26 @@ class PackageConverter { name = path.basename(child, extension); } + let selector; try { - (async () => { - await ready; - })(); + await ready; if (scope) { selector = new ScopeSelector(scope).toCssSelector(); } } catch (e) { e.message = `In file ${e.fileName} at ${JSON.stringify(scope)}: ${e.message}`; throw e; } - if (selector == null) { selector = '*'; } + selector ??= '*'; - if (snippetsBySelector[selector] == null) { snippetsBySelector[selector] = {}; } + snippetsBySelector[selector] ??= {}; snippetsBySelector[selector][name] = {prefix: tabTrigger, body: content}; } + const destination = path.join(this.destinationPath, 'snippets'); this.writeFileSync(path.join(destination, `${packageName}.cson`), snippetsBySelector); return this.normalizeFilenames(destination); } - convertPreferences(packageName, source) { + async convertPreferences(packageName, source) { let sourcePreferences = path.join(source, 'preferences'); if (!fs.isDirectorySync(sourcePreferences)) { sourcePreferences = path.join(source, 'Preferences'); @@ -227,30 +225,27 @@ class PackageConverter { const preferencesBySelector = {}; const destination = path.join(this.destinationPath, 'settings'); for (let child of Array.from(fs.readdirSync(sourcePreferences))) { - var left, properties; - const {scope, settings} = (left = this.readFileSync(path.join(sourcePreferences, child))) != null ? left : {}; + const {scope, settings} = this.readFileSync(path.join(sourcePreferences, child)) ?? {}; if (!scope || !settings) { continue; } - if (properties = this.convertSettings(settings)) { - var selector; - try { - (async () => { - await ready; - })(); - selector = new ScopeSelector(scope).toCssSelector(); - } catch (e) { - e.message = `In file ${e.fileName} at ${JSON.stringify(scope)}: ${e.message}`; - throw e; - } - for (let key in properties) { - const value = properties[key]; - if (preferencesBySelector[selector] == null) { preferencesBySelector[selector] = {}; } - if (preferencesBySelector[selector][key] != null) { - preferencesBySelector[selector][key] = _.extend(value, preferencesBySelector[selector][key]); - } else { - preferencesBySelector[selector][key] = value; - } - } + const properties = this.convertSettings(settings); + if (!properties) { + continue; + } + let selector; + try { + await ready; + selector = new ScopeSelector(scope).toCssSelector(); + } catch (e) { + e.message = `In file ${e.fileName} at ${JSON.stringify(scope)}: ${e.message}`; + throw e; + } + for (let key in properties) { + const value = properties[key]; + preferencesBySelector[selector] ??= {}; + preferencesBySelector[selector][key] = preferencesBySelector[selector][key] != null + ? _.extend(value, preferencesBySelector[selector][key]) + : value; } } diff --git a/src/packages.js b/src/packages.js index adbdc08b..c33d3d57 100644 --- a/src/packages.js +++ b/src/packages.js @@ -9,7 +9,7 @@ module.exports = { // // Returns a name/owner string or null if not parseable. getRepository(pack) { - if (pack == null) { pack = {}; } + pack ??= {}; let repository = pack.repository?.url ?? pack.repository; if (repository) { const repoPath = url.parse(repository.replace(/\.git$/, '')).pathname; @@ -24,7 +24,7 @@ module.exports = { // pack - The package metadata object. // Returns a the remote or 'origin' if not parseable. getRemote(pack) { - if (pack == null) { pack = {}; } + pack ??= {}; return pack.repository?.url ?? pack.repository ?? "origin"; } }; diff --git a/src/publish.js b/src/publish.js index 99f827ce..4dfef937 100644 --- a/src/publish.js +++ b/src/publish.js @@ -57,21 +57,24 @@ have published it.\ // Create a new version and tag use the `npm version` command. // // version - The new version or version increment. - // callback - The callback function to invoke with an error as the first - // argument and a the generated tag string as the second argument. - versionPackage(version, callback) { + // + // return value - A Promise that can reject with an error string + // or resolve to the generated tag string. + versionPackage(version) { process.stdout.write('Preparing and tagging a new version '); const versionArgs = ['version', version, '-m', 'Prepare v%s release']; - return this.fork(this.atomNpmPath, versionArgs, (code, stderr, stdout) => { - if (stderr == null) { stderr = ''; } - if (stdout == null) { stdout = ''; } - if (code === 0) { - this.logSuccess(); - return callback(null, stdout.trim()); - } else { - this.logFailure(); - return callback(`${stdout}\n${stderr}`.trim()); - } + return new Promise((resolve, reject) => { + this.fork(this.atomNpmPath, versionArgs, (code, stderr, stdout) => { + stderr ??= ''; + stdout ??= ''; + if (code === 0) { + this.logSuccess(); + resolve(stdout.trim()); + } else { + this.logFailure(); + reject(`${stdout}\n${stderr}`.trim()); + } + }); }); } @@ -79,13 +82,15 @@ have published it.\ // // tag - The tag to push. // pack - The package metadata. - // callback - The callback function to invoke with an error as the first - // argument. - pushVersion(tag, pack, callback) { + // + // return value - A Promise that delegates the result of the logCommandResults call. + pushVersion(tag, pack) { process.stdout.write(`Pushing ${tag} tag `); const pushArgs = ['push', Packages.getRemote(pack), 'HEAD', tag]; - return this.spawn('git', pushArgs, (...args) => { - return this.logCommandResults(callback, ...args); + return new Promise((resolve, reject) => { + this.spawn('git', pushArgs, (...args) => { + this.logCommandResults(...args).then(resolve, reject); + }); }); } @@ -96,10 +101,11 @@ have published it.\ // // pack - The package metadata. // tag - The tag that was pushed. - // callback - The callback function to invoke when either the tag is available - // or the maximum numbers of requests for the tag have been made. - // No arguments are passed to the callback when it is invoked. - waitForTagToBeAvailable(pack, tag, callback) { + // + // return value - A Promise that resolves (without a value) when either the + // number of max retries have been reached or the tag could + // actually be retrieved. + waitForTagToBeAvailable(pack, tag) { let retryCount = 5; const interval = 1000; const requestSettings = { @@ -107,48 +113,46 @@ have published it.\ json: true }; - var requestTags = () => request.get(requestSettings, function(error, response, tags) { - if (tags == null) { tags = []; } - if ((response != null ? response.statusCode : undefined) === 200) { - for (let index = 0; index < tags.length; index++) { - const {name} = tags[index]; - if (name === tag) { - return callback(); + return new Promise((resolve, _reject) => { + const requestTags = () => void request.get(requestSettings, (_error, response, tags) => { + tags ??= []; + if (response?.statusCode === 200) { + if (tags.find(elem => elem.name === tag) != null) { + resolve(); + return; } } - } - if (--retryCount <= 0) { - return callback(); - } else { - return setTimeout(requestTags, interval); - } + if (--retryCount <= 0) { + return void resolve(); + } + + setTimeout(requestTags, interval); + }); }); - return requestTags(); } // Does the given package already exist in the registry? // // packageName - The string package name to check. - // callback - The callback function invoke with an error as the first - // argument and true/false as the second argument. - packageExists(packageName, callback) { - return Login.getTokenOrLogin(function(error, token) { - if (error != null) { return callback(error); } - - const requestSettings = { - url: `${config.getAtomPackagesUrl()}/${packageName}`, - json: true, - headers: { - authorization: token - } - }; - return request.get(requestSettings, function(error, response, body) { - if (body == null) { body = {}; } + // + // return value - A Promise that can reject with an error or resolve to + // a boolean value. + async packageExists(packageName) { + const token = await Login.getTokenOrLogin(); + const requestSettings = { + url: `${config.getAtomPackagesUrl()}/${packageName}`, + json: true, + headers: { + authorization: token + } + }; + return new Promise((resolve, reject) => { + request.get(requestSettings, (error, response, body) => { + body ??= {}; if (error != null) { - return callback(error); - } else { - return callback(null, response.statusCode === 200); + return void reject(error); } + resolve(response.statusCode === 200); }); }); } @@ -156,92 +160,91 @@ have published it.\ // Register the current repository with the package registry. // // pack - The package metadata. - // callback - The callback function. - registerPackage(pack, callback) { + // + // return value - A Promise that can reject with various errors (even without a value) + // or resolve with true value. + async registerPackage(pack) { if (!pack.name) { - callback('Required name field in package.json not found'); - return; + throw 'Required name field in package.json not found'; } - this.packageExists(pack.name, (error, exists) => { - let repository; - if (error != null) { return callback(error); } - if (exists) { return callback(); } + const exists = await this.packageExists(pack.name); + if (exists) { return Promise.reject(); } - if (!(repository = Packages.getRepository(pack))) { - callback('Unable to parse repository name/owner from package.json repository field'); - return; - } + const repository = Packages.getRepository(pack); - process.stdout.write(`Registering ${pack.name} `); - return Login.getTokenOrLogin((error, token) => { - if (error != null) { - this.logFailure(); - callback(error); - return; - } + if (!repository) { + throw 'Unable to parse repository name/owner from package.json repository field'; + } - const requestSettings = { - url: config.getAtomPackagesUrl(), - json: true, - qs: { - repository - }, - headers: { - authorization: token - } - }; + process.stdout.write(`Registering ${pack.name} `); + + try { + const token = await Login.getTokenOrLogin(); + + const requestSettings = { + url: config.getAtomPackagesUrl(), + json: true, + qs: { + repository + }, + headers: { + authorization: token + } + }; + return new Promise((resolve, reject) => { request.post(requestSettings, (error, response, body) => { - if (body == null) { body = {}; } + body ??= {}; if (error != null) { - return callback(error); - } else if (response.statusCode !== 201) { + return void reject(error); + } + if (response.statusCode !== 201) { const message = request.getErrorMessage(body, error); this.logFailure(); - return callback(`Registering package in ${repository} repository failed: ${message}`); - } else { - this.logSuccess(); - return callback(null, true); + return void reject(`Registering package in ${repository} repository failed: ${message}`); } + + this.logSuccess(); + return resolve(true); }); }); - }); + } catch (error) { + this.logFailure(); + throw error; + } } // Create a new package version at the given Git tag. // // packageName - The string name of the package. // tag - The string Git tag of the new version. - // callback - The callback function to invoke with an error as the first - // argument. - createPackageVersion(packageName, tag, options, callback) { - Login.getTokenOrLogin(function(error, token) { - if (error != null) { - callback(error); - return; + // + // return value - A Promise that rejects with an error or resolves without a value. + async createPackageVersion(packageName, tag, options) { + const token = await Login.getTokenOrLogin(); + const requestSettings = { + url: `${config.getAtomPackagesUrl()}/${packageName}/versions`, + json: true, + qs: { + tag, + rename: options.rename + }, + headers: { + authorization: token } - - const requestSettings = { - url: `${config.getAtomPackagesUrl()}/${packageName}/versions`, - json: true, - qs: { - tag, - rename: options.rename - }, - headers: { - authorization: token - } - }; - request.post(requestSettings, function(error, response, body) { - if (body == null) { body = {}; } + }; + return new Promise((resolve, reject) => { + request.post(requestSettings, (error, response, body) => { + body ??= {}; if (error != null) { - return callback(error); - } else if (response.statusCode !== 201) { + return void reject(error); + } + if (response.statusCode !== 201) { const message = request.getErrorMessage(body, error); - return callback(`Creating new version failed: ${message}`); - } else { - return callback(); + return void reject(`Creating new version failed: ${message}`); } + + resolve(); }); }); } @@ -251,24 +254,20 @@ have published it.\ // pack - The package metadata. // tag - The Git tag string of the package version to publish. // options - An options Object (optional). - // callback - The callback function to invoke when done with an error as the - // first argument. - publishPackage(pack, tag, ...remaining) { - let options; - if (remaining.length >= 2) { options = remaining.shift(); } - if (options == null) { options = {}; } - const callback = remaining.shift(); + // + // return value - A Promise that rejects with an error or resolves without a value. + async publishPackage(pack, tag, options) { + options ??= {}; process.stdout.write(`Publishing ${options.rename || pack.name}@${tag} `); - this.createPackageVersion(pack.name, tag, options, error => { - if (error != null) { - this.logFailure(); - return callback(error); - } else { - this.logSuccess(); - return callback(); - } - }); + try { + await this.createPackageVersion(pack.name, tag, options); + } catch (error) { + this.logFailure(); + throw error; + } + + this.logSuccess(); } logFirstTimePublishMessage(pack) { @@ -278,7 +277,7 @@ have published it.\ process.stdout.write(' \uD83D\uDC4D \uD83D\uDCE6 \uD83C\uDF89'); } - return process.stdout.write(`\nCheck it out at https://web.pulsar-edit.dev/packages/${pack.name}\n`); + process.stdout.write(`\nCheck it out at https://web.pulsar-edit.dev/packages/${pack.name}\n`); } loadMetadata() { @@ -294,10 +293,10 @@ have published it.\ } } - saveMetadata(pack, callback) { + saveMetadata(pack) { const metadataPath = path.resolve('package.json'); const metadataJson = JSON.stringify(pack, null, 2); - return fs.writeFile(metadataPath, `${metadataJson}\n`, callback); + fs.writeFileSync(metadataPath, `${metadataJson}\n`); } loadRepository() { @@ -324,53 +323,53 @@ have published it.\ } // Rename package if necessary - renamePackage(pack, name, callback) { - if ((name != null ? name.length : undefined) > 0) { - if (pack.name === name) { return callback('The new package name must be different than the name in the package.json file'); } + async renamePackage(pack, name) { + if (name?.length <= 0) { + // Just fall through if the name is empty + return; // error or return value? + } + if (pack.name === name) { throw 'The new package name must be different than the name in the package.json file'; } - const message = `Renaming ${pack.name} to ${name} `; - process.stdout.write(message); - return this.setPackageName(pack, name, error => { - if (error != null) { + const message = `Renaming ${pack.name} to ${name} `; + process.stdout.write(message); + try { + this.setPackageName(pack, name); + } catch (error) { + this.logFailure(); + throw error; + } + + const gitCommand = await config.getSetting('git') ?? 'git'; + return new Promise((resolve, reject) => { + this.spawn(gitCommand, ['add', 'package.json'], (code, stderr, stdout) => { + stderr ??= ''; + stdout ??= ''; + if (code !== 0) { this.logFailure(); - return callback(error); + const addOutput = `${stdout}\n${stderr}`.trim(); + return void reject(`\`git add package.json\` failed: ${addOutput}`); } - return config.getSetting('git', gitCommand => { - if (gitCommand == null) { gitCommand = 'git'; } - return this.spawn(gitCommand, ['add', 'package.json'], (code, stderr, stdout) => { - if (stderr == null) { stderr = ''; } - if (stdout == null) { stdout = ''; } - if (code !== 0) { - this.logFailure(); - const addOutput = `${stdout}\n${stderr}`.trim(); - return callback(`\`git add package.json\` failed: ${addOutput}`); - } - - return this.spawn(gitCommand, ['commit', '-m', message], (code, stderr, stdout) => { - if (stderr == null) { stderr = ''; } - if (stdout == null) { stdout = ''; } - if (code === 0) { - this.logSuccess(); - return callback(); - } else { - this.logFailure(); - const commitOutput = `${stdout}\n${stderr}`.trim(); - return callback(`Failed to commit package.json: ${commitOutput}`); - } - }); - }); + this.spawn(gitCommand, ['commit', '-m', message], (code, stderr, stdout) => { + stderr ??= ''; + stdout ??= ''; + if (code === 0) { + this.logFailure(); + const commitOutput = `${stdout}\n${stderr}`.trim(); + reject(`Failed to commit package.json: ${commitOutput}`); + return; + } + + this.logSuccess(); + resolve(); }); }); - } else { - // Just fall through if the name is empty - return callback(); - } + }); } - setPackageName(pack, name, callback) { + setPackageName(pack, name) { pack.name = name; - return this.saveMetadata(pack, callback); + this.saveMetadata(pack); } validateSemverRanges(pack) { @@ -411,83 +410,79 @@ have published it.\ } // Run the publish command with the given options - run(options) { - let error, pack; - const {callback} = options; + async run(options) { + let pack; options = this.parseOptions(options.commandArgs); let {tag, rename} = options.argv; let [version] = options.argv._; try { pack = this.loadMetadata(); - } catch (error1) { - error = error1; - return callback(error); + } catch (error) { + return error; } try { this.validateSemverRanges(pack); - } catch (error2) { - error = error2; - return callback(error); + } catch (error) { + return error; } try { this.loadRepository(); - } catch (error3) { - error = error3; - return callback(error); + } catch (error) { + return error; } - if (((version != null ? version.length : undefined) > 0) || ((rename != null ? rename.length : undefined) > 0)) { + if ((version?.length > 0) || (rename?.length > 0)) { let originalName; - if (!((version != null ? version.length : undefined) > 0)) { version = 'patch'; } - if ((rename != null ? rename.length : undefined) > 0) { originalName = pack.name; } - - this.registerPackage(pack, (error, firstTimePublishing) => { - if (error != null) { return callback(error); } - - this.renamePackage(pack, rename, error => { - if (error != null) { return callback(error); } - - this.versionPackage(version, (error, tag) => { - if (error != null) { return callback(error); } - - this.pushVersion(tag, pack, error => { - if (error != null) { return callback(error); } - - this.waitForTagToBeAvailable(pack, tag, () => { - - if (originalName != null) { - // If we're renaming a package, we have to hit the API with the - // current name, not the new one, or it will 404. - rename = pack.name; - pack.name = originalName; - } - this.publishPackage(pack, tag, {rename}, error => { - if (firstTimePublishing && (error == null)) { - this.logFirstTimePublishMessage(pack); - } - return callback(error); - }); - }); - }); - }); - }); - }); - } else if ((tag != null ? tag.length : undefined) > 0) { - this.registerPackage(pack, (error, firstTimePublishing) => { - if (error != null) { return callback(error); } + if (version?.length <= 0) { version = 'patch'; } + if (rename?.length > 0) { originalName = pack.name; } - this.publishPackage(pack, tag, error => { - if (firstTimePublishing && (error == null)) { - this.logFirstTimePublishMessage(pack); - } - return callback(error); - }); - }); + let firstTimePublishing; + try { + firstTimePublishing = await this.registerPackage(pack); + await this.renamePackage(pack, rename); + const tag = await this.versionPackage(version); + await this.pushVersion(tag, pack); + } catch (error) { + return error; + } + + await this.waitForTagToBeAvailable(pack, tag); + if (originalName != null) { + // If we're renaming a package, we have to hit the API with the + // current name, not the new one, or it will 404. + rename = pack.name; + pack.name = originalName; + } + + try { + await this.publishPackage(pack, tag, {rename}); + } catch (error) { + if (firstTimePublishing) { + this.logFirstTimePublishMessage(pack); + } + return error; + } + } else if (tag?.length > 0) { + let firstTimePublishing; + try { + firstTimePublishing = await this.registerPackage(pack); + } catch (error) { + return error; + } + + try { + await this.publishPackage(pack, tag); + } catch (error) { + if (firstTimePublishing) { + this.logFirstTimePublishMessage(pack); + } + return error; + } } else { - return callback('A version, tag, or new package name is required'); + return 'A version, tag, or new package name is required'; } } } diff --git a/src/rebuild-module-cache.js b/src/rebuild-module-cache.js index bb79d660..abe3ce4a 100644 --- a/src/rebuild-module-cache.js +++ b/src/rebuild-module-cache.js @@ -33,49 +33,48 @@ This command skips all linked packages.\ return options.alias('h', 'help').describe('help', 'Print this usage message'); } - getResourcePath(callback) { - if (this.resourcePath) { - return process.nextTick(() => callback(this.resourcePath)); - } else { - return config.getResourcePath(resourcePath => { this.resourcePath = resourcePath; return callback(this.resourcePath); }); + async getResourcePath() { + if (!this.resourcePath) { + const resourcePath = await config.getResourcePath(); + this.resourcePath = resourcePath; + return this.resourcePath; } - } - - rebuild(packageDirectory, callback) { - return this.getResourcePath(resourcePath => { - try { - if (this.moduleCache == null) { this.moduleCache = require(path.join(resourcePath, 'src', 'module-cache')); } - this.moduleCache.create(packageDirectory); - } catch (error) { - return callback(error); - } - return callback(); - }); + return new Promise((resolve, _reject) => + void process.nextTick(() => resolve(this.resourcePath)) + ); } - run(options) { - const {callback} = options; + async rebuild(packageDirectory) { + const resourcePath = await this.getResourcePath(); + this.moduleCache ??= require(path.join(resourcePath, 'src', 'module-cache')); + this.moduleCache.create(packageDirectory); + } + async run(_options) { const commands = []; fs.list(this.atomPackagesDirectory).forEach(packageName => { const packageDirectory = path.join(this.atomPackagesDirectory, packageName); if (fs.isSymbolicLinkSync(packageDirectory)) { return; } if (!fs.isFileSync(path.join(packageDirectory, 'package.json'))) { return; } - return commands.push(callback => { + commands.push(async () => { process.stdout.write(`Rebuilding ${packageName} module cache `); - return this.rebuild(packageDirectory, error => { - if (error != null) { - this.logFailure(); - } else { - this.logSuccess(); - } - return callback(error); - }); + try { + await this.rebuild(packageDirectory); + this.logSuccess(); + } catch (error) { + this.logFailure(); + console.error(error); + throw error; + } }); }); - return async.waterfall(commands, callback); + try { + await async.waterfall(commands); + } catch (error) { + return error; //errors as return values atm + } } } diff --git a/src/rebuild.js b/src/rebuild.js index f32f308a..dbac1cf8 100644 --- a/src/rebuild.js +++ b/src/rebuild.js @@ -7,7 +7,6 @@ const yargs = require('yargs'); const config = require('./apm'); const Command = require('./command'); const fs = require('./fs'); -const Install = require('./install'); module.exports = class Rebuild extends Command { @@ -35,7 +34,7 @@ All the modules will be rebuilt if no module names are specified.\ return options.alias('h', 'help').describe('help', 'Print this usage message'); } - forkNpmRebuild(options, callback) { + forkNpmRebuild(options) { process.stdout.write('Rebuilding modules '); const rebuildArgs = ['--globalconfig', config.getGlobalConfigPath(), '--userconfig', config.getUserConfigPath(), 'rebuild']; @@ -47,27 +46,30 @@ All the modules will be rebuilt if no module names are specified.\ const env = _.extend({}, process.env, {HOME: this.atomNodeDirectory, RUSTUP_HOME: config.getRustupHomeDirPath()}); this.addBuildEnvVars(env); - return this.fork(this.atomNpmPath, rebuildArgs, {env}, callback); + return new Promise((resolve, reject) => + void this.fork(this.atomNpmPath, rebuildArgs, {env}, (code, stderr) => { + if (code !== 0) { + reject(stderr ?? ''); + return; + } + + resolve(); + }) + ); } - run(options) { - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); - config.loadNpm((error, npm) => { - this.npm = npm; - this.loadInstalledAtomMetadata(() => { - this.forkNpmRebuild(options, (code, stderr) => { - if (stderr == null) { stderr = ''; } - if (code === 0) { - this.logSuccess(); - return callback(); - } else { - this.logFailure(); - return callback(stderr); - } - }); - }); - }); + const npm = await config.loadNpm(); + this.npm = npm; + try { + await this.loadInstalledAtomMetadata(); + await this.forkNpmRebuild(options); + this.logSuccess(); + } catch (error) { + this.logFailure(); + return stderr; //errors as return values atm + } } } diff --git a/src/request.js b/src/request.js index 09955af9..c4066236 100644 --- a/src/request.js +++ b/src/request.js @@ -10,15 +10,18 @@ const config = require("./apm.js"); // So we have to specifically say these are valid, or otherwise redo a lot of our logic const OK_STATUS_CODES = [200, 201, 204, 404]; -const loadNpm = function(callback) { +function loadNpm() { const npmOptions = { userconfig: config.getUserConfigPath(), globalconfig: config.getGlobalConfigPath() }; - return npm.load(npmOptions, callback); + return new Promise((resolve, reject) => + void npm.load(npmOptions, (error, value) => void(error != null ? reject(error) : resolve(value))) + ); }; -const configureRequest = (requestOptions, callback) => loadNpm(function() { +async function configureRequest(requestOptions){ + await loadNpm(); requestOptions.proxy ??= npm.config.get("https-proxy") ?? npm.config.get("proxy") ?? process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY; requestOptions.strictSSL ??= npm.config.get("strict-ssl") ?? true; @@ -30,77 +33,110 @@ const configureRequest = (requestOptions, callback) => loadNpm(function() { } requestOptions.qs ??= {}; - - return callback(); -}); +} module.exports = { get(opts, callback) { - configureRequest(opts, () => { - let retryCount = opts.retries ?? 0; + configureRequest(opts).then(async () => { + const retryCount = opts.retries ?? 0; if (typeof opts.strictSSL === "boolean") { - superagent.get(opts.url).proxy(opts.proxy).set(opts.headers).query(opts.qs).retry(retryCount).disableTLSCerts().ok((res) => OK_STATUS_CODES.includes(res.status)).then((res) => { - return callback(null, res, res.body); - }).catch((err) => { - return callback(err, null, null); - }); - } else { - superagent.get(opts.url).proxy(opts.proxy).set(opts.headers).query(opts.qs).retry(retryCount).ok((res) => OK_STATUS_CODES.includes(res.status)).then((res) => { - return callback(null, res, res.body); - }).catch((err) => { - return callback(err, null, null); - }); + try { + const res = await superagent + .get(opts.url) + .proxy(opts.proxy) + .set(opts.headers) + .query(opts.qs) + .retry(retryCount) + .disableTLSCerts() + .ok((res) => OK_STATUS_CODES.includes(res.status)); + return void callback(null, res, res.body); + } catch (error) { + return void callback(error, null, null); + } + } + + try { + const res = await superagent + .get(opts.url) + .proxy(opts.proxy) + .set(opts.headers) + .query(opts.qs) + .retry(retryCount) + .ok((res) => OK_STATUS_CODES.includes(res.status)); + return void callback(null, res, res.body); + } catch (error) { + return void callback(error, null, null); } }); }, del(opts, callback) { - configureRequest(opts, () => { + configureRequest(opts).then(async () => { if (typeof opts.strictSSL === "boolean") { - superagent.delete(opts.url).proxy(opts.proxy).set(opts.headers).query(opts.qs).disableTLSCerts().ok((res) => OK_STATUS_CODES.includes(res.status)).then((res) => { - return callback(null, res, res.body); - }).catch((err) => { - return callback(err, null, null); - }); - } else { - superagent.delete(opts.url).proxy(opts.proxy).set(opts.headers).query(opts.qs).ok((res) => OK_STATUS_CODES.includes(res.status)).then((res) => { - return callback(null, res, res.body); - }).catch((err) => { - return callback(err, null, null); - }); + try { + const res = await superagent + .delete(opts.url) + .proxy(opts.proxy) + .set(opts.headers) + .query(opts.qs) + .disableTLSCerts() + .ok((res) => OK_STATUS_CODES.includes(res.status)); + return void callback(null, res, res.body); + } catch (error) { + return void callback(error, null, null); + } + } + + try { + const res = await superagent + .delete(opts.url) + .proxy(opts.proxy) + .set(opts.headers) + .query(opts.qs) + .ok((res) => OK_STATUS_CODES.includes(res.status)); + return void callback(null, res, res.body); + } catch (error) { + return void callback(error, null, null); } }); }, post(opts, callback) { - configureRequest(opts, () => { + configureRequest(opts).then(async () => { if (typeof opts.strictSSL === "boolean") { - superagent.post(opts.url).proxy(opts.proxy).set(opts.headers).query(opts.qs).disableTLSCerts().ok((res) => OK_STATUS_CODES.includes(res.status)).then((res) => { - return callback(null, res, res.body); - }).catch((err) => { - return callback(err, null, null); - }); - } else { - superagent.post(opts.url).proxy(opts.proxy).set(opts.headers).query(opts.qs).ok((res) => OK_STATUS_CODES.includes(res.status)).then((res) => { - return callback(null, res, res.body); - }).catch((err) => { - return callback(err, null, null); - }); + try { + const res = await superagent + .post(opts.url) + .proxy(opts.proxy) + .set(opts.headers) + .query(opts.qs) + .disableTLSCerts() + .ok((res) => OK_STATUS_CODES.includes(res.status)); + return void callback(null, res, res.body); + } catch (error) { + return void callback(error, null, null); + } } - }); - }, - createReadStream(opts) { - configureRequest(opts, () => { - if (typeof opts.strictSSL === "boolean") { - return superagent.get(opts.url).proxy(opts.proxy).set(opts.headers).query(opts.qs).disableTLSCerts().ok((res) => OK_STATUS_CODES.includes(res.status)); - } else { - return superagent.get(opts.url).proxy(opts.proxy).set(opts.headers).query(opts.qs).ok((res) => OK_STATUS_CODES.includes(res.status)); + try { + const res = await superagent.post(opts.url).proxy(opts.proxy).set(opts.headers).query(opts.qs).ok((res) => OK_STATUS_CODES.includes(res.status)); + return void callback(null, res, res.body); + } catch (error) { + return void callback(error, null, null); } }); }, + async createReadStream(opts) { + await configureRequest(opts); + if (typeof opts.strictSSL === "boolean") { + return superagent.get(opts.url).proxy(opts.proxy).set(opts.headers).query(opts.qs).disableTLSCerts().ok((res) => OK_STATUS_CODES.includes(res.status)); + } else { + return superagent.get(opts.url).proxy(opts.proxy).set(opts.headers).query(opts.qs).ok((res) => OK_STATUS_CODES.includes(res.status)); + } + }, + getErrorMessage(body, err) { if (err?.status === 503) { return `${err.response.req.host} is temporarily unavailable, please try again later.`; diff --git a/src/search.js b/src/search.js index 6186b3f5..c6fb9e7a 100644 --- a/src/search.js +++ b/src/search.js @@ -28,7 +28,7 @@ Search for packages/themes.\ return options.boolean('themes').describe('themes', 'Search only themes').alias('t', 'themes'); } - searchPackages(query, opts, callback) { + searchPackages(query, opts) { const qs = {q: query}; @@ -44,30 +44,31 @@ Search for packages/themes.\ json: true }; - return request.get(requestSettings, function(error, response, body) { - if (body == null) { body = {}; } - if (error != null) { - return callback(error); - } else if (response.statusCode === 200) { - let packages = body.filter(pack => (pack.releases != null ? pack.releases.latest : undefined) != null); - packages = packages.map(({readme, metadata, downloads, stargazers_count}) => _.extend({}, metadata, {readme, downloads, stargazers_count})); - packages = packages.filter(({name, version}) => !isDeprecatedPackage(name, version)); - return callback(null, packages); - } else { + return new Promise((resolve, reject) => { + request.get(requestSettings, function(error, response, body) { + body ??= {}; + if (error != null) { + return void reject(error); + } + if (response.statusCode === 200) { + let packages = body.filter(pack => (pack.releases != null ? pack.releases.latest : undefined) != null); + packages = packages.map(({readme, metadata, downloads, stargazers_count}) => _.extend({}, metadata, {readme, downloads, stargazers_count})); + packages = packages.filter(({name, version}) => !isDeprecatedPackage(name, version)); + return void resolve(packages); + } + const message = request.getErrorMessage(body, error); - return callback(`Searching packages failed: ${message}`); - } + reject(`Searching packages failed: ${message}`); + }); }); } - run(options) { - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); const [query] = options.argv._; if (!query) { - callback("Missing required search query"); - return; + return "Missing required search query"; // error as return value on this layer atm } const searchOptions = { @@ -75,31 +76,29 @@ Search for packages/themes.\ themes: options.argv.themes }; - this.searchPackages(query, searchOptions, function(error, packages) { - if (error != null) { - callback(error); - return; - } - - if (options.argv.json) { - console.log(JSON.stringify(packages)); - } else { - const heading = `Search Results For '${query}'`.cyan; - console.log(`${heading} (${packages.length})`); - - tree(packages, function({name, version, description, downloads, stargazers_count}) { - let label = name.yellow; - if (description) { label += ` ${description.replace(/\s+/g, ' ')}`; } - if ((downloads >= 0) && (stargazers_count >= 0)) { label += ` (${_.pluralize(downloads, 'download')}, ${_.pluralize(stargazers_count, 'star')})`.grey; } - return label; - }); - - console.log(); - console.log(`Use \`ppm install\` to install them or visit ${'https://web.pulsar-edit.dev'.underline} to read more about them.`); - console.log(); - } - - return callback(); - }); + let packages; + try { + packages = await this.searchPackages(query, searchOptions); + } catch (error) { + return error; // error as return value on this layer atm + } + + if (options.argv.json) { + console.log(JSON.stringify(packages)); + } else { + const heading = `Search Results For '${query}'`.cyan; + console.log(`${heading} (${packages.length})`); + + tree(packages, ({name, description, downloads, stargazers_count}) => { + let label = name.yellow; + if (description) { label += ` ${description.replace(/\s+/g, ' ')}`; } + if ((downloads >= 0) && (stargazers_count >= 0)) { label += ` (${_.pluralize(downloads, 'download')}, ${_.pluralize(stargazers_count, 'star')})`.grey; } + return label; + }); + + console.log(); + console.log(`Use \`ppm install\` to install them or visit ${'https://web.pulsar-edit.dev'.underline} to read more about them.`); + console.log(); + } } } diff --git a/src/star.js b/src/star.js index 69cb14d3..11fd437b 100644 --- a/src/star.js +++ b/src/star.js @@ -32,8 +32,8 @@ Run \`ppm stars\` to see all your starred packages.\ return options.boolean('installed').describe('installed', 'Star all packages in ~/.pulsar/packages'); } - starPackage(packageName, param, callback) { - if (param == null) { param = {}; } + starPackage(packageName, param) { + param ??= {}; const {ignoreUnpublishedPackages, token} = param; if (process.platform === 'darwin') { process.stdout.write('\u2B50 '); } process.stdout.write(`Starring ${packageName} `); @@ -44,22 +44,27 @@ Run \`ppm stars\` to see all your starred packages.\ authorization: token } }; - request.post(requestSettings, (error, response, body) => { - if (body == null) { body = {}; } - if (error != null) { - this.logFailure(); - return callback(error); - } else if ((response.statusCode === 404) && ignoreUnpublishedPackages) { - process.stdout.write('skipped (not published)\n'.yellow); - return callback(); - } else if (response.statusCode !== 200) { - this.logFailure(); - const message = request.getErrorMessage(body, error); - return callback(`Starring package failed: ${message}`); - } else { + + return new Promise((resolve, reject) => { + request.post(requestSettings, (error, response, body) => { + body ??= {}; + if (error != null) { + this.logFailure(); + return void reject(error); + } + if ((response.statusCode === 404) && ignoreUnpublishedPackages) { + process.stdout.write('skipped (not published)\n'.yellow); + return void reject(); + } + if (response.statusCode !== 200) { + this.logFailure(); + const message = request.getErrorMessage(body, error); + return void reject(`Starring package failed: ${message}`); + } + this.logSuccess(); - return callback(); - } + resolve(); + }); }); } @@ -83,37 +88,34 @@ Run \`ppm stars\` to see all your starred packages.\ return _.uniq(installedPackages); } - run(options) { + async run(options) { let packageNames; - const {callback} = options; options = this.parseOptions(options.commandArgs); if (options.argv.installed) { packageNames = this.getInstalledPackageNames(); if (packageNames.length === 0) { - callback(); return; } } else { packageNames = this.packageNamesFromArgv(options.argv); if (packageNames.length === 0) { - callback("Please specify a package name to star"); - return; + return "Please specify a package name to star"; // error as return value for now } } - Login.getTokenOrLogin((error, token) => { - if (error != null) { return callback(error); } - + try { + const token = await Login.getTokenOrLogin(); const starOptions = { ignoreUnpublishedPackages: options.argv.installed, token }; - const commands = packageNames.map(packageName => { - return callback => this.starPackage(packageName, starOptions, callback); + return async () => await this.starPackage(packageName, starOptions); }); - return async.waterfall(commands, callback); - }); + return await async.waterfall(commands); + } catch (error) { + return error; // error as return value + } } } diff --git a/src/stars.js b/src/stars.js index e7035fb2..eb0f7bd6 100644 --- a/src/stars.js +++ b/src/stars.js @@ -32,64 +32,58 @@ List or install starred Atom packages and themes.\ return options.boolean('json').describe('json', 'Output packages as a JSON array'); } - getStarredPackages(user, atomVersion, callback) { + async getStarredPackages(user, atomVersion) { const requestSettings = {json: true}; if (atomVersion) { requestSettings.qs = {engine: atomVersion}; } if (user) { requestSettings.url = `${config.getAtomApiUrl()}/users/${user}/stars`; - return this.requestStarredPackages(requestSettings, callback); - } else { - requestSettings.url = `${config.getAtomApiUrl()}/stars`; - return Login.getTokenOrLogin((error, token) => { - if (error != null) { return callback(error); } - - requestSettings.headers = {authorization: token}; - return this.requestStarredPackages(requestSettings, callback); - }); + return this.requestStarredPackages(requestSettings); } + + requestSettings.url = `${config.getAtomApiUrl()}/stars`; + const token = await Login.getTokenOrLogin(); + requestSettings.headers = {authorization: token}; + return this.requestStarredPackages(requestSettings); } - requestStarredPackages(requestSettings, callback) { - return request.get(requestSettings, function(error, response, body) { - if (body == null) { body = []; } - if (error != null) { - return callback(error); - } else if (response.statusCode === 200) { - let packages = body.filter(pack => pack?.releases?.latest != null); - packages = packages.map(({readme, metadata, downloads, stargazers_count}) => _.extend({}, metadata, {readme, downloads, stargazers_count})); - packages = _.sortBy(packages, 'name'); - return callback(null, packages); - } else { + requestStarredPackages(requestSettings) { + return new Promise((resolve, reject) => { + request.get(requestSettings, (error, response, body) => { + body ??= []; + if (error != null) { + return void reject(error); + } + if (response.statusCode === 200) { + let packages = body.filter(pack => pack?.releases?.latest != null); + packages = packages.map(({readme, metadata, downloads, stargazers_count}) => _.extend({}, metadata, {readme, downloads, stargazers_count})); + packages = _.sortBy(packages, 'name'); + return void resolve(packages); + } + const message = request.getErrorMessage(body, error); - return callback(`Requesting packages failed: ${message}`); - } + reject(`Requesting packages failed: ${message}`); + }); }); } - installPackages(packages, callback) { - if (packages.length === 0) { return callback(); } + async installPackages(packages) { + if (packages.length === 0) { return; } const commandArgs = packages.map(({name}) => name); - return new Install().run({commandArgs, callback}); + return new Install().run({commandArgs}); } - logPackagesAsJson(packages, callback) { + logPackagesAsJson(packages) { console.log(JSON.stringify(packages)); - return callback(); } - logPackagesAsText(user, packagesAreThemes, packages, callback) { - let label; - const userLabel = user != null ? user : 'you'; - if (packagesAreThemes) { - label = `Themes starred by ${userLabel}`; - } else { - label = `Packages starred by ${userLabel}`; - } + logPackagesAsText(user, packagesAreThemes, packages) { + const userLabel = user ?? 'you'; + let label = `${packagesAreThemes ? 'Themes' : 'Packages'} starred by ${userLabel}`; console.log(`${label.cyan} (${packages.length})`); - tree(packages, function({name, version, description, downloads, stargazers_count}) { + tree(packages, ({name, description, downloads, stargazers_count}) => { label = name.yellow; if (process.platform === 'darwin') { label = `\u2B50 ${label}`; } if (description) { label += ` ${description.replace(/\s+/g, ' ')}`; } @@ -100,28 +94,29 @@ List or install starred Atom packages and themes.\ console.log(); console.log(`Use \`ppm stars --install\` to install them all or visit ${'https://web.pulsar-edit.dev'.underline} to read more about them.`); console.log(); - return callback(); } - run(options) { - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); const user = options.argv.user?.toString().trim(); - return this.getStarredPackages(user, options.argv.compatible, (error, packages) => { - if (error != null) { return callback(error); } + let packages; + try { + packages = await this.getStarredPackages(user, options.argv.compatible); + } catch (error) { + return error; //errors as values for now + } + if (options.argv.themes) { + packages = packages.filter(({theme}) => theme); + } - if (options.argv.themes) { - packages = packages.filter(({theme}) => theme); - } + if (options.argv.install) { + return await this.installPackages(packages); + } + if (options.argv.json) { + return void this.logPackagesAsJson(packages); + } - if (options.argv.install) { - return this.installPackages(packages, callback); - } else if (options.argv.json) { - return this.logPackagesAsJson(packages, callback); - } else { - return this.logPackagesAsText(user, options.argv.themes, packages, callback); - } - }); + this.logPackagesAsText(user, options.argv.themes, packages); } } diff --git a/src/test.js b/src/test.js index 8b180e22..938c5082 100644 --- a/src/test.js +++ b/src/test.js @@ -28,7 +28,6 @@ to the current working directory).\ run(options) { let atomCommand; - const {callback} = options; options = this.parseOptions(options.commandArgs); const {env} = process; @@ -41,38 +40,42 @@ to the current working directory).\ const packagePath = process.cwd(); const testArgs = ['--dev', '--test', path.join(packagePath, 'spec')]; - if (process.platform === 'win32') { - const logFile = temp.openSync({suffix: '.log', prefix: `${path.basename(packagePath)}-`}); - fs.closeSync(logFile.fd); - const logFilePath = logFile.path; - testArgs.push(`--log-file=${logFilePath}`); + return new Promise((resolve, _reject) => { + if (process.platform === 'win32') { + const logFile = temp.openSync({suffix: '.log', prefix: `${path.basename(packagePath)}-`}); + fs.closeSync(logFile.fd); + const logFilePath = logFile.path; + testArgs.push(`--log-file=${logFilePath}`); + + this.spawn(atomCommand, testArgs, code => { + try { + const loggedOutput = fs.readFileSync(logFilePath, 'utf8'); + if (loggedOutput) { process.stdout.write(`${loggedOutput}\n`); } + } catch (error) {} + + if (code === 0) { + process.stdout.write('Tests passed\n'.green); + return void resolve(); + } + if (code?.message) { + return void resolve(`Error spawning Atom: ${code.message}`); // errors as return value atm + } + + resolve('Tests failed'); // errors as return value atm + }); + } else { + this.spawn(atomCommand, testArgs, {env, streaming: true}, code => { + if (code === 0) { + process.stdout.write('Tests passed\n'.green); + return void resolve(); + } + if (code?.message) { + return void resolve(`Error spawning ${atomCommand}: ${code.message}`); // errors as return value + } - this.spawn(atomCommand, testArgs, function(code) { - try { - const loggedOutput = fs.readFileSync(logFilePath, 'utf8'); - if (loggedOutput) { process.stdout.write(`${loggedOutput}\n`); } - } catch (error) {} - - if (code === 0) { - process.stdout.write('Tests passed\n'.green); - return callback(); - } else if ((code != null ? code.message : undefined)) { - return callback(`Error spawning Atom: ${code.message}`); - } else { - return callback('Tests failed'); - } - }); - } else { - this.spawn(atomCommand, testArgs, {env, streaming: true}, function(code) { - if (code === 0) { - process.stdout.write('Tests passed\n'.green); - return callback(); - } else if ((code != null ? code.message : undefined)) { - return callback(`Error spawning ${atomCommand}: ${code.message}`); - } else { - return callback('Tests failed'); - } - }); - } + resolve('Tests failed'); // errors as return value + }); + } + }); } } diff --git a/src/text-mate-theme.js b/src/text-mate-theme.js index 733bfe8c..10ebcb9d 100644 --- a/src/text-mate-theme.js +++ b/src/text-mate-theme.js @@ -5,16 +5,21 @@ const {ScopeSelector, ready} = require('second-mate'); module.exports = class TextMateTheme { + static async createInstance(contents) { + const newInstance = new TextMateTheme(contents); + await newInstance.buildRulesets(); + return newInstance; + } + constructor(contents) { this.contents = contents; this.rulesets = []; - this.buildRulesets(); } - buildRulesets() { + async buildRulesets() { let variableSettings; let { settings } = plist.parseStringSync(this.contents) ?? {}; - if (settings == null) { settings = []; } + settings ??= []; for (let setting of settings) { const {scope, name} = setting.settings; @@ -46,7 +51,7 @@ The theme being converted must contain a settings array with all of the followin this.buildSyntaxVariables(variableSettings); this.buildGlobalSettingsRulesets(variableSettings); - this.buildScopeSelectorRulesets(settings); + await this.buildScopeSelectorRulesets(settings); } getStylesheet() { @@ -152,25 +157,21 @@ atom-text-editor.is-focused .line.cursor-line`, }); } - buildScopeSelectorRulesets(scopeSelectorSettings) { - return (() => { - const result = []; - for (let {name, scope, settings} of Array.from(scopeSelectorSettings)) { - if (!scope) { continue; } - result.push(this.rulesets.push({ - comment: name, - selector: this.translateScopeSelector(scope), - properties: this.translateScopeSelectorSettings(settings) - })); - } - return result; - })(); + async buildScopeSelectorRulesets(scopeSelectorSettings) { + const result = []; + for (let {name, scope, settings} of Array.from(scopeSelectorSettings)) { + if (!scope) { continue; } + result.push(this.rulesets.push({ + comment: name, + selector: await this.translateScopeSelector(scope), + properties: this.translateScopeSelectorSettings(settings) + })); + } + return result; } - translateScopeSelector(textmateScopeSelector) { - (async () => { - await ready; - })(); + async translateScopeSelector(textmateScopeSelector) { + await ready; return new ScopeSelector(textmateScopeSelector).toCssSyntaxSelector(); } @@ -193,28 +194,24 @@ atom-text-editor.is-focused .line.cursor-line`, textmateColor = `#${textmateColor.replace(/^#+/, '')}`; if (textmateColor.length <= 7) { return textmateColor; - } else { - const r = this.parseHexColor(textmateColor.slice(1, 3)); - const g = this.parseHexColor(textmateColor.slice(3, 5)); - const b = this.parseHexColor(textmateColor.slice(5, 7)); - let a = this.parseHexColor(textmateColor.slice(7, 9)); - a = Math.round((a / 255.0) * 100) / 100; - - return `rgba(${r}, ${g}, ${b}, ${a})`; } + + const r = this.parseHexColor(textmateColor.slice(1, 3)); + const g = this.parseHexColor(textmateColor.slice(3, 5)); + const b = this.parseHexColor(textmateColor.slice(5, 7)); + let a = this.parseHexColor(textmateColor.slice(7, 9)); + a = Math.round((a / 255.0) * 100) / 100; + + return `rgba(${r}, ${g}, ${b}, ${a})`; } parseHexColor(color) { const parsed = Math.min(255, Math.max(0, parseInt(color, 16))); - if (isNaN(parsed)) { - return 0; - } else { - return parsed; - } + return isNaN(parsed) ? 0 : parsed; } }; -var SyntaxVariablesTemplate = `\ +const SyntaxVariablesTemplate = `\ // This defines all syntax variables that syntax themes must implement when they // include a syntax-variables.less file. diff --git a/src/theme-converter.js b/src/theme-converter.js index 353a2db4..33e508c1 100644 --- a/src/theme-converter.js +++ b/src/theme-converter.js @@ -13,46 +13,39 @@ class ThemeConverter { this.destinationPath = path.resolve(destinationPath); } - readTheme(callback) { + async readTheme() { const {protocol} = url.parse(this.sourcePath); if ((protocol === 'http:') || (protocol === 'https:')) { const requestOptions = {url: this.sourcePath}; - request.get(requestOptions, (error, response, body) => { - if (error != null) { - if (error?.code === 'ENOTFOUND') { - error = `Could not resolve URL: ${this.sourcePath}`; + return new Promise((resolve, reject) => { + request.get(requestOptions, (error, response, body) => { + if (error != null) { + if (error?.code === 'ENOTFOUND') { + error = `Could not resolve URL: ${this.sourcePath}`; + } + return void reject(error); } - return callback(error); - } else if (response.statusCode !== 200) { - return callback(`Request to ${this.sourcePath} failed (${response.headers.status})`); - } else { - return callback(null, body); - } + if (response.statusCode !== 200) { + return void reject(`Request to ${this.sourcePath} failed (${response.headers.status})`); + } + + resolve(body); + }); }); - } else { - const sourcePath = path.resolve(this.sourcePath); - if (fs.isFileSync(sourcePath)) { - return callback(null, fs.readFileSync(sourcePath, 'utf8')); - } else { - return callback(`TextMate theme file not found: ${sourcePath}`); - } } - } - convert(callback) { - this.readTheme((error, themeContents) => { - let theme; - if (error != null) { return callback(error); } + const sourcePath = path.resolve(this.sourcePath); + if (!fs.isFileSync(sourcePath)) { + throw `TextMate theme file not found: ${sourcePath}`; + } - try { - theme = new TextMateTheme(themeContents); - } catch (error) { - return callback(error); - } + return fs.readFileSync(sourcePath, 'utf8'); + } + async convert() { + const themeContents = await this.readTheme(); + const theme = await TextMateTheme.createInstance(themeContents); fs.writeFileSync(path.join(this.destinationPath, 'styles', 'base.less'), theme.getStylesheet()); fs.writeFileSync(path.join(this.destinationPath, 'styles', 'syntax-variables.less'), theme.getSyntaxVariables()); - return callback(); - }); } }; diff --git a/src/tree.js b/src/tree.js index 44ce7b23..5fbee28d 100644 --- a/src/tree.js +++ b/src/tree.js @@ -1,31 +1,22 @@ const _ = require('underscore-plus'); -module.exports = function(items, options, callback) { - if (options == null) { options = {}; } +module.exports = (items, options, callback) => { + options ??= {}; if (_.isFunction(options)) { callback = options; options = {}; } - if (callback == null) { callback = item => item; } + callback ??= item => item; if (items.length === 0) { - const emptyMessage = options.emptyMessage != null ? options.emptyMessage : '(empty)'; + const emptyMessage = options.emptyMessage ?? '(empty)'; console.log(`\u2514\u2500\u2500 ${emptyMessage}`); - } else { - return (() => { - const result = []; - for (let index = 0; index < items.length; index++) { - var itemLine; - const item = items[index]; - if (index === (items.length - 1)) { - itemLine = '\u2514\u2500\u2500 '; - } else { - itemLine = '\u251C\u2500\u2500 '; - } - result.push(console.log(`${itemLine}${callback(item)}`)); - } - return result; - })(); + return; } + + items.forEach((item, index) => { + const itemLine = index === (items.length - 1) ? '\u2514\u2500\u2500 ' : '\u251C\u2500\u2500 '; + console.log(`${itemLine}${callback(item)}`) + }); }; diff --git a/src/uninstall.js b/src/uninstall.js index d555c41f..9d78c368 100644 --- a/src/uninstall.js +++ b/src/uninstall.js @@ -37,12 +37,11 @@ Delete the installed package(s) from the ~/.pulsar/packages directory.\ } } - registerUninstall({packageName, packageVersion}, callback) { - if (!packageVersion) { return callback(); } - - return auth.getToken(function(error, token) { - if (!token) { return callback(); } + async registerUninstall({packageName, packageVersion}) { + if (!packageVersion) { return; } + try { + const token = await auth.getToken(); const requestOptions = { url: `${config.getAtomPackagesUrl()}/${packageName}/versions/${packageVersion}/events/uninstall`, json: true, @@ -50,19 +49,18 @@ Delete the installed package(s) from the ~/.pulsar/packages directory.\ authorization: token } }; - - return request.post(requestOptions, (error, response, body) => callback()); - }); + return new Promise((resolve, _reject) => void request.post(requestOptions, (_error, _response, _body) => resolve())); + } catch (error) { + return error; // error as value here + } } - run(options) { - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); const packageNames = this.packageNamesFromArgv(options.argv); if (packageNames.length === 0) { - callback("Please specify a package name to uninstall"); - return; + return "Please specify a package name to uninstall"; // error as return value atm } const packagesDirectory = path.join(config.getAtomDirectory(), 'packages'); @@ -109,6 +107,7 @@ Delete the installed package(s) from the ~/.pulsar/packages directory.\ } } - return async.eachSeries(uninstallsToRegister, this.registerUninstall.bind(this), () => callback(uninstallError)); + await async.eachSeries(uninstallsToRegister, (data, errorHandler) =>void this.registerUninstall(data).then(errorHandler)); + return uninstallError; // both error and lack of error, as return value atm } } diff --git a/src/unlink.js b/src/unlink.js index 880618a3..1c1ad09d 100644 --- a/src/unlink.js +++ b/src/unlink.js @@ -44,74 +44,59 @@ Run \`ppm links\` to view all the currently linked packages.\ try { process.stdout.write(`Unlinking ${pathToUnlink} `); fs.unlinkSync(pathToUnlink); - return this.logSuccess(); + this.logSuccess(); } catch (error) { this.logFailure(); throw error; } } - unlinkAll(options, callback) { - try { - let child, packagePath; - for (child of fs.list(this.devPackagesPath)) { - packagePath = path.join(this.devPackagesPath, child); + unlinkAll(options) { + let child, packagePath; + for (child of fs.list(this.devPackagesPath)) { + packagePath = path.join(this.devPackagesPath, child); + if (fs.isSymbolicLinkSync(packagePath)) { this.unlinkPath(packagePath); } + } + if (!options.argv.dev) { + for (child of fs.list(this.packagesPath)) { + packagePath = path.join(this.packagesPath, child); if (fs.isSymbolicLinkSync(packagePath)) { this.unlinkPath(packagePath); } } - if (!options.argv.dev) { - for (child of fs.list(this.packagesPath)) { - packagePath = path.join(this.packagesPath, child); - if (fs.isSymbolicLinkSync(packagePath)) { this.unlinkPath(packagePath); } - } - } - return callback(); - } catch (error) { - return callback(error); } } - unlinkPackage(options, callback) { - let packageName; + unlinkPackage(options) { const packagePath = options.argv._[0]?.toString() ?? "."; const linkPath = path.resolve(process.cwd(), packagePath); + let packageName; try { packageName = CSON.readFileSync(CSON.resolve(path.join(linkPath, 'package'))).name; } catch (error) {} - if (!packageName) { packageName = path.basename(linkPath); } + packageName ||= path.basename(linkPath); if (options.argv.hard) { - try { this.unlinkPath(this.getDevPackagePath(packageName)); this.unlinkPath(this.getPackagePath(packageName)); - return callback(); - } catch (error) { - return callback(error); - } } else { - let targetPath; - if (options.argv.dev) { - targetPath = this.getDevPackagePath(packageName); - } else { - targetPath = this.getPackagePath(packageName); - } - try { - this.unlinkPath(targetPath); - return callback(); - } catch (error) { - return callback(error); - } + const targetPath = options.argv.dev + ? this.getDevPackagePath(packageName) + : this.getPackagePath(packageName); + this.unlinkPath(targetPath); } } - run(options) { - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); - if (options.argv.all) { - return this.unlinkAll(options, callback); - } else { - return this.unlinkPackage(options, callback); + try { + if (options.argv.all) { + this.unlinkAll(options); + } else { + this.unlinkPackage(options); + } + } catch (error) { + return error; //error as return value for the time being } } } diff --git a/src/unpublish.js b/src/unpublish.js index bade75d5..c5eb73e4 100644 --- a/src/unpublish.js +++ b/src/unpublish.js @@ -31,19 +31,14 @@ name is specified.\ return options.alias('f', 'force').boolean('force').describe('force', 'Do not prompt for confirmation'); } - unpublishPackage(packageName, packageVersion, callback) { + async unpublishPackage(packageName, packageVersion) { let packageLabel = packageName; if (packageVersion) { packageLabel += `@${packageVersion}`; } process.stdout.write(`Unpublishing ${packageLabel} `); - auth.getToken((error, token) => { - if (error != null) { - this.logFailure(); - callback(error); - return; - } - + try { + const token = await auth.getToken(); const options = { url: `${config.getAtomPackagesUrl()}/${packageName}`, headers: { @@ -54,59 +49,62 @@ name is specified.\ if (packageVersion) { options.url += `/versions/${packageVersion}`; } - request.del(options, (error, response, body) => { - if (body == null) { body = {}; } - if (error != null) { - this.logFailure(); - return callback(error); - } else if (response.statusCode !== 204) { - this.logFailure(); - const message = body.message ?? body.error ?? body; - return callback(`Unpublishing failed: ${message}`); - } else { + return new Promise((resolve, reject) =>{ + request.del(options, (error, response, body) => { + body ??= {}; + if (error != null) { + this.logFailure(); + return void reject(error); + } + if (response.statusCode !== 204) { + this.logFailure(); + const message = body.message ?? body.error ?? body; + return void reject(`Unpublishing failed: ${message}`); + } + this.logSuccess(); - return callback(); - } + resolve(); + }); }); - }); + } catch (error) { + this.logFailure(); + throw error; + } } - promptForConfirmation(packageName, packageVersion, callback) { - let question; + async promptForConfirmation(packageName, packageVersion) { let packageLabel = packageName; if (packageVersion) { packageLabel += `@${packageVersion}`; } - if (packageVersion) { - question = `Are you sure you want to unpublish '${packageLabel}'? (no) `; + const question = packageVersion + ? `Are you sure you want to unpublish '${packageLabel}'? (no) ` + : `Are you sure you want to unpublish ALL VERSIONS of '${packageLabel}'? ` + + "This will remove it from the ppm registry, including " + + "download counts and stars, and will render the package " + + "name permanently unusable. This action is irreversible. (no)"; + + let answer = await this.prompt(question); + answer = answer ? answer.trim().toLowerCase() : 'no'; + if (['y', 'yes'].includes(answer)) { + await this.unpublishPackage(packageName, packageVersion); } else { - question = `Are you sure you want to unpublish ALL VERSIONS of '${packageLabel}'? ` + - "This will remove it from the ppm registry, including " + - "download counts and stars, and will render the package " + - "name permanently unusable. This action is irreversible. (no)"; + return `Cancelled unpublishing ${packageLabel}`; } - - return this.prompt(question, answer => { - answer = answer ? answer.trim().toLowerCase() : 'no'; - if (['y', 'yes'].includes(answer)) { - return this.unpublishPackage(packageName, packageVersion, callback); - } else { - return callback(`Cancelled unpublishing ${packageLabel}`); - } - }); } - prompt(question, callback) { + prompt(question) { const prompt = readline.createInterface(process.stdin, process.stdout); - prompt.question(question, function(answer) { - prompt.close(); - return callback(answer); + return new Promise((resolve, _reject) => { + prompt.question(question, answer => { + prompt.close(); + resolve(answer); + }); }); } - run(options) { + async run(options) { let version; - const {callback} = options; options = this.parseOptions(options.commandArgs); let [name] = options.argv._; @@ -123,15 +121,12 @@ name is specified.\ name = JSON.parse(fs.readFileSync('package.json'))?.name; } catch (error) {} } - - if (!name) { - name = path.basename(process.cwd()); - } + name ||= path.basename(process.cwd()); if (options.argv.force) { - return this.unpublishPackage(name, version, callback); - } else { - return this.promptForConfirmation(name, version, callback); + return await this.unpublishPackage(name, version).catch(error => error); //errors as values atm } + + return await this.promptForConfirmation(name, version).catch(error => error); //errors as values atm } } diff --git a/src/unstar.js b/src/unstar.js index 35e54aae..60e75981 100644 --- a/src/unstar.js +++ b/src/unstar.js @@ -25,7 +25,7 @@ Run \`ppm stars\` to see all your starred packages.\ return options.alias('h', 'help').describe('help', 'Print this usage message'); } - starPackage(packageName, token, callback) { + starPackage(packageName, token) { if (process.platform === 'darwin') { process.stdout.write('\uD83D\uDC5F \u2B50 '); } process.stdout.write(`Unstarring ${packageName} `); const requestSettings = { @@ -35,39 +35,41 @@ Run \`ppm stars\` to see all your starred packages.\ authorization: token } }; - request.del(requestSettings, (error, response, body) => { - if (body == null) { body = {}; } - if (error != null) { - this.logFailure(); - return callback(error); - } else if (response.statusCode !== 204) { - this.logFailure(); - const message = request.getErrorMessage(body, error); - return callback(`Unstarring package failed: ${message}`); - } else { + return new Promise((resolve, reject) => { + request.del(requestSettings, (error, response, body) => { + body ??= {}; + if (error != null) { + this.logFailure(); + return void reject(error); + } + if (response.statusCode !== 204) { + this.logFailure(); + const message = request.getErrorMessage(body, error); + return void reject(`Unstarring package failed: ${message}`); + } + this.logSuccess(); - return callback(); - } + resolve(); + }); }); } - run(options) { - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); const packageNames = this.packageNamesFromArgv(options.argv); if (packageNames.length === 0) { - callback("Please specify a package name to unstar"); - return; + return "Please specify a package name to unstar"; // error as return value atm } - Login.getTokenOrLogin((error, token) => { - if (error != null) { return callback(error); } - + try { + const token = await Login.getTokenOrLogin(); const commands = packageNames.map(packageName => { - return callback => this.starPackage(packageName, token, callback); + return async () => await this.starPackage(packageName, token); }); - return async.waterfall(commands, callback); - }); + return await async.waterfall(commands); + } catch (error) { + return error; // error as return value atm + } } } diff --git a/src/upgrade.js b/src/upgrade.js index 995d97dc..b7da09e8 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -75,16 +75,18 @@ available updates.\ } catch (error) {} } - loadInstalledAtomVersion(options, callback) { + async loadInstalledAtomVersion(options) { if (options.argv.compatible) { - process.nextTick(() => { - const version = this.normalizeVersion(options.argv.compatible); - if (semver.valid(version)) { this.installedAtomVersion = version; } - return callback(); + return new Promise((resolve, _reject) => { + process.nextTick(() => { + const version = this.normalizeVersion(options.argv.compatible); + if (semver.valid(version)) { this.installedAtomVersion = version; } + resolve(); + }); }); - } else { - return this.loadInstalledAtomMetadata(callback); } + + return await this.loadInstalledAtomMetadata(); } folderIsRepo(pack) { @@ -92,26 +94,29 @@ available updates.\ return fs.existsSync(repoGitFolderPath); } - getLatestVersion(pack, callback) { + getLatestVersion(pack) { const requestSettings = { url: `${config.getAtomPackagesUrl()}/${pack.name}`, json: true }; - return request.get(requestSettings, (error, response, body) => { - if (body == null) { body = {}; } - if (error != null) { - return callback(`Request for package information failed: ${error.message}`); - } else if (response.statusCode === 404) { - return callback(); - } else if (response.statusCode !== 200) { - const message = body.message ?? body.error ?? body; - return callback(`Request for package information failed: ${message}`); - } else { - let version; + return new Promise((resolve, reject) => { + request.get(requestSettings, (error, response, body) => { + body ??= {}; + if (error != null) { + return void reject(`Request for package information failed: ${error.message}`); + } + if (response.statusCode === 404) { + return void resolve(); + } + if (response.statusCode !== 200) { + const message = body.message ?? body.error ?? body; + return void reject(`Request for package information failed: ${message}`); + } + const atomVersion = this.installedAtomVersion; let latestVersion = pack.version; - const object = body.versions != null ? body.versions : {}; - for (version in object) { + const object = body?.versions ?? {}; + for (let version in object) { const metadata = object[version]; if (!semver.valid(version)) { continue; } if (!metadata) { continue; } @@ -120,35 +125,40 @@ available updates.\ if (!semver.validRange(engine)) { continue; } if (!semver.satisfies(atomVersion, engine)) { continue; } - if (semver.gt(version, latestVersion)) { latestVersion = version; } + if (!semver.gt(version, latestVersion)) { continue; } + + latestVersion = version; } - if ((latestVersion !== pack.version) && this.hasRepo(pack)) { - return callback(null, latestVersion); - } else { - return callback(); + if ((latestVersion === pack.version) || !this.hasRepo(pack)) { + return void resolve(); } - } + + resolve(latestVersion); + }); }); } - getLatestSha(pack, callback) { + async getLatestSha(pack) { const repoPath = path.join(this.atomPackagesDirectory, pack.name); const repo = Git.open(repoPath); - return config.getSetting('git', command => { - if (command == null) { command = 'git'; } - const args = ['fetch', 'origin', repo.getShortHead()]; - git.addGitToEnv(process.env); - return this.spawn(command, args, {cwd: repoPath}, function(code, stderr, stdout) { - if (stderr == null) { stderr = ''; } - if (stdout == null) { stdout = ''; } - if (code !== 0) { return callback(new Error('Exit code: ' + code + ' - ' + stderr)); } + + const command = await config.getSetting('git') ?? 'git'; + const args = ['fetch', 'origin', repo.getShortHead()]; + git.addGitToEnv(process.env); + return new Promise((resolve, reject) => { + this.spawn(command, args, {cwd: repoPath}, (code, stderr, stdout) => { + stderr ??= ''; + stdout ??= ''; + if (code !== 0) { + return void reject(new Error('Exit code: ' + code + ' - ' + stderr)); + } + const sha = repo.getReferenceTarget(repo.getUpstreamBranch(repo.getHead())); if (sha !== pack.apmInstallSource.sha) { - return callback(null, sha); - } else { - return callback(); + return void resolve(sha); } + resolve(); }); }); } @@ -157,55 +167,50 @@ available updates.\ return (Packages.getRepository(pack) != null); } - getAvailableUpdates(packages, callback) { - const getLatestVersionOrSha = (pack, done) => { + async getAvailableUpdates(packages) { + const getLatestVersionOrSha = async pack => { if (this.folderIsRepo(pack) && (pack.apmInstallSource?.type === 'git')) { - return this.getLatestSha(pack, (err, sha) => done(err, {pack, sha})); - } else { - return this.getLatestVersion(pack, (err, latestVersion) => done(err, {pack, latestVersion})); + return await this.getLatestSha(pack).then(sha => ({pack, sha})); } - }; - async.mapLimit(packages, 10, getLatestVersionOrSha, function(error, updates) { - if (error != null) { return callback(error); } - - updates = _.filter(updates, update => (update.latestVersion != null) || (update.sha != null)); - updates.sort((updateA, updateB) => updateA.pack.name.localeCompare(updateB.pack.name)); + return await this.getLatestVersion(pack).then(latestVersion => ({pack, latestVersion})); + }; - return callback(null, updates); - }); + let updates = await async.mapLimit(packages, 10, getLatestVersionOrSha); + updates = _.filter(updates, update => (update.latestVersion != null) || (update.sha != null)); + updates.sort((updateA, updateB) => updateA.pack.name.localeCompare(updateB.pack.name)); + return updates; } - promptForConfirmation(callback) { - read({prompt: 'Would you like to install these updates? (yes)', edit: true}, function(error, answer) { - answer = answer ? answer.trim().toLowerCase() : 'yes'; - return callback(error, (answer === 'y') || (answer === 'yes')); + promptForConfirmation() { + return new Promise((resolve, reject) => { + read({prompt: 'Would you like to install these updates? (yes)', edit: true}, (error, answer) => { + if (error != null) { + return void reject(error); + } + answer = answer ? answer.trim().toLowerCase() : 'yes'; + resolve((answer === 'y') || (answer === 'yes')); + }); }); } - installUpdates(updates, callback) { + async installUpdates(updates) { const installCommands = []; - const { - verbose - } = this; for (let {pack, latestVersion} of Array.from(updates)) { - (((pack, latestVersion) => installCommands.push(function(callback) { - let commandArgs; - if (pack.apmInstallSource?.type === 'git') { - commandArgs = [pack.apmInstallSource.source]; - } else { - commandArgs = [`${pack.name}@${latestVersion}`]; - } - if (verbose) { commandArgs.unshift('--verbose'); } - return new Install().run({callback, commandArgs}); - })))(pack, latestVersion); + installCommands.push(async () => { + const commandArgs = pack.apmInstallSource?.type === 'git' + ? [pack.apmInstallSource.source] + : [`${pack.name}@${latestVersion}`]; + if (this.verbose) { commandArgs.unshift('--verbose'); } + await new Install().run({commandArgs}); + }); } - return async.waterfall(installCommands, callback); + await async.waterfall(installCommands); } - run(options) { - const {callback, command} = options; + async run(options) { + const {command} = options; options = this.parseOptions(options.commandArgs); options.command = command; @@ -214,63 +219,60 @@ available updates.\ process.env.NODE_DEBUG = 'request'; } - return this.loadInstalledAtomVersion(options, () => { - if (this.installedAtomVersion) { - return this.upgradePackages(options, callback); - } else { - return callback('Could not determine current Atom version installed'); + try { + await this.loadInstalledAtomVersion(options); + if (!this.installedAtomVersion) { + return 'Could not determine current Atom version installed'; //errors as return values atm } - }); + + await this.upgradePackages(options); + } catch (error) { + return error; //rewiring error as return value + } } - upgradePackages(options, callback) { + async upgradePackages(options) { const packages = this.getInstalledPackages(options); - return this.getAvailableUpdates(packages, (error, updates) => { - if (error != null) { return callback(error); } - - if (options.argv.json) { - const packagesWithLatestVersionOrSha = updates.map(function({pack, latestVersion, sha}) { - if (latestVersion) { pack.latestVersion = latestVersion; } - if (sha) { pack.latestSha = sha; } - return pack; - }); - console.log(JSON.stringify(packagesWithLatestVersionOrSha)); - } else { - console.log("Package Updates Available".cyan + ` (${updates.length})`); - tree(updates, function({pack, latestVersion, sha}) { - let {name, apmInstallSource, version} = pack; - name = name.yellow; - if (sha != null) { - version = apmInstallSource.sha.substr(0, 8).red; - latestVersion = sha.substr(0, 8).green; - } else { - version = version.red; - latestVersion = latestVersion.green; - } - latestVersion = latestVersion?.green || apmInstallSource?.sha?.green; - return `${name} ${version} -> ${latestVersion}`; - }); - } + const updates = await this.getAvailableUpdates(packages); - if (options.command === 'outdated') { return callback(); } - if (options.argv.list) { return callback(); } - if (updates.length === 0) { return callback(); } - - console.log(); - if (options.argv.confirm) { - return this.promptForConfirmation((error, confirmed) => { - if (error != null) { return callback(error); } - - if (confirmed) { - console.log(); - return this.installUpdates(updates, callback); - } else { - return callback(); - } - }); - } else { - return this.installUpdates(updates, callback); + if (options.argv.json) { + const packagesWithLatestVersionOrSha = updates.map(({pack, latestVersion, sha}) => { + if (latestVersion) { pack.latestVersion = latestVersion; } + if (sha) { pack.latestSha = sha; } + return pack; + }); + console.log(JSON.stringify(packagesWithLatestVersionOrSha)); + } else { + console.log("Package Updates Available".cyan + ` (${updates.length})`); + tree(updates, ({pack, latestVersion, sha}) => { + let {name, apmInstallSource, version} = pack; + name = name.yellow; + if (sha != null) { + version = apmInstallSource.sha.substr(0, 8).red; + latestVersion = sha.substr(0, 8).green; + } else { + version = version.red; + latestVersion = latestVersion.green; + } + latestVersion = latestVersion?.green || apmInstallSource?.sha?.green; + return `${name} ${version} -> ${latestVersion}`; + }); + } + + if (options.command === 'outdated') { return; } + if (options.argv.list) { return; } + if (updates.length === 0) { return; } + + console.log(); + if (options.argv.confirm) { + const confirmed = await this.promptForConfirmation(); + if (confirmed) { + console.log(); + await this.installUpdates(updates); } - }); + return; + } + + await this.installUpdates(updates); } } diff --git a/src/view.js b/src/view.js index 32ef5846..ec99bb07 100644 --- a/src/view.js +++ b/src/view.js @@ -26,38 +26,39 @@ View information about a package/theme.\ return options.string('compatible').describe('compatible', 'Show the latest version compatible with this Atom version'); } - loadInstalledAtomVersion(options, callback) { - return process.nextTick(() => { - let installedAtomVersion; - if (options.argv.compatible) { - const version = this.normalizeVersion(options.argv.compatible); - if (semver.valid(version)) { installedAtomVersion = version; } - } - return callback(installedAtomVersion); + loadInstalledAtomVersion(options) { + return new Promise((resolve, _reject) => { + process.nextTick(() => { + let installedAtomVersion; + if (options.argv.compatible) { + const version = this.normalizeVersion(options.argv.compatible); + if (semver.valid(version)) { installedAtomVersion = version; } + } + return resolve(installedAtomVersion); + }); }); } - getLatestCompatibleVersion(pack, options, callback) { - return this.loadInstalledAtomVersion(options, function(installedAtomVersion) { - if (!installedAtomVersion) { return callback(pack.releases.latest); } + async getLatestCompatibleVersion(pack, options) { + const installedAtomVersion = await this.loadInstalledAtomVersion(options); + if (!installedAtomVersion) { return pack.releases.latest; } - let latestVersion = null; - const object = pack.versions != null ? pack.versions : {}; - for (let version in object) { - const metadata = object[version]; - if (!semver.valid(version)) { continue; } - if (!metadata) { continue; } + let latestVersion = null; + const object = pack?.versions ?? {}; + for (let version in object) { + const metadata = object[version]; + if (!semver.valid(version)) { continue; } + if (!metadata) { continue; } - const engine = metadata.engines?.pulsar ?? metadata.engines?.atom ?? "*"; - if (!semver.validRange(engine)) { continue; } - if (!semver.satisfies(installedAtomVersion, engine)) { continue; } + const engine = metadata.engines?.pulsar ?? metadata.engines?.atom ?? "*"; + if (!semver.validRange(engine)) { continue; } + if (!semver.satisfies(installedAtomVersion, engine)) { continue; } - if (latestVersion == null) { latestVersion = version; } - if (semver.gt(version, latestVersion)) { latestVersion = version; } - } + latestVersion ??= version; + if (semver.gt(version, latestVersion)) { latestVersion = version; } + } - return callback(latestVersion); - }); + return latestVersion; } getRepository(pack) { @@ -67,71 +68,72 @@ View information about a package/theme.\ } } - getPackage(packageName, options, callback) { + getPackage(packageName, options) { const requestSettings = { url: `${config.getAtomPackagesUrl()}/${packageName}`, json: true }; - return request.get(requestSettings, (error, response, body) => { - if (body == null) { body = {}; } - if (error != null) { - return callback(error); - } else if (response.statusCode === 200) { - return this.getLatestCompatibleVersion(body, options, function(version) { + return new Promise((resolve, reject) => { + request.get(requestSettings, (error, response, body) => { + body ??= {}; + if (error != null) { + return void reject(error); + } + if (response.statusCode !== 200) { + const message = body.message ?? body.error ?? body; + return void reject(`Requesting package failed: ${message}`); + } + + this.getLatestCompatibleVersion(body, options).then(version => { const {name, readme, downloads, stargazers_count} = body; - const metadata = (body.versions != null ? body.versions[version] : undefined) != null ? (body.versions != null ? body.versions[version] : undefined) : {name}; + const metadata = body.versions?.[version] ?? {name}; const pack = _.extend({}, metadata, {readme, downloads, stargazers_count}); - return callback(null, pack); + resolve(pack); }); - } else { - const message = body.message ?? body.error ?? body; - return callback(`Requesting package failed: ${message}`); - } + }); }); } - run(options) { - const {callback} = options; + async run(options) { options = this.parseOptions(options.commandArgs); const [packageName] = options.argv._; if (!packageName) { - callback("Missing required package name"); + return "Missing required package name"; //errors as return values atm + } + + let pack; + try { + pack = await this.getPackage(packageName, options); + } catch (error) { + return error; //errors as return values atm + } + + if (options.argv.json) { + console.log(JSON.stringify(pack, null, 2)); return; } - return this.getPackage(packageName, options, (error, pack) => { - if (error != null) { - callback(error); - return; - } - - if (options.argv.json) { - console.log(JSON.stringify(pack, null, 2)); - } else { - let repository; - console.log(`${pack.name.cyan}`); - const items = []; - if (pack.version) { items.push(pack.version.yellow); } - if (repository = this.getRepository(pack)) { - items.push(repository.underline); - } - if (pack.description) { items.push(pack.description.replace(/\s+/g, ' ')); } - if (pack.downloads >= 0) { - items.push(_.pluralize(pack.downloads, 'download')); - } - if (pack.stargazers_count >= 0) { - items.push(_.pluralize(pack.stargazers_count, 'star')); - } - tree(items); + console.log(`${pack.name.cyan}`); + const items = []; + if (pack.version) { items.push(pack.version.yellow); } + const repository = this.getRepository(pack); + if (repository) { + items.push(repository.underline); + } + if (pack.description) { items.push(pack.description.replace(/\s+/g, ' ')); } + if (pack.downloads >= 0) { + items.push(_.pluralize(pack.downloads, 'download')); + } + if (pack.stargazers_count >= 0) { + items.push(_.pluralize(pack.stargazers_count, 'star')); + } - console.log(); - console.log(`Run \`ppm install ${pack.name}\` to install this package.`); - console.log(); - } + tree(items); - return callback(); - }); + console.log(); + console.log(`Run \`ppm install ${pack.name}\` to install this package.`); + console.log(); } }