From 8b03d4c55045c7978ceb584c6b83b28bc4c56a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 15 Sep 2023 23:25:55 +0200 Subject: [PATCH 01/64] Beautification of apm-cli No functional changes so far. --- src/apm-cli.js | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/apm-cli.js b/src/apm-cli.js index d872af33..8208970f 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,10 +104,10 @@ const showHelp = function(options) { help += "\n colored output."; } - return console.error(help); + console.error(help); }; -const printVersions = function(args, callback) { +function printVersions(args, callback) { const apmVersion = require("../package.json").version ?? ""; const npmVersion = require("npm/package.json").version ?? ""; const nodeVersion = process.versions.node ?? ""; @@ -154,22 +154,24 @@ ${'git'.magenta} ${gitVersion.magenta}\ }))); }; -var getAtomVersion = callback => config.getResourcePath(function(resourcePath) { - const unknownVersion = 'unknown'; - try { - const { version } = require(path.join(resourcePath, "package.json")) ?? unknownVersion; - return callback(version); - } catch (error) { - return callback(unknownVersion); - } -}); +function getAtomVersion(callback) { + return config.getResourcePath(function(resourcePath) { + const unknownVersion = 'unknown'; + try { + const { version } = require(path.join(resourcePath, "package.json")) ?? unknownVersion; + return callback(version); + } catch (error) { + return callback(unknownVersion); + } + }); +} -var getPythonVersion = function(callback) { +function getPythonVersion(callback) { const npmOptions = { userconfig: config.getUserConfigPath(), globalconfig: config.getGlobalConfigPath() }; - return npm.load(npmOptions, function() { + return npm.load(npmOptions, () => { let python = npm.config.get("python") ?? process.env.PYTHON; if (config.isWin32() && !python) { let rootDir = process.env.SystemDrive != null ? process.env.SystemDrive : 'C:\\'; @@ -178,14 +180,14 @@ var getPythonVersion = function(callback) { if (fs.isFileSync(pythonExe)) { python = pythonExe; } } - if (python == null) { python = 'python'; } + 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) { + spawned.on('error', () => {}); + return spawned.on('close', (code) => { let version, name; if (code === 0) { [name, version] = Buffer.concat(outputChunks).toString().split(' '); From 8095925831ad6c08ea982b3fb9c4aa20200400a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 15 Sep 2023 23:40:39 +0200 Subject: [PATCH 02/64] Wrap printVersions No tests broke. This is a functional change but very much a simple and basic transformation. --- src/apm-cli.js | 88 ++++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/src/apm-cli.js b/src/apm-cli.js index 8208970f..2da07a20 100644 --- a/src/apm-cli.js +++ b/src/apm-cli.js @@ -107,51 +107,53 @@ function showHelp(options) { console.error(help); }; -function printVersions(args, callback) { - const apmVersion = require("../package.json").version ?? ""; - const npmVersion = require("npm/package.json").version ?? ""; - const nodeVersion = process.versions.node ?? ""; +function printVersions(args) { + return new Promise((resolve, _reject) => { + 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 versions; - if (args.json) { - versions = { - apm: apmVersion, - ppm: apmVersion, - npm: npmVersion, - node: nodeVersion, - atom: atomVersion, - pulsar: atomVersion, - python: pythonVersion, - git: gitVersion, - nodeArch: process.arch - }; - if (config.isWin32()) { - versions.visualStudio = config.getInstalledVisualStudioFlag(); - } - console.log(JSON.stringify(versions)); - } else { - if (pythonVersion == null) { pythonVersion = ''; } - if (gitVersion == null) { gitVersion = ''; } - if (atomVersion == null) { atomVersion = ''; } - versions = `\ -${'ppm'.red} ${apmVersion.red} -${'npm'.green} ${npmVersion.green} -${'node'.blue} ${nodeVersion.blue} ${process.arch.blue} -${'pulsar'.cyan} ${atomVersion.cyan} -${'python'.yellow} ${pythonVersion.yellow} -${'git'.magenta} ${gitVersion.magenta}\ -`; + getPythonVersion(pythonVersion => git.getGitVersion(gitVersion => getAtomVersion(function(atomVersion) { + let versions; + if (args.json) { + versions = { + apm: apmVersion, + ppm: apmVersion, + npm: npmVersion, + node: nodeVersion, + atom: atomVersion, + pulsar: atomVersion, + python: pythonVersion, + git: gitVersion, + nodeArch: process.arch + }; + if (config.isWin32()) { + versions.visualStudio = config.getInstalledVisualStudioFlag(); + } + console.log(JSON.stringify(versions)); + } else { + if (pythonVersion == null) { pythonVersion = ''; } + if (gitVersion == null) { gitVersion = ''; } + if (atomVersion == null) { atomVersion = ''; } + versions = `\ + ${'ppm'.red} ${apmVersion.red} + ${'npm'.green} ${npmVersion.green} + ${'node'.blue} ${nodeVersion.blue} ${process.arch.blue} + ${'pulsar'.cyan} ${atomVersion.cyan} + ${'python'.yellow} ${pythonVersion.yellow} + ${'git'.magenta} ${gitVersion.magenta}\ + `; - if (config.isWin32()) { - const visualStudioVersion = config.getInstalledVisualStudioFlag() ?? ""; - versions += `\n${'visual studio'.cyan} ${visualStudioVersion.cyan}`; - } + if (config.isWin32()) { + const visualStudioVersion = config.getInstalledVisualStudioFlag() ?? ""; + versions += `\n${'visual studio'.cyan} ${visualStudioVersion.cyan}`; + } - console.log(versions); - } - return callback(); - }))); + console.log(versions); + } + return resolve(); + }))); + }); }; function getAtomVersion(callback) { @@ -235,7 +237,7 @@ module.exports = { command } = options; if (args.version) { - return printVersions(args, options.callback); + return printVersions(args).then(options.callback); } else if (args.help) { if ((Command = commands[options.command])) { showHelp(new Command().parseOptions?.(options.command)); From aa6b2e9f9c19ef4f8cbb811c9de7f1906b04fbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 15 Sep 2023 23:48:57 +0200 Subject: [PATCH 03/64] Promisify getPythonVersion All tests succeed. This is a wrapper implementation, like for printVersions. --- src/apm-cli.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/apm-cli.js b/src/apm-cli.js index 2da07a20..527c5079 100644 --- a/src/apm-cli.js +++ b/src/apm-cli.js @@ -113,7 +113,7 @@ function printVersions(args) { const npmVersion = require("npm/package.json").version ?? ""; const nodeVersion = process.versions.node ?? ""; - getPythonVersion(pythonVersion => git.getGitVersion(gitVersion => getAtomVersion(function(atomVersion) { + getPythonVersion(pythonVersion => git.getGitVersion(gitVersion => getAtomVersion().then(atomVersion => { let versions; if (args.json) { versions = { @@ -132,9 +132,9 @@ function printVersions(args) { } console.log(JSON.stringify(versions)); } else { - if (pythonVersion == null) { pythonVersion = ''; } - if (gitVersion == null) { gitVersion = ''; } - if (atomVersion == null) { atomVersion = ''; } + pythonVersion ??= ''; + gitVersion ??= ''; + atomVersion ??= ''; versions = `\ ${'ppm'.red} ${apmVersion.red} ${'npm'.green} ${npmVersion.green} @@ -156,15 +156,17 @@ function printVersions(args) { }); }; -function getAtomVersion(callback) { - return config.getResourcePath(function(resourcePath) { - const unknownVersion = 'unknown'; - try { - const { version } = require(path.join(resourcePath, "package.json")) ?? unknownVersion; - return callback(version); - } catch (error) { - return callback(unknownVersion); - } +function getAtomVersion() { + return new Promise((resolve, _reject) => { + config.getResourcePath((resourcePath) => { + const unknownVersion = 'unknown'; + try { + const { version } = require(path.join(resourcePath, "package.json")) ?? unknownVersion; + resolve(version); + } catch (error) { + resolve(unknownVersion); + } + }); }); } From debca2921f92a8bfe4d6931fcde6cc8c6664bb81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 15 Sep 2023 23:55:47 +0200 Subject: [PATCH 04/64] Promisify getPythonVersion Under similar terms as the previous ones. Tests passing. --- src/apm-cli.js | 58 ++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/apm-cli.js b/src/apm-cli.js index 527c5079..adcd1d0f 100644 --- a/src/apm-cli.js +++ b/src/apm-cli.js @@ -113,7 +113,7 @@ function printVersions(args) { const npmVersion = require("npm/package.json").version ?? ""; const nodeVersion = process.versions.node ?? ""; - getPythonVersion(pythonVersion => git.getGitVersion(gitVersion => getAtomVersion().then(atomVersion => { + getPythonVersion().then(pythonVersion => git.getGitVersion(gitVersion => getAtomVersion().then(atomVersion => { let versions; if (args.json) { versions = { @@ -170,34 +170,36 @@ function getAtomVersion() { }); } -function getPythonVersion(callback) { - const npmOptions = { - userconfig: config.getUserConfigPath(), - globalconfig: config.getGlobalConfigPath() - }; - return npm.load(npmOptions, () => { - 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; } - } - - 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(); +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); + }); }); }); }; From 6058e8c2998ec60c0894d484422335b690c82d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Sat, 16 Sep 2023 02:40:08 +0200 Subject: [PATCH 05/64] Wrapping all the command executions There are failing tests that I isolated in a spec2 folder. - develop-spec seems like it "mocks out" the resolution of the Promise, causing some tests to fail - stars-spec explodes with a "trying to reopen HTTP port" error, probably some cleanup section couldn't run - test-spec mocks I/O of processes and for some reason, the tests won't produce the right output - unpublish-spec fails at the unpublishing itself, there is a lot of mocking but the failure looks too legit nevertheless - upgrade-spec first times out and then dies with the "HTTP port" error, like stars-spec --- {spec => spec2}/develop-spec.js | 0 {spec => spec2}/stars-spec.js | 0 {spec => spec2}/test-spec.js | 0 {spec => spec2}/unpublish-spec.js | 0 {spec => spec2}/upgrade-spec.js | 0 src/apm-cli.js | 25 ++++++++++++++++++------- 6 files changed, 18 insertions(+), 7 deletions(-) rename {spec => spec2}/develop-spec.js (100%) rename {spec => spec2}/stars-spec.js (100%) rename {spec => spec2}/test-spec.js (100%) rename {spec => spec2}/unpublish-spec.js (100%) rename {spec => spec2}/upgrade-spec.js (100%) diff --git a/spec/develop-spec.js b/spec2/develop-spec.js similarity index 100% rename from spec/develop-spec.js rename to spec2/develop-spec.js diff --git a/spec/stars-spec.js b/spec2/stars-spec.js similarity index 100% rename from spec/stars-spec.js rename to spec2/stars-spec.js diff --git a/spec/test-spec.js b/spec2/test-spec.js similarity index 100% rename from spec/test-spec.js rename to spec2/test-spec.js diff --git a/spec/unpublish-spec.js b/spec2/unpublish-spec.js similarity index 100% rename from spec/unpublish-spec.js rename to spec2/unpublish-spec.js diff --git a/spec/upgrade-spec.js b/spec2/upgrade-spec.js similarity index 100% rename from spec/upgrade-spec.js rename to spec2/upgrade-spec.js diff --git a/src/apm-cli.js b/src/apm-cli.js index adcd1d0f..068819b9 100644 --- a/src/apm-cli.js +++ b/src/apm-cli.js @@ -61,11 +61,22 @@ const commandClasses = [ const commands = {}; for (let commandClass of commandClasses) { + const originalRun = commandClass.prototype.run; + commandClass.prototype.run = promisifiedRun(originalRun); for (let name of commandClass.commandNames ?? []) { commands[name] = commandClass; } } +function promisifiedRun(commandRun) { + return function(options) { + return new Promise((resolve, _reject) => { + options.callback = resolve; + commandRun.call(this, options); + }); + }; +} + function parseOptions(args) { args ??= []; const options = yargs(args).wrap(Math.min(100, yargs.terminalWidth())); @@ -215,7 +226,7 @@ module.exports = { } let callbackCalled = false; - options.callback = function(error) { + const errorHandler = error => { if (callbackCalled) { return; } callbackCalled = true; if (error != null) { @@ -241,14 +252,14 @@ module.exports = { command } = options; if (args.version) { - return printVersions(args).then(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])) { @@ -256,15 +267,15 @@ module.exports = { } else { showHelp(options); } - return options.callback(); + return errorHandler(); } else if ((Command = commands[command])) { - return new Command().run(options); + return new 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(); } } }; From fe3bf8793b0edcfd1505fb78d33efca5dfcd99c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Mon, 18 Sep 2023 01:57:46 +0200 Subject: [PATCH 06/64] Better sort broken testfiles It seems that install-spec was the one that didn't close the server, causing the remaining tests using a server to break. --- {spec2 => spec}/stars-spec.js | 0 {spec2 => spec}/unpublish-spec.js | 0 {spec => spec2}/install-spec.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {spec2 => spec}/stars-spec.js (100%) rename {spec2 => spec}/unpublish-spec.js (100%) rename {spec => spec2}/install-spec.js (100%) diff --git a/spec2/stars-spec.js b/spec/stars-spec.js similarity index 100% rename from spec2/stars-spec.js rename to spec/stars-spec.js diff --git a/spec2/unpublish-spec.js b/spec/unpublish-spec.js similarity index 100% rename from spec2/unpublish-spec.js rename to spec/unpublish-spec.js diff --git a/spec/install-spec.js b/spec2/install-spec.js similarity index 100% rename from spec/install-spec.js rename to spec2/install-spec.js From b092ca5178ab6026144a7997e96f312d1d150f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Mon, 18 Sep 2023 02:41:52 +0200 Subject: [PATCH 07/64] =?UTF-8?q?Cleanup=20and=20promisification=20of=20th?= =?UTF-8?q?e=20develop=20command=20And=20the=20polishment=20of=20the=20cor?= =?UTF-8?q?responding=20test=20which=20passes=20now.=20=F0=9F=8E=89=20getR?= =?UTF-8?q?epositoryUrl=20has=20a=20true=20resolve-reject=20interface,=20t?= =?UTF-8?q?he=20rest=20always=20resolves,=20mostly=20wrapped=20by=20the=20?= =?UTF-8?q?Promise=20constructor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {spec2 => spec}/develop-spec.js | 13 ++-- src/apm-cli.js | 8 +- src/develop.js | 128 +++++++++++++++++--------------- 3 files changed, 77 insertions(+), 72 deletions(-) rename {spec2 => spec}/develop-spec.js (86%) diff --git a/spec2/develop-spec.js b/spec/develop-spec.js similarity index 86% rename from spec2/develop-spec.js rename to spec/develop-spec.js index 63e85cd4..7340ef26 100644 --- a/spec2/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/src/apm-cli.js b/src/apm-cli.js index 068819b9..bbd81a9a 100644 --- a/src/apm-cli.js +++ b/src/apm-cli.js @@ -61,8 +61,6 @@ const commandClasses = [ const commands = {}; for (let commandClass of commandClasses) { - const originalRun = commandClass.prototype.run; - commandClass.prototype.run = promisifiedRun(originalRun); for (let name of commandClass.commandNames ?? []) { commands[name] = commandClass; } @@ -269,7 +267,11 @@ module.exports = { } return errorHandler(); } else if ((Command = commands[command])) { - return new Command().run(options).then(errorHandler); + const command = new Command(); + if (!Command.promiseBased) { + command.run = promisifiedRun(command.run); + } + return command.run(options).then(errorHandler); } else { return errorHandler(`Unrecognized command: ${command}`); } diff --git a/src/develop.js b/src/develop.js index 58a46804..93498311 100644 --- a/src/develop.js +++ b/src/develop.js @@ -15,6 +15,7 @@ const request = require('./request'); module.exports = class Develop extends Command { + static promiseBased = true; static commandNames = [ "dev", "develop" ]; constructor() { @@ -44,69 +45,76 @@ 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}`); } - } else { + + 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}`); + } + 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) => { - if (options.argv.json) { - return this.logCommandResultsIfFail(callback, ...args); - } else { - return this.logCommandResults(callback, ...args); - } + cloneRepository(repoUrl, packageDirectory, options) { + return new Promise((resolve, _reject) => { + 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) => { + if (options.argv.json) { + return void this.logCommandResultsIfFail(resolve, ...args); + } + + return void this.logCommandResults(resolve, ...args); + }); }); }); } - 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) { + return new Promise((resolve, _reject) => { + process.chdir(packageDirectory); + const installOptions = _.clone(options); + installOptions.callback = resolve; + + return void new Install().run(installOptions); + }); } - linkPackage(packageDirectory, options, callback) { - const linkOptions = _.clone(options); - if (callback) { - linkOptions.callback = callback; - } - linkOptions.commandArgs = [packageDirectory, '--dev']; - return new Link().run(linkOptions); + linkPackage(packageDirectory, options) { + return new Promise((resolve, _reject) => { + const linkOptions = _.clone(options); + linkOptions.callback = resolve; + + linkOptions.commandArgs = [packageDirectory, '--dev']; + return void new Link().run(linkOptions); + }); } 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 +122,19 @@ 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)); - - return async.waterfall(tasks, options.callback); - } - }); } + + return new Promise((resolve, _reject) => { + this.getRepositoryUrl(packageName).then(repoUrl => { + const tasks = []; + tasks.push(callback => this.cloneRepository(repoUrl, packageDirectory, options).then(callback)); + + tasks.push(callback => this.installDependencies(packageDirectory, options).then(callback)); + + tasks.push(callback => this.linkPackage(packageDirectory, options).then(callback)); + + return async.waterfall(tasks, resolve); + }, resolve); + }); } } From 1baaa605a7b05ff5f302388252433f0f671f1a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Mon, 18 Sep 2023 16:25:38 +0200 Subject: [PATCH 08/64] auth.js getToken asyncified All the previous tests pass still. Finally, a function that could be changed to an async flow, rather than just wrapped into a Promise. The calls have been modified appropriately. --- spec/spec-helper.js | 2 +- src/auth.js | 38 ++++++++++++++++++-------------------- src/login.js | 8 +------- src/uninstall.js | 8 +++----- src/unpublish.js | 19 ++++++++----------- 5 files changed, 31 insertions(+), 44 deletions(-) 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/src/auth.js b/src/auth.js index 215fa92e..205c70c4 100644 --- a/src/auth.js +++ b/src/auth.js @@ -18,28 +18,26 @@ 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) { + return token; + } + + return Promise.reject(); + } 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. diff --git a/src/login.js b/src/login.js index 544dbe04..9da28b03 100644 --- a/src/login.js +++ b/src/login.js @@ -20,13 +20,7 @@ class Login extends Command { } static getTokenOrLogin(callback) { - return auth.getToken(function(error, token) { - if (error != null) { - return new Login().run({callback, commandArgs: []}); - } else { - return callback(null, token); - } - }); + return void auth.getToken().then(token => void callback(null, token), _error => new Login().run({callback, commandArgs: []})); } parseOptions(argv) { diff --git a/src/uninstall.js b/src/uninstall.js index d555c41f..b1864e24 100644 --- a/src/uninstall.js +++ b/src/uninstall.js @@ -40,9 +40,7 @@ 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(); } - + return void auth.getToken().then(token => { const requestOptions = { url: `${config.getAtomPackagesUrl()}/${packageName}/versions/${packageVersion}/events/uninstall`, json: true, @@ -51,8 +49,8 @@ Delete the installed package(s) from the ~/.pulsar/packages directory.\ } }; - return request.post(requestOptions, (error, response, body) => callback()); - }); + return request.post(requestOptions, (_error, _response, _body) => callback()); + }, callback); } run(options) { diff --git a/src/unpublish.js b/src/unpublish.js index bade75d5..9ca4ff1b 100644 --- a/src/unpublish.js +++ b/src/unpublish.js @@ -37,13 +37,7 @@ name is specified.\ process.stdout.write(`Unpublishing ${packageLabel} `); - auth.getToken((error, token) => { - if (error != null) { - this.logFailure(); - callback(error); - return; - } - + auth.getToken().then(token => { const options = { url: `${config.getAtomPackagesUrl()}/${packageName}`, headers: { @@ -58,17 +52,20 @@ name is specified.\ if (body == null) { body = {}; } if (error != null) { this.logFailure(); - return callback(error); + return void callback(error); } else if (response.statusCode !== 204) { this.logFailure(); const message = body.message ?? body.error ?? body; - return callback(`Unpublishing failed: ${message}`); + return void callback(`Unpublishing failed: ${message}`); } else { this.logSuccess(); - return callback(); + return void callback(); } }); - }); + }, error => { + this.logFailure(); + callback(error); + }); } promptForConfirmation(packageName, packageVersion, callback) { From cf85347de0007e79c01b00aac3286f3482d97602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Mon, 18 Sep 2023 16:56:04 +0200 Subject: [PATCH 09/64] Promisify ci.js Tests still pass. It's a really thin Promise wrapper on the existing code. --- src/ci.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/ci.js b/src/ci.js index 0f8165d4..d76f4fc2 100644 --- a/src/ci.js +++ b/src/ci.js @@ -10,6 +10,7 @@ const Command = require('./command'); module.exports = class Ci extends Command { + static promiseBased = true; static commandNames = ["ci"]; constructor() { @@ -37,7 +38,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 +61,25 @@ 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(resolve, ...args) + ) + ) } run(options) { - const {callback} = 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(callback => config.loadNpm((error, npm) => { this.npm = npm; callback(error); })); commands.push(cb => this.loadInstalledAtomMetadata(cb)); - commands.push(cb => this.installModules(opts, cb)); + commands.push(cb => this.installModules(opts).then(cb)); const iteratee = (item, next) => item(next); - return async.mapSeries(commands, iteratee, function(err) { - if (err) { return callback(err); } - return callback(null); - }); + return new Promise((resolve, _reject) => + void async.mapSeries(commands, iteratee, err => + resolve(err || null) + ) + ); } }; From ccb5d3ebde78b0fa4d906d8c923731e4bd728b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Mon, 18 Sep 2023 17:09:46 +0200 Subject: [PATCH 10/64] Promisification of clean.js Tiny file, tiny wrapper, tests pass. --- src/clean.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/clean.js b/src/clean.js index 844fc588..6aa54fb7 100644 --- a/src/clean.js +++ b/src/clean.js @@ -13,6 +13,7 @@ const fs = require('./fs'); module.exports = class Clean extends Command { + static promiseBased = true; static commandNames = ["clean", "prune"]; constructor() { @@ -33,10 +34,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(resolve, ...args) + ) + ); } }; From 34d78f79f0eef2a19d74fbcd9ea34c1fc0333912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Mon, 18 Sep 2023 18:05:05 +0200 Subject: [PATCH 11/64] Promisification of config.js Tests pass, tiny wrapper of tiny command --- src/config.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/config.js b/src/config.js index 1b6de278..5a74ca5b 100644 --- a/src/config.js +++ b/src/config.js @@ -7,6 +7,7 @@ const Command = require('./command'); module.exports = class Config extends Command { + static promiseBased = true; static commandNames = [ "config" ]; constructor() { @@ -32,7 +33,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 +41,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}`)); + }) + ); } } From 689e8e666225782c61bb532ab7b057a9a5ccfdcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Mon, 18 Sep 2023 18:19:35 +0200 Subject: [PATCH 12/64] Promisification of disable.js A bit fatter wrapping for a bit fatter command. Tests are passing. --- src/disable.js | 100 +++++++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/disable.js b/src/disable.js index fc04341d..1e646e59 100644 --- a/src/disable.js +++ b/src/disable.js @@ -10,6 +10,7 @@ const List = require('./list'); module.exports = class Disable extends Command { + static promiseBased = true; static commandNames = [ "disable" ]; parseOptions(argv) { @@ -24,7 +25,7 @@ Disables the named package(s).\ return options.alias('h', 'help').describe('help', 'Print this usage message'); } - getInstalledPackages(callback) { + getInstalledPackages() { const options = { argv: { theme: false, @@ -33,63 +34,72 @@ 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))))); + return new Promise((resolve, _reject) => + void lister.listBundledPackages(options, (_error, core_packages) => + void lister.listDevPackages(options, (_error, dev_packages) => + void lister.listUserPackages(options, (_error, user_packages) => + void resolve(core_packages.concat(dev_packages, user_packages)) + ) + ) + ) + ); } run(options) { - let settings; - const {callback} = 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; - } - - 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 ')}`); - } + return new Promise((resolve, _reject) => { + let settings; + options = this.parseOptions(options.commandArgs); - // only installed packages can be disabled - packageNames = _.difference(packageNames, uninstalledPackageNames); + let packageNames = this.packageNamesFromArgv(options.argv); - if (packageNames.length === 0) { - callback("Please specify a package to disable"); + const configFilePath = CSON.resolve(path.join(config.getAtomDirectory(), 'config')); + if (!configFilePath) { + resolve("Could not find config.cson. Run Atom first?"); return; } - const keyPath = '*.core.disabledPackages'; - const disabledPackages = _.valueForKeyPath(settings, keyPath) ?? []; - const result = _.union(disabledPackages, packageNames); - _.setValueForKeyPath(settings, keyPath, result); - try { - CSON.writeFileSync(configFilePath, settings); + settings = CSON.readFileSync(configFilePath); } catch (error) { - callback(`Failed to save \`${configFilePath}\`: ${error.message}`); + resolve(`Failed to load \`${configFilePath}\`: ${error.message}`); return; } - console.log(`Disabled:\n ${packageNames.join('\n ')}`); - this.logSuccess(); - return callback(); + return void this.getInstalledPackages().then(installedPackages => { + + + 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 ')}`); + } + + // only installed packages can be disabled + packageNames = _.difference(packageNames, uninstalledPackageNames); + + if (packageNames.length === 0) { + resolve("Please specify a package to disable"); + return; + } + + 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) { + resolve(`Failed to save \`${configFilePath}\`: ${error.message}`); + return; + } + + console.log(`Disabled:\n ${packageNames.join('\n ')}`); + this.logSuccess(); + resolve(); + }, error => void resolve(error)); }); } } From 2e25bb06b597eb6e17871a5a167c8cfdb42e093e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Mon, 18 Sep 2023 18:24:34 +0200 Subject: [PATCH 13/64] Promisification of docs.js Tests passing, trivial wrapper used. --- src/docs.js | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/docs.js b/src/docs.js index 3d42b900..a372d4b6 100644 --- a/src/docs.js +++ b/src/docs.js @@ -7,6 +7,7 @@ const config = require('./apm'); module.exports = class Docs extends View { + static promiseBased = true; static commandNames = [ "docs", "home", "open" ]; parseOptions(argv) { @@ -27,29 +28,30 @@ Open a package's homepage in the default browser.\ } run(options) { - const {callback} = options; - options = this.parseOptions(options.commandArgs); - const [packageName] = options.argv._; - - if (!packageName) { - callback("Missing required package name"); - return; - } - - 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); + return new Promise((resolve, _reject) => { + options = this.parseOptions(options.commandArgs); + const [packageName] = options.argv._; + + if (!packageName) { + resolve("Missing required package name"); + return; + } + + this.getPackage(packageName, options, (error, pack) => { + let repository; + if (error != null) { return void resolve(error); } + + if (repository = this.getRepository(pack)) { + if (options.argv.print) { + console.log(repository); + } else { + this.openRepositoryUrl(repository); + } + return void resolve(); } else { - this.openRepositoryUrl(repository); + return void resolve(`Package \"${packageName}\" does not contain a repository URL`); } - return callback(); - } else { - return callback(`Package \"${packageName}\" does not contain a repository URL`); - } + }); }); } } From 11b6f14bab7c24f7f95389297834a62175ff2490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Mon, 18 Sep 2023 18:27:48 +0200 Subject: [PATCH 14/64] Promisification of enable.js Tests passing. Trivial wrapper added. --- src/enable.js | 96 ++++++++++++++++++++++++++------------------------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/src/enable.js b/src/enable.js index 3471f991..4861138b 100644 --- a/src/enable.js +++ b/src/enable.js @@ -9,6 +9,7 @@ const Command = require('./command'); module.exports = class Enable extends Command { + static promiseBased = true; static commandNames = [ "enable" ]; parseOptions(argv) { @@ -24,52 +25,53 @@ Enables the named package(s).\ } run(options) { - let error, settings; - const {callback} = 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; - } - - try { - settings = CSON.readFileSync(configFilePath); - } catch (error) { - callback(`Failed to load \`${configFilePath}\`: ${error.message}`); - return; - } - - const keyPath = '*.core.disabledPackages'; - const disabledPackages = _.valueForKeyPath(settings, keyPath) ?? []; - - const errorPackages = _.difference(packageNames, disabledPackages); - if (errorPackages.length > 0) { - console.log(`Not Disabled:\n ${errorPackages.join('\n ')}`); - } - - // can't enable a package that isn't disabled - packageNames = _.difference(packageNames, errorPackages); - - if (packageNames.length === 0) { - callback("Please specify a package to enable"); - return; - } - - const result = _.difference(disabledPackages, packageNames); - _.setValueForKeyPath(settings, keyPath, result); - - try { - CSON.writeFileSync(configFilePath, settings); - } catch (error) { - callback(`Failed to save \`${configFilePath}\`: ${error.message}`); - return; - } - - console.log(`Enabled:\n ${packageNames.join('\n ')}`); - this.logSuccess(); - return callback(); + return new Promise((resolve, _reject) => { + let settings; + options = this.parseOptions(options.commandArgs); + let packageNames = this.packageNamesFromArgv(options.argv); + + const configFilePath = CSON.resolve(path.join(config.getAtomDirectory(), 'config')); + if (!configFilePath) { + resolve("Could not find config.cson. Run Atom first?"); + return; + } + + try { + settings = CSON.readFileSync(configFilePath); + } catch (error) { + resolve(`Failed to load \`${configFilePath}\`: ${error.message}`); + return; + } + + const keyPath = '*.core.disabledPackages'; + const disabledPackages = _.valueForKeyPath(settings, keyPath) ?? []; + + const errorPackages = _.difference(packageNames, disabledPackages); + if (errorPackages.length > 0) { + console.log(`Not Disabled:\n ${errorPackages.join('\n ')}`); + } + + // can't enable a package that isn't disabled + packageNames = _.difference(packageNames, errorPackages); + + if (packageNames.length === 0) { + resolve("Please specify a package to enable"); + return; + } + + const result = _.difference(disabledPackages, packageNames); + _.setValueForKeyPath(settings, keyPath, result); + + try { + CSON.writeFileSync(configFilePath, settings); + } catch (error) { + resolve(`Failed to save \`${configFilePath}\`: ${error.message}`); + return; + } + + console.log(`Enabled:\n ${packageNames.join('\n ')}`); + this.logSuccess(); + resolve(); + }); } } From 9823c2184936cc9db327fc0a6f8b7d790b9ce07d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Mon, 18 Sep 2023 18:58:54 +0200 Subject: [PATCH 15/64] =?UTF-8?q?Promisification=20of=20featured.js=20Test?= =?UTF-8?q?s=20passing.=20Two=20of=20the=20three=20methods=20could=20immed?= =?UTF-8?q?iately=20be=20turned=20into=20async=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/featured.js | 83 +++++++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/src/featured.js b/src/featured.js index 11b12a0b..35df8da4 100644 --- a/src/featured.js +++ b/src/featured.js @@ -9,6 +9,7 @@ const tree = require('./tree'); module.exports = class Featured extends Command { + static promiseBased = true; static commandNames = [ "featured" ]; parseOptions(argv) { @@ -28,8 +29,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 +37,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 } + } } From a4e6d709ddfea9bd23d4f8fc83dbc15fea68723f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 22 Sep 2023 02:46:30 +0200 Subject: [PATCH 16/64] Promisification of fs.js It was so small that it was easier to do than not do. Only install.js was modified which is currently not tested. --- src/fs.js | 34 ++++++++++++++++++---------------- src/install.js | 16 ++++++---------- 2 files changed, 24 insertions(+), 26 deletions(-) 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/install.js b/src/install.js index 4653a8e5..25c49746 100644 --- a/src/install.js +++ b/src/install.js @@ -108,7 +108,7 @@ package names to install with optional versions using the 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 => fs.cp(source, destination).then(next, next)); commands.push(next => this.buildModuleCache(pack.name, next)); commands.push(next => this.warmCompileCache(pack.name, next)); @@ -607,15 +607,11 @@ Run ppm -v after installing Git to see what version has been detected.\ 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); - } - }); + return fs.cp(cloneDir, targetDir).then(_value => { + if (!options.argv.json) { this.logSuccess(); } + const json = {installPath: targetDir, metadata: data.metadata}; + next(null, json); + }, next); }); const iteratee = (currentData, task, next) => task(currentData, next); From 1093f4a5a568ca679b4a6ca8b1be955829a0307f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 22 Sep 2023 03:11:51 +0200 Subject: [PATCH 17/64] Promisify git.js Tests still passing. The main change is that printVersions in apm-cli could be promisified as a result. --- src/apm-cli.js | 81 +++++++++++++++++++++++++------------------------- src/git.js | 41 ++++++++++++------------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/apm-cli.js b/src/apm-cli.js index bbd81a9a..c8ad21aa 100644 --- a/src/apm-cli.js +++ b/src/apm-cli.js @@ -116,53 +116,52 @@ function showHelp(options) { console.error(help); }; -function printVersions(args) { - return new Promise((resolve, _reject) => { +async function printVersions(args) { const apmVersion = require("../package.json").version ?? ""; const npmVersion = require("npm/package.json").version ?? ""; const nodeVersion = process.versions.node ?? ""; - getPythonVersion().then(pythonVersion => git.getGitVersion(gitVersion => getAtomVersion().then(atomVersion => { - let versions; - if (args.json) { - versions = { - apm: apmVersion, - ppm: apmVersion, - npm: npmVersion, - node: nodeVersion, - atom: atomVersion, - pulsar: atomVersion, - python: pythonVersion, - git: gitVersion, - nodeArch: process.arch - }; - if (config.isWin32()) { - versions.visualStudio = config.getInstalledVisualStudioFlag(); - } - console.log(JSON.stringify(versions)); - } else { - pythonVersion ??= ''; - gitVersion ??= ''; - atomVersion ??= ''; - versions = `\ - ${'ppm'.red} ${apmVersion.red} - ${'npm'.green} ${npmVersion.green} - ${'node'.blue} ${nodeVersion.blue} ${process.arch.blue} - ${'pulsar'.cyan} ${atomVersion.cyan} - ${'python'.yellow} ${pythonVersion.yellow} - ${'git'.magenta} ${gitVersion.magenta}\ - `; + const pythonVersion = await getPythonVersion(); + const gitVersion = await git.getGitVersion(); + const atomVersion = await getAtomVersion(); + let versions; + if (args.json) { + versions = { + apm: apmVersion, + ppm: apmVersion, + npm: npmVersion, + node: nodeVersion, + atom: atomVersion, + pulsar: atomVersion, + python: pythonVersion, + git: gitVersion, + nodeArch: process.arch + }; + if (config.isWin32()) { + versions.visualStudio = config.getInstalledVisualStudioFlag(); + } + console.log(JSON.stringify(versions)); + return; + } - if (config.isWin32()) { - const visualStudioVersion = config.getInstalledVisualStudioFlag() ?? ""; - versions += `\n${'visual studio'.cyan} ${visualStudioVersion.cyan}`; - } + pythonVersion ??= ''; + gitVersion ??= ''; + atomVersion ??= ''; + versions = `\ +${'ppm'.red} ${apmVersion.red} +${'npm'.green} ${npmVersion.green} +${'node'.blue} ${nodeVersion.blue} ${process.arch.blue} +${'pulsar'.cyan} ${atomVersion.cyan} +${'python'.yellow} ${pythonVersion.yellow} +${'git'.magenta} ${gitVersion.magenta}\ +`; - console.log(versions); - } - return resolve(); - }))); - }); + if (config.isWin32()) { + const visualStudioVersion = config.getInstalledVisualStudioFlag() ?? ""; + versions += `\n${'visual studio'.cyan} ${visualStudioVersion.cyan}`; + } + + console.log(versions); }; function getAtomVersion() { diff --git a/src/git.js b/src/git.js index 69023421..dd5f9fae 100644 --- a/src/git.js +++ b/src/git.js @@ -34,7 +34,7 @@ const addPortableGitToEnv = function(env) { }; -const addGitBashToEnv = function(env) { +const addGitBashToEnv = env => { let gitPath; if (env.ProgramFiles) { gitPath = path.join(env.ProgramFiles, 'Git'); @@ -63,28 +63,29 @@ exports.addGitToEnv = function(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); + }); }); - }); + })); }; From df0adba707e6772bc59f43034b0d53767ebfe595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 22 Sep 2023 03:34:51 +0200 Subject: [PATCH 18/64] Promisification of init.js pt.1 Tests passing. run could be asyncified right away. I still want to tackle generateFromTemplate which is ugly. --- src/init.js | 124 ++++++++++++++++++++++++---------------------------- 1 file changed, 56 insertions(+), 68 deletions(-) diff --git a/src/init.js b/src/init.js index 7f0d13bc..7c1934ee 100644 --- a/src/init.js +++ b/src/init.js @@ -8,6 +8,7 @@ const fs = require('./fs'); module.exports = class Init extends Command { + static promiseBased = true; static commandNames = [ "init" ]; constructor() { @@ -46,87 +47,89 @@ 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.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 + } + 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 } - } else if ((options.argv.theme != null ? options.argv.theme.length : undefined) > 0) { + 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 != null ? options.argv.language.length : undefined) > 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) { + convertPackage(sourcePath, destinationPath) { if (!destinationPath) { - callback("Specify directory to create package in using --package"); - return; + return Promise.reject("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 { + return new Promise((resolve, reject) => { + converter.convert(error => { + if (error != null) { + return void reject(error); + } + destinationPath = path.resolve(destinationPath); const templatePath = path.resolve(__dirname, '..', 'templates', 'bundle'); this.generateFromTemplate(destinationPath, templatePath); - return callback(); - } + resolve(); + }); }); } - convertTheme(sourcePath, destinationPath, callback) { + convertTheme(sourcePath, destinationPath) { if (!destinationPath) { - callback("Specify directory to create theme in using --theme"); - return; + return void Promise.reject("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 { + return new Promise((resolve, reject) => { + converter.convert(error => { + if (error != null) { + return void reject(error); + } + 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(); - } + resolve(); + }); }); } @@ -170,18 +173,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 +196,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 +210,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() : "_"); } } From 47f7dbab424b7b2f913de88e64531f6f47dfdc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 22 Sep 2023 03:38:10 +0200 Subject: [PATCH 19/64] init.js pt2: make generateFromTemplate look a bit more sensible Tests still passing. --- src/init.js | 50 ++++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/init.js b/src/init.js index 7c1934ee..8e2738da 100644 --- a/src/init.js +++ b/src/init.js @@ -134,37 +134,35 @@ on the option selected.\ } 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) { From 6da61f549f700879122f50c5ac515452efc07fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 22 Sep 2023 03:48:39 +0200 Subject: [PATCH 20/64] Asyncification of link.js Tests still passing. It was so banal that it could be asyncified right away. As a positive side effect, linkPackage in develop.js doesn't have to be wrapped with the Promise constructor anymore. --- src/develop.js | 9 +++------ src/link.js | 23 +++++++++-------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/develop.js b/src/develop.js index 93498311..8bcead74 100644 --- a/src/develop.js +++ b/src/develop.js @@ -101,13 +101,10 @@ cmd-shift-o to run the package out of the newly cloned repository.\ } linkPackage(packageDirectory, options) { - return new Promise((resolve, _reject) => { - const linkOptions = _.clone(options); - linkOptions.callback = resolve; + const linkOptions = _.clone(options); - linkOptions.commandArgs = [packageDirectory, '--dev']; - return void new Link().run(linkOptions); - }); + linkOptions.commandArgs = [packageDirectory, '--dev']; + return new Link().run(linkOptions); } run(options) { diff --git a/src/link.js b/src/link.js index 9a5f09a2..840093f5 100644 --- a/src/link.js +++ b/src/link.js @@ -10,6 +10,7 @@ const fs = require('./fs'); module.exports = class Link extends Command { + static promiseBased = true; static commandNames = [ "link", "ln" ]; parseOptions(argv) { @@ -28,9 +29,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 +37,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 +54,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 } } } From 34b00e95186601f4aa1eb8582822c2500f85c75a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 22 Sep 2023 11:47:56 +0200 Subject: [PATCH 21/64] Asyncification of links.js This one has no tests but the code is banal. --- src/links.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/links.js b/src/links.js index e1d32094..215695ea 100644 --- a/src/links.js +++ b/src/links.js @@ -10,6 +10,7 @@ const tree = require('./tree'); module.exports = class Links extends Command { + static promiseBased = true; static commandNames = [ "linked", "links", "lns" ]; constructor() { @@ -58,11 +59,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(); } } From 8aea62aab57418bf6732cf0eb0930e17a500d12f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 22 Sep 2023 12:36:58 +0200 Subject: [PATCH 22/64] Promisification of list.js All tests passing, some code could be asyncified again. It's important to note that some errors might slip through the commands but the API of run is not stable yet either way. --- src/disable.js | 15 ++---- src/list.js | 130 ++++++++++++++++++++----------------------------- 2 files changed, 57 insertions(+), 88 deletions(-) diff --git a/src/disable.js b/src/disable.js index 1e646e59..370214b0 100644 --- a/src/disable.js +++ b/src/disable.js @@ -25,7 +25,7 @@ Disables the named package(s).\ return options.alias('h', 'help').describe('help', 'Print this usage message'); } - getInstalledPackages() { + async getInstalledPackages() { const options = { argv: { theme: false, @@ -34,15 +34,10 @@ Disables the named package(s).\ }; const lister = new List(); - return new Promise((resolve, _reject) => - void lister.listBundledPackages(options, (_error, core_packages) => - void lister.listDevPackages(options, (_error, dev_packages) => - void lister.listUserPackages(options, (_error, user_packages) => - void resolve(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) { diff --git a/src/list.js b/src/list.js index d606ae29..6e2d1561 100644 --- a/src/list.js +++ b/src/list.js @@ -13,6 +13,7 @@ const {getRepository} = require("./packages"); module.exports = class List extends Command { + static promiseBased = true; static commandNames = [ "list", "ls" ]; constructor() { @@ -129,17 +130,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 +149,55 @@ 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); + listBundledPackages(options) { + return new Promise((resolve, _reject) => { + config.getResourcePath(resourcePath => { + 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)); + + if (!options.argv.bare && !options.argv.json) { + console.log(`${`Built-in Atom ${options.argv.themes ? 'Themes' : 'Packages'}`.cyan} (${packagesMeta.length})`); } - return result; - })()); - - packages = packages.filter(metadata => { - return this.isPackageVisible(options, metadata); + + resolve(packagesMeta); }); - - 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})`); - } - } - - return callback?.(null, packages); }); } 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 +205,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); } } From 17a9ace0f25004474cb4b6600ad9f76cd823c0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 22 Sep 2023 16:25:59 +0200 Subject: [PATCH 23/64] Asyncification and polishment of package-converter.js Tests passing. init.js could also see some refinement. --- src/init.js | 24 ++---- src/package-converter.js | 160 +++++++++++++++++++-------------------- 2 files changed, 86 insertions(+), 98 deletions(-) diff --git a/src/init.js b/src/init.js index 8e2738da..5b7a8304 100644 --- a/src/init.js +++ b/src/init.js @@ -50,7 +50,7 @@ on the option selected.\ async run(options) { let templatePath; 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).catch(error => error); // rewire the error as a value for te time being } @@ -72,7 +72,7 @@ on the option selected.\ this.generateFromTemplate(themePath, templatePath); return; } - if ((options.argv.language != null ? options.argv.language.length : undefined) > 0) { + 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}`); @@ -89,25 +89,17 @@ on the option selected.\ return 'You must specify either --package, --theme or --language to `ppm init`'; // errors as values... } - convertPackage(sourcePath, destinationPath) { + async convertPackage(sourcePath, destinationPath) { if (!destinationPath) { - return Promise.reject("Specify directory to create package in using --package"); + throw "Specify directory to create package in using --package"; } const PackageConverter = require('./package-converter'); const converter = new PackageConverter(sourcePath, destinationPath); - return new Promise((resolve, reject) => { - converter.convert(error => { - if (error != null) { - return void reject(error); - } - - destinationPath = path.resolve(destinationPath); - const templatePath = path.resolve(__dirname, '..', 'templates', 'bundle'); - this.generateFromTemplate(destinationPath, templatePath); - resolve(); - }); - }); + await converter.convert(); + destinationPath = path.resolve(destinationPath); + const templatePath = path.resolve(__dirname, '..', 'templates', 'bundle'); + this.generateFromTemplate(destinationPath, templatePath); } convertTheme(sourcePath, destinationPath) { diff --git a/src/package-converter.js b/src/package-converter.js index 8f272185..46faaf91 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,44 @@ class PackageConverter { return downloadUrl += '/archive/master.tar.gz'; } - downloadBundle(callback) { - const tempPath = temp.mkdirSync('atom-bundle-'); - const requestOptions = {url: this.getDownloadUrl()}; - return request.createReadStream(requestOptions, readStream => { - readStream.on('response', function({headers, statusCode}) { - if (statusCode !== 200) { - return callback(`Download failed (${headers.status})`); - } - }); - - return readStream.pipe(zlib.createGunzip()).pipe(tar.extract({cwd: tempPath})) - .on('error', error => callback(error)) - .on('end', () => { - const sourcePath = path.join(tempPath, fs.readdirSync(tempPath)[0]); - return this.copyDirectories(sourcePath, callback); + downloadBundle() { + return new Promise((resolve, reject) => { + const tempPath = temp.mkdirSync('atom-bundle-'); + const requestOptions = {url: this.getDownloadUrl()}; + return request.createReadStream(requestOptions, readStream => { + readStream.on('response', ({headers, statusCode}) => { + if (statusCode !== 200) { + reject(`Download failed (${headers.status})`); + } + }); + + return 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]); + 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 +121,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 +177,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 +197,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 +226,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; } } From 1cb698be81052fa63969340b7366128a6377085d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 22 Sep 2023 16:27:54 +0200 Subject: [PATCH 24/64] Tiny refinement of packages.js All tests passing, this one was banal. --- src/packages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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"; } }; From 808201873d61ab5715c1ec880244fe52ffc10c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sat, 23 Sep 2023 04:05:05 +0200 Subject: [PATCH 25/64] Promisification of publish.js this was a large one but also rather educational one. Tests still passing, although their amount is worryingly low so probably some further testing will be necessary. --- src/publish.js | 447 +++++++++++++++++++++++++------------------------ 1 file changed, 227 insertions(+), 220 deletions(-) diff --git a/src/publish.js b/src/publish.js index 99f827ce..ecff25e0 100644 --- a/src/publish.js +++ b/src/publish.js @@ -15,6 +15,7 @@ const request = require('./request'); module.exports = class Publish extends Command { + static promiseBased = true; static commandNames = [ "publish" ]; constructor() { @@ -57,21 +58,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 +83,16 @@ 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 resolves to whatever error logCommandResults + // can produce. + 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(resolve, ...args); + }); }); } @@ -96,10 +103,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 +115,49 @@ 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 = {}; } - if (error != null) { - return callback(error); - } else { - return callback(null, response.statusCode === 200); - } + // + // return value - A Promise that can reject with an error or resolve to + // a boolean value. + packageExists(packageName) { + return new Promise((resolve, reject) => { + Login.getTokenOrLogin((error, token) => { + if (error != null) { return void reject(error); } + + const requestSettings = { + url: `${config.getAtomPackagesUrl()}/${packageName}`, + json: true, + headers: { + authorization: token + } + }; + request.get(requestSettings, (error, response, body) => { + body ??= {}; + if (error != null) { + return void reject(error); + } + resolve(response.statusCode === 200); + }); }); }); } @@ -156,28 +165,29 @@ 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 (!repository) { + throw 'Unable to parse repository name/owner from package.json repository field'; + } + + process.stdout.write(`Registering ${pack.name} `); + return new Promise((resolve, reject) => { + Login.getTokenOrLogin((error, token) => { if (error != null) { this.logFailure(); - callback(error); + reject(error); return; } @@ -192,17 +202,18 @@ have published it.\ } }; 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); }); }); }); @@ -212,36 +223,39 @@ have published it.\ // // 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; - } - - 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 value - A Promise that rejects with an error or resolves without a value. + createPackageVersion(packageName, tag, options) { + return new Promise((resolve, reject) => { + Login.getTokenOrLogin((error, token) => { if (error != null) { - return callback(error); - } else if (response.statusCode !== 201) { - const message = request.getErrorMessage(body, error); - return callback(`Creating new version failed: ${message}`); - } else { - return callback(); + reject(error); + return; } + + const requestSettings = { + url: `${config.getAtomPackagesUrl()}/${packageName}/versions`, + json: true, + qs: { + tag, + rename: options.rename + }, + headers: { + authorization: token + } + }; + request.post(requestSettings, (error, response, body) => { + body ??= {}; + if (error != null) { + return void reject(error); + } + if (response.statusCode !== 201) { + const message = request.getErrorMessage(body, error); + return void reject(`Creating new version failed: ${message}`); + } + + resolve(); + }); }); }); } @@ -251,24 +265,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 +288,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 +304,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 +334,54 @@ 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) { - this.logFailure(); - return callback(error); - } + const message = `Renaming ${pack.name} to ${name} `; + process.stdout.write(message); + try { + this.setPackageName(pack, name); + } catch (error) { + this.logFailure(); + throw error; + } - 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) { + return new Promise((resolve, reject) => { + config.getSetting('git', gitCommand => { + gitCommand ??= 'git'; + return this.spawn(gitCommand, ['add', 'package.json'], (code, stderr, stdout) => { + stderr ??= ''; + stdout ??= ''; + if (code !== 0) { + this.logFailure(); + const addOutput = `${stdout}\n${stderr}`.trim(); + return void reject(`\`git add package.json\` failed: ${addOutput}`); + } + + this.spawn(gitCommand, ['commit', '-m', message], (code, stderr, stdout) => { + stderr ??= ''; + stdout ??= ''; + if (code === 0) { + this.logSuccess(); + resolve(); + } else { this.logFailure(); - const addOutput = `${stdout}\n${stderr}`.trim(); - return callback(`\`git add package.json\` failed: ${addOutput}`); + const commitOutput = `${stdout}\n${stderr}`.trim(); + reject(`Failed to commit package.json: ${commitOutput}`); } - - 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}`); - } - }); }); }); }); - } 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 +422,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'; } } } From 63ba42c33419174ab09eb95b40d067822de28780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sat, 23 Sep 2023 22:53:33 +0200 Subject: [PATCH 26/64] Promisification of search.js Tests passing. It's a rather small change but the run method could be asyncified at least. --- src/search.js | 86 +++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/search.js b/src/search.js index 6186b3f5..7eed53d5 100644 --- a/src/search.js +++ b/src/search.js @@ -10,6 +10,7 @@ const {isDeprecatedPackage} = require('./deprecated-packages'); module.exports = class Search extends Command { + static promiseBased = true; static commandNames = [ "search" ]; @@ -28,7 +29,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 +45,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 +77,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(); + } } } From aee88bba957e94b62ae81193b0b959a7aaf89413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sat, 23 Sep 2023 23:53:08 +0200 Subject: [PATCH 27/64] Fix with auth.js There was a mistake in the porting, and perhaps it's good to make it more apparent that auth.saveToken really wraps an async call --- src/auth.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/auth.js b/src/auth.js index 205c70c4..270ebb82 100644 --- a/src/auth.js +++ b/src/auth.js @@ -22,11 +22,11 @@ module.exports = { async getToken() { try { const token = await keytar.findPassword(tokenName); - if (token) { - return token; + if (!token) { + throw 'Missing token in keytar.'; } - return Promise.reject(); + return token; } catch { const token = process.env.ATOM_ACCESS_TOKEN; if (token) { @@ -43,7 +43,7 @@ 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); } }; From 1f3ecedff3f974b1ead494f2c424c68cd4664206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sat, 23 Sep 2023 23:55:49 +0200 Subject: [PATCH 28/64] Make saveToken more synchronized It really wraps a Promise that has async operations in it; it probably shouldn't throw a synchronous error either. --- src/login.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/login.js b/src/login.js index 9da28b03..308722c1 100644 --- a/src/login.js +++ b/src/login.js @@ -87,11 +87,11 @@ copy the token and paste it below when prompted. }); } - 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); } From a6a5410e6304f774c5fc84b7751a8576cef4c936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sat, 23 Sep 2023 23:58:20 +0200 Subject: [PATCH 29/64] Promisify star.js There were no tests for this and currently I couldn't get keytar working in my environment so I'm relying on common sense. --- src/star.js | 67 ++++++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/src/star.js b/src/star.js index 69cb14d3..81fa926a 100644 --- a/src/star.js +++ b/src/star.js @@ -15,6 +15,7 @@ const request = require('./request'); module.exports = class Star extends Command { + static promiseBased = true; static commandNames = [ "star" ]; parseOptions(argv) { @@ -32,8 +33,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 +45,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 +89,36 @@ 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); } + return new Promise((resolve, _reject) => { + Login.getTokenOrLogin((error, token) => { + if (error != null) { return void resolve(error); } // error as return value - const starOptions = { - ignoreUnpublishedPackages: options.argv.installed, - token - }; + const starOptions = { + ignoreUnpublishedPackages: options.argv.installed, + token + }; - const commands = packageNames.map(packageName => { - return callback => this.starPackage(packageName, starOptions, callback); + const commands = packageNames.map(packageName => { + return callback => this.starPackage(packageName, starOptions).then(callback, callback); + }); + async.waterfall(commands, resolve); }); - return async.waterfall(commands, callback); }); } } From 02d399cf7625e3476f6a358d4cb1470c2072b66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sun, 24 Sep 2023 00:31:01 +0200 Subject: [PATCH 30/64] Asyncification of stars.js All tests still passing (which actually means not all is lost with Install either, because it uses Install under the hood in some cases). Some of the commands had to be wrapped with the Promise constructor, others could be redone as async (as a result). :) --- src/stars.js | 105 ++++++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/src/stars.js b/src/stars.js index e7035fb2..1c251822 100644 --- a/src/stars.js +++ b/src/stars.js @@ -11,6 +11,7 @@ const tree = require('./tree'); module.exports = class Stars extends Command { + static promiseBased = true; static commandNames = [ "stars", "starred" ]; parseOptions(argv) { @@ -32,64 +33,65 @@ List or install starred Atom packages and themes.\ return options.boolean('json').describe('json', 'Output packages as a JSON array'); } - getStarredPackages(user, atomVersion, callback) { + 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); } + return this.requestStarredPackages(requestSettings); + } + + requestSettings.url = `${config.getAtomApiUrl()}/stars`; + return new Promise((resolve, reject) => { + Login.getTokenOrLogin((error, token) => { + if (error != null) { return void reject(error); } requestSettings.headers = {authorization: token}; - return this.requestStarredPackages(requestSettings, callback); + resolve(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 Promise((resolve, _reject) => + void new Install().run({commandArgs, callback: resolve}) + ); } - 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 +102,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); } } From 356b6ef83dd5e26d55822bf43de153d4b3876fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sun, 24 Sep 2023 11:47:50 +0200 Subject: [PATCH 31/64] =?UTF-8?q?Promisification=20of=20test.js,=20fixing?= =?UTF-8?q?=20related=20test=20It=20simply=20didn't=20wait=20for=20the=20c?= =?UTF-8?q?allback=20to=20run...=20=F0=9F=A4=A6=E2=80=8D=E2=99=82=EF=B8=8F?= =?UTF-8?q?=20The=20tests=20are=20passing,=20some=20trivial=20promisificat?= =?UTF-8?q?ion=20done=20in=20the=20command.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {spec2 => spec}/test-spec.js | 1 + src/test.js | 70 +++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 33 deletions(-) rename {spec2 => spec}/test-spec.js (96%) diff --git a/spec2/test-spec.js b/spec/test-spec.js similarity index 96% rename from spec2/test-spec.js rename to spec/test-spec.js index 6a4ea894..933d9bf6 100644 --- a/spec2/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/src/test.js b/src/test.js index 8b180e22..a91a4a03 100644 --- a/src/test.js +++ b/src/test.js @@ -9,6 +9,7 @@ const fs = require('./fs'); module.exports = class Test extends Command { + static promiseBased = true; static commandNames = [ "test" ]; parseOptions(argv) { @@ -28,7 +29,6 @@ to the current working directory).\ run(options) { let atomCommand; - const {callback} = options; options = this.parseOptions(options.commandArgs); const {env} = process; @@ -41,38 +41,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 + }); + } + }); } } From c6de74f838faa3d0a59c9e0304958d0f73c24a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sun, 24 Sep 2023 13:21:17 +0200 Subject: [PATCH 32/64] Asyncification of TextMate theme conversion All tests passing. Some synchronization has been fixed and converTheme got nicer and better in init.js. --- src/init.js | 24 +++++---------- src/text-mate-theme.js | 67 ++++++++++++++++++++---------------------- src/theme-converter.js | 53 +++++++++++++++------------------ 3 files changed, 63 insertions(+), 81 deletions(-) diff --git a/src/init.js b/src/init.js index 5b7a8304..8e4dc232 100644 --- a/src/init.js +++ b/src/init.js @@ -102,27 +102,19 @@ on the option selected.\ this.generateFromTemplate(destinationPath, templatePath); } - convertTheme(sourcePath, destinationPath) { + async convertTheme(sourcePath, destinationPath) { if (!destinationPath) { - return void Promise.reject("Specify directory to create theme in using --theme"); + throw "Specify directory to create theme in using --theme"; } const ThemeConverter = require('./theme-converter'); const converter = new ThemeConverter(sourcePath, destinationPath); - return new Promise((resolve, reject) => { - converter.convert(error => { - if (error != null) { - return void reject(error); - } - - 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')); - resolve(); - }); - }); + 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) { 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(); - }); } }; From f2306f733e35b6e658c46d1a87d63057b3ebe16d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sun, 24 Sep 2023 13:35:32 +0200 Subject: [PATCH 33/64] Prettifying tree.js All tests passing. This is not strictly an async-related change because this utility is mostly a modified forEach but the code could be improved nevertheless. Maybe it can even be eliminated eventually. --- src/tree.js | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) 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)}`) + }); }; From 325bf0af8d276df972b5f3e0bd8369a3ea45fbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sun, 24 Sep 2023 15:53:06 +0200 Subject: [PATCH 34/64] Promisification of uninstall.js Tests are still passing. This async.eachSeries is quite bothering, it would be great to eliminate it eventually. --- src/uninstall.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/uninstall.js b/src/uninstall.js index b1864e24..6eef08c5 100644 --- a/src/uninstall.js +++ b/src/uninstall.js @@ -13,6 +13,7 @@ const request = require('./request'); module.exports = class Uninstall extends Command { + static promiseBased = true; static commandNames = [ "deinstall", "delete", "erase", "remove", "rm", "uninstall" ]; parseOptions(argv) { @@ -37,10 +38,11 @@ Delete the installed package(s) from the ~/.pulsar/packages directory.\ } } - registerUninstall({packageName, packageVersion}, callback) { - if (!packageVersion) { return callback(); } + async registerUninstall({packageName, packageVersion}) { + if (!packageVersion) { return; } - return void auth.getToken().then(token => { + try { + const token = await auth.getToken(); const requestOptions = { url: `${config.getAtomPackagesUrl()}/${packageName}/versions/${packageVersion}/events/uninstall`, json: true, @@ -48,19 +50,18 @@ Delete the installed package(s) from the ~/.pulsar/packages directory.\ authorization: token } }; - - return request.post(requestOptions, (_error, _response, _body) => callback()); - }, 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'); @@ -107,6 +108,10 @@ Delete the installed package(s) from the ~/.pulsar/packages directory.\ } } - return async.eachSeries(uninstallsToRegister, this.registerUninstall.bind(this), () => callback(uninstallError)); + return new Promise((resolve, _reject) => + void async.eachSeries(uninstallsToRegister, (data, errorHandler) => + void this.registerUninstall(data).then(errorHandler), () => resolve(uninstallError) + ) + ); } } From dba039656a3b998d8a09a95d521d8cae721626dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sun, 24 Sep 2023 17:02:05 +0200 Subject: [PATCH 35/64] Asyncification of unlink.js All tests passing. This could be plain sequential code as well but it's more convenient to stick to the async interface. --- src/unlink.js | 66 ++++++++++++++++++++------------------------------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/src/unlink.js b/src/unlink.js index 880618a3..a4c67c5c 100644 --- a/src/unlink.js +++ b/src/unlink.js @@ -10,6 +10,7 @@ const fs = require('./fs'); module.exports = class Unlink extends Command { + static promiseBased = true; static commandNames = [ "unlink" ]; constructor() { @@ -51,67 +52,52 @@ Run \`ppm links\` to view all the currently linked packages.\ } } - 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 } } } From c016e26cd8e00f7124aa3d416b3bfdca00ef7ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= <2colours@github.com> Date: Sun, 24 Sep 2023 21:26:14 +0200 Subject: [PATCH 36/64] Asyncification of unpublish.js Test passes after the sufficient modification. The code is basically what we can expect as final state overall. --- spec/unpublish-spec.js | 10 +---- src/unpublish.js | 93 +++++++++++++++++++++--------------------- 2 files changed, 48 insertions(+), 55 deletions(-) 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/unpublish.js b/src/unpublish.js index 9ca4ff1b..f48c4833 100644 --- a/src/unpublish.js +++ b/src/unpublish.js @@ -12,6 +12,7 @@ const request = require('./request'); module.exports = class Unpublish extends Command { + static promiseBased = true; static commandNames = [ "unpublish" ]; parseOptions(argv) { @@ -31,13 +32,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().then(token => { + try { + const token = await auth.getToken(); const options = { url: `${config.getAtomPackagesUrl()}/${packageName}`, headers: { @@ -48,62 +50,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 void callback(error); - } else if (response.statusCode !== 204) { - this.logFailure(); - const message = body.message ?? body.error ?? body; - return void 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 void callback(); - } + resolve(); + }); }); - }, error => { + } catch (error) { this.logFailure(); - callback(error); - }); + 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._; @@ -120,15 +122,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 } } From 0ef666235e655a43b23d2ca7a7568f271cb2946a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Tue, 26 Sep 2023 00:23:46 +0200 Subject: [PATCH 37/64] Promisification of unstar.js Tests passing but a properly executed unstar operation throws a weird error (just like now) because the backend sends an unexpected HTTP status code. The code itself is much like star.js. --- src/unstar.js | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/unstar.js b/src/unstar.js index 35e54aae..11529587 100644 --- a/src/unstar.js +++ b/src/unstar.js @@ -9,6 +9,7 @@ const request = require('./request'); module.exports = class Unstar extends Command { + static promiseBased = true; static commandNames = [ "unstar" ]; parseOptions(argv) { @@ -25,7 +26,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 +36,42 @@ 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); } + return new Promise((resolve, _reject) => { + Login.getTokenOrLogin((error, token) => { + if (error != null) { return void resolve(error); } // error as return value atm - const commands = packageNames.map(packageName => { - return callback => this.starPackage(packageName, token, callback); + const commands = packageNames.map(packageName => { + return callback => this.starPackage(packageName, token).then(callback, callback); + }); + return async.waterfall(commands, resolve); }); - return async.waterfall(commands, callback); }); } } From 8fc1243e65150dbfaadd65d13c0d0aaf4f1f8c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Tue, 26 Sep 2023 00:48:51 +0200 Subject: [PATCH 38/64] Asyncification of view.js Tests still passing. This means that docs.js could be completely asyncified. --- src/docs.js | 51 +++++++++--------- src/view.js | 145 +++++++++++++++++++++++++++------------------------- 2 files changed, 99 insertions(+), 97 deletions(-) diff --git a/src/docs.js b/src/docs.js index a372d4b6..eaac33cd 100644 --- a/src/docs.js +++ b/src/docs.js @@ -27,31 +27,30 @@ Open a package's homepage in the default browser.\ return open(repositoryUrl); } - run(options) { - return new Promise((resolve, _reject) => { - options = this.parseOptions(options.commandArgs); - const [packageName] = options.argv._; - - if (!packageName) { - resolve("Missing required package name"); - return; - } - - this.getPackage(packageName, options, (error, pack) => { - let repository; - if (error != null) { return void resolve(error); } - - if (repository = this.getRepository(pack)) { - if (options.argv.print) { - console.log(repository); - } else { - this.openRepositoryUrl(repository); - } - return void resolve(); - } else { - return void resolve(`Package \"${packageName}\" does not contain a repository URL`); - } - }); - }); + async run(options) { + options = this.parseOptions(options.commandArgs); + const [packageName] = options.argv._; + + if (!packageName) { + return "Missing required package name"; //error as return value + } + + 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/view.js b/src/view.js index 32ef5846..6352f29c 100644 --- a/src/view.js +++ b/src/view.js @@ -10,6 +10,7 @@ const tree = require('./tree'); module.exports = class View extends Command { + static promiseBased = true; static commandNames = [ "view", "show" ]; parseOptions(argv) { @@ -26,38 +27,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 +69,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(); } } From 9139db1e41ce8107a5d25edec68798ea29b1981e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Tue, 26 Sep 2023 01:11:33 +0200 Subject: [PATCH 39/64] Promisification of rebuild.js All tests passing. This one is interweaved with config.js and the Command base class so it's still mostly a trivial Promise wrapper. --- src/rebuild.js | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/rebuild.js b/src/rebuild.js index f32f308a..984c9b68 100644 --- a/src/rebuild.js +++ b/src/rebuild.js @@ -7,10 +7,10 @@ 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 { + static promiseBased = true; static commandNames = [ "rebuild" ]; constructor() { @@ -35,7 +35,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,25 +47,32 @@ 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; 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) { + return new Promise((resolve, _reject) => { + config.loadNpm((_error, npm) => { + this.npm = npm; + this.loadInstalledAtomMetadata(() => { + this.forkNpmRebuild(options).then(() => { this.logSuccess(); - return callback(); - } else { + resolve(); + }, stderr => { this.logFailure(); - return callback(stderr); - } + resolve(stderr); //errors as return values atm + }); }); }); }); From 34c73ba0fa85e65fb77c51ef05bdb78fb88af1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Wed, 27 Sep 2023 00:45:58 +0200 Subject: [PATCH 40/64] Promise-based rework of command.js All tests still passing. All the related calls have been modified. There is a reason I didn't hurry to do this... but it will allow for nicer interfaces. Actually, it already let me keep resolve and reject separate for a longer time. --- src/ci.js | 8 +-- src/clean.js | 4 +- src/command.js | 135 +++++++++++++++++++++---------------------------- src/dedupe.js | 4 +- src/develop.js | 8 +-- src/install.js | 19 +++---- src/publish.js | 7 ++- src/rebuild.js | 2 +- src/unlink.js | 2 +- src/upgrade.js | 2 +- 10 files changed, 87 insertions(+), 104 deletions(-) diff --git a/src/ci.js b/src/ci.js index d76f4fc2..d965dd61 100644 --- a/src/ci.js +++ b/src/ci.js @@ -61,9 +61,9 @@ but cannot be used to install new packages or dependencies.\ const installOptions = {env, streaming: options.argv.verbose}; - return new Promise((resolve, _reject) => + return new Promise((resolve, reject) => void this.fork(this.atomNpmPath, installArgs, installOptions, (...args) => - void this.logCommandResults(resolve, ...args) + void this.logCommandResults(...args).then(resolve, reject) ) ) } @@ -73,8 +73,8 @@ but cannot be used to install new packages or dependencies.\ const commands = []; commands.push(callback => config.loadNpm((error, npm) => { this.npm = npm; callback(error); })); - commands.push(cb => this.loadInstalledAtomMetadata(cb)); - commands.push(cb => this.installModules(opts).then(cb)); + commands.push(cb => this.loadInstalledAtomMetadata().then(cb, cb)); + commands.push(cb => this.installModules(opts).then(cb, cb)); const iteratee = (item, next) => item(next); return new Promise((resolve, _reject) => void async.mapSeries(commands, iteratee, err => diff --git a/src/clean.js b/src/clean.js index 6aa54fb7..bd82b174 100644 --- a/src/clean.js +++ b/src/clean.js @@ -36,9 +36,9 @@ as a dependency in the package.json file.\ run(_options) { process.stdout.write("Removing extraneous modules "); - return new Promise((resolve, _reject) => + return new Promise((resolve, reject) => void this.fork(this.atomNpmPath, ['prune'], (...args) => - void this.logCommandResults(resolve, ...args) + void this.logCommandResults(...args).then(resolve, reject) ) ); } diff --git a/src/command.js b/src/command.js index 4972bc5f..ef5f5745 100644 --- a/src/command.js +++ b/src/command.js @@ -13,33 +13,33 @@ class Command { 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 +52,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 +60,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 +102,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(resourcePath => { this.resourcePath = resourcePath; resolve(this.resourcePath); }); + } + }); } addBuildEnvVars(env) { @@ -163,40 +150,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/dedupe.js b/src/dedupe.js index 8c8c3c3e..21b29113 100644 --- a/src/dedupe.js +++ b/src/dedupe.js @@ -39,7 +39,7 @@ This command is experimental.\ process.stdout.write('Deduping modules '); this.forkDedupeCommand(options, (...args) => { - this.logCommandResults(callback, ...args); + this.logCommandResults(...args).then(callback, callback); }); } @@ -75,7 +75,7 @@ This command is experimental.\ this.createAtomDirectories(); const commands = []; - commands.push(callback => this.loadInstalledAtomMetadata(callback)); + commands.push(callback => this.loadInstalledAtomMetadata().then(callback, callback)); commands.push(callback => this.dedupeModules(options, callback)); return async.waterfall(commands, callback); } diff --git a/src/develop.js b/src/develop.js index 8bcead74..7356ff77 100644 --- a/src/develop.js +++ b/src/develop.js @@ -73,7 +73,7 @@ cmd-shift-o to run the package out of the newly cloned repository.\ } cloneRepository(repoUrl, packageDirectory, options) { - return new Promise((resolve, _reject) => { + return new Promise((resolve, reject) => { return config.getSetting('git', command => { if (command == null) { command = 'git'; } const args = ['clone', '--recursive', repoUrl, packageDirectory]; @@ -81,10 +81,10 @@ cmd-shift-o to run the package out of the newly cloned repository.\ git.addGitToEnv(process.env); return this.spawn(command, args, (...args) => { if (options.argv.json) { - return void this.logCommandResultsIfFail(resolve, ...args); + return void this.logCommandResultsIfFail(...args).then(resolve, reject); } - return void this.logCommandResults(resolve, ...args); + this.logCommandResults(...args).then(resolve, reject); }); }); }); @@ -124,7 +124,7 @@ cmd-shift-o to run the package out of the newly cloned repository.\ return new Promise((resolve, _reject) => { this.getRepositoryUrl(packageName).then(repoUrl => { const tasks = []; - tasks.push(callback => this.cloneRepository(repoUrl, packageDirectory, options).then(callback)); + tasks.push(callback => this.cloneRepository(repoUrl, packageDirectory, options).then(callback, callback)); tasks.push(callback => this.installDependencies(packageDirectory, options).then(callback)); diff --git a/src/install.js b/src/install.js index 25c49746..3e7bef75 100644 --- a/src/install.js +++ b/src/install.js @@ -176,9 +176,9 @@ Run ppm -v after installing Git to see what version has been detected.\ return this.forkInstallCommand(options, (...args) => { if (options.argv.json) { - return this.logCommandResultsIfFail(callback, ...args); + return this.logCommandResultsIfFail(...args).then(callback, callback); } else { - return this.logCommandResults(callback, ...args); + return this.logCommandResults(...args).then(callback, callback); } }); } @@ -463,7 +463,7 @@ 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 this.logCommandResults(...args).then(callback, callback); }); } @@ -488,7 +488,7 @@ Run ppm -v after installing Git to see what version has been detected.\ warmCompileCache(packageName, callback) { const packageDirectory = path.join(this.atomPackagesDirectory, packageName); - return this.getResourcePath(resourcePath => { + return this.getResourcePath().then(resourcePath => { try { const CompileCache = require(path.join(resourcePath, 'src', 'compile-cache')); @@ -507,7 +507,7 @@ Run ppm -v after installing Git to see what version has been detected.\ } isBundledPackage(packageName, callback) { - return this.getResourcePath(function(resourcePath) { + return this.getResourcePath().then(resourcePath => { let atomMetadata; try { atomMetadata = JSON.parse(fs.readFileSync(path.join(resourcePath, 'package.json'))); @@ -655,7 +655,7 @@ Run ppm -v after installing Git to see what version has been detected.\ const Develop = require('./develop'); const develop = new Develop(); - return develop.cloneRepository(url, cloneDir, options, err => callback(err)); + return develop.cloneRepository(url, cloneDir, options).then(callback, callback); } installGitPackageDependencies(directory, options, callback) { @@ -684,9 +684,10 @@ Run ppm -v after installing Git to see what version has been detected.\ if (options.argv.check) { config.loadNpm((error, npm) => { this.npm = npm; - return this.loadInstalledAtomMetadata(() => { + const cb = () => { return this.checkNativeBuildTools(callback); - }); + }; + return this.loadInstalledAtomMetadata().then(cb, cb); }); return; } @@ -739,7 +740,7 @@ 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())); + commands.push(callback => this.loadInstalledAtomMetadata().then(() => callback(), () => callback())); packageNames.forEach(packageName => commands.push(callback => installPackage(packageName, callback))); const iteratee = (item, next) => item(next); return async.mapSeries(commands, iteratee, function(err, installedPackagesInfo) { diff --git a/src/publish.js b/src/publish.js index ecff25e0..7c2bcbdb 100644 --- a/src/publish.js +++ b/src/publish.js @@ -84,14 +84,13 @@ have published it.\ // tag - The tag to push. // pack - The package metadata. // - // return value - A Promise that resolves to whatever error logCommandResults - // can produce. + // 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 new Promise((resolve, _reject) => { + return new Promise((resolve, reject) => { this.spawn('git', pushArgs, (...args) => { - this.logCommandResults(resolve, ...args); + this.logCommandResults(...args).then(resolve, reject); }); }); } diff --git a/src/rebuild.js b/src/rebuild.js index 984c9b68..04bc9513 100644 --- a/src/rebuild.js +++ b/src/rebuild.js @@ -65,7 +65,7 @@ All the modules will be rebuilt if no module names are specified.\ return new Promise((resolve, _reject) => { config.loadNpm((_error, npm) => { this.npm = npm; - this.loadInstalledAtomMetadata(() => { + this.loadInstalledAtomMetadata().then(() => { this.forkNpmRebuild(options).then(() => { this.logSuccess(); resolve(); diff --git a/src/unlink.js b/src/unlink.js index a4c67c5c..c0c02d2d 100644 --- a/src/unlink.js +++ b/src/unlink.js @@ -45,7 +45,7 @@ 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; diff --git a/src/upgrade.js b/src/upgrade.js index 995d97dc..2a0a81d2 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -83,7 +83,7 @@ available updates.\ return callback(); }); } else { - return this.loadInstalledAtomMetadata(callback); + return this.loadInstalledAtomMetadata().then(callback, callback); } } From 2cb92e55808c6c6f2011324d4b229be3035a552f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Wed, 27 Sep 2023 01:07:07 +0200 Subject: [PATCH 41/64] Prettifying git.js No interface modifications, just some looks. All tests passing. --- src/git.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/git.js b/src/git.js index dd5f9fae..4747974f 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 = env => { +function addGitBashToEnv(env) { let gitPath; if (env.ProgramFiles) { gitPath = path.join(env.ProgramFiles, 'Git'); @@ -51,13 +50,12 @@ const addGitBashToEnv = 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); @@ -80,8 +78,8 @@ exports.getGitVersion = () => { spawned.on('close', code => { let version; if (code === 0) { - let gitName, versionName; - [gitName, versionName, version] = Buffer.concat(outputChunks).toString().split(' '); + let _gitName, _versionName; + [_gitName, _versionName, version] = Buffer.concat(outputChunks).toString().split(' '); version = version?.trim(); } resolve(version); From f598740b24a5d619668ffa4038da0141ee00d651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Wed, 27 Sep 2023 01:53:33 +0200 Subject: [PATCH 42/64] Promisification of apm.js All tests still passing. For the modules where this had an effect and which have already been promisified, the benefits can already be seen: without changing their interface, their implementation could be made much more async-like. --- src/apm-cli.js | 21 +++--- src/apm.js | 137 +++++++++++++++++++----------------- src/ci.js | 2 +- src/command.js | 2 +- src/develop.js | 22 +++--- src/install.js | 4 +- src/list.js | 37 +++++----- src/publish.js | 37 +++++----- src/rebuild-module-cache.js | 2 +- src/rebuild.js | 26 +++---- src/upgrade.js | 2 +- 11 files changed, 142 insertions(+), 150 deletions(-) diff --git a/src/apm-cli.js b/src/apm-cli.js index c8ad21aa..c407dad5 100644 --- a/src/apm-cli.js +++ b/src/apm-cli.js @@ -164,18 +164,15 @@ ${'git'.magenta} ${gitVersion.magenta}\ console.log(versions); }; -function getAtomVersion() { - return new Promise((resolve, _reject) => { - config.getResourcePath((resourcePath) => { - const unknownVersion = 'unknown'; - try { - const { version } = require(path.join(resourcePath, "package.json")) ?? unknownVersion; - resolve(version); - } catch (error) { - resolve(unknownVersion); - } - }); - }); +async function getAtomVersion() { + const resourcePath = await config.getResourcePath(); + const unknownVersion = 'unknown'; + try { + const { version } = require(path.join(resourcePath, "package.json")) ?? unknownVersion; + return version; + } catch (error) { + return unknownVersion; + } } function getPythonVersion() { diff --git a/src/apm.js b/src/apm.js index dcd4493c..77476d3b 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 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/ci.js b/src/ci.js index d965dd61..ef9b2549 100644 --- a/src/ci.js +++ b/src/ci.js @@ -72,7 +72,7 @@ but cannot be used to install new packages or dependencies.\ const opts = this.parseOptions(options.commandArgs); const commands = []; - commands.push(callback => config.loadNpm((error, npm) => { this.npm = npm; callback(error); })); + commands.push(callback => config.loadNpm().then(npm => { this.npm = npm; callback(); })); commands.push(cb => this.loadInstalledAtomMetadata().then(cb, cb)); commands.push(cb => this.installModules(opts).then(cb, cb)); const iteratee = (item, next) => item(next); diff --git a/src/command.js b/src/command.js index ef5f5745..63563e55 100644 --- a/src/command.js +++ b/src/command.js @@ -123,7 +123,7 @@ class Command { if (this.resourcePath) { process.nextTick(() => void resolve(this.resourcePath)); } else { - config.getResourcePath(resourcePath => { this.resourcePath = resourcePath; resolve(this.resourcePath); }); + config.getResourcePath().then(resourcePath => { this.resourcePath = resourcePath; resolve(this.resourcePath); }); } }); } diff --git a/src/develop.js b/src/develop.js index 7356ff77..34bf0f32 100644 --- a/src/develop.js +++ b/src/develop.js @@ -72,20 +72,18 @@ cmd-shift-o to run the package out of the newly cloned repository.\ }); } - cloneRepository(repoUrl, packageDirectory, options) { + 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) => { - 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) => { - if (options.argv.json) { - return void this.logCommandResultsIfFail(...args).then(resolve, reject); - } + this.spawn(command, args, (...args) => { + if (options.argv.json) { + return void this.logCommandResultsIfFail(...args).then(resolve, reject); + } - this.logCommandResults(...args).then(resolve, reject); - }); + this.logCommandResults(...args).then(resolve, reject); }); }); } diff --git a/src/install.js b/src/install.js index 3e7bef75..8ccd7fd6 100644 --- a/src/install.js +++ b/src/install.js @@ -682,7 +682,7 @@ Run ppm -v after installing Git to see what version has been detected.\ this.createAtomDirectories(); if (options.argv.check) { - config.loadNpm((error, npm) => { + config.loadNpm().then(npm => { this.npm = npm; const cb = () => { return this.checkNativeBuildTools(callback); @@ -739,7 +739,7 @@ with Pulsar will be used.\ } const commands = []; - commands.push(callback => { return config.loadNpm((error, npm) => { this.npm = npm; return callback(error); }); }); + commands.push(callback => void config.loadNpm().then(npm => { this.npm = npm; return callback(); })); commands.push(callback => this.loadInstalledAtomMetadata().then(() => callback(), () => callback())); packageNames.forEach(packageName => commands.push(callback => installPackage(packageName, callback))); const iteratee = (item, next) => item(next); diff --git a/src/list.js b/src/list.js index 6e2d1561..abde4a66 100644 --- a/src/list.js +++ b/src/list.js @@ -164,26 +164,23 @@ List all the installed packages and also the packages bundled with Atom.\ return gitPackages; } - listBundledPackages(options) { - return new Promise((resolve, _reject) => { - config.getResourcePath(resourcePath => { - 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)); - - if (!options.argv.bare && !options.argv.json) { - console.log(`${`Built-in Atom ${options.argv.themes ? 'Themes' : 'Packages'}`.cyan} (${packagesMeta.length})`); - } - - resolve(packagesMeta); - }); - }); + 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)); + + if (!options.argv.bare && !options.argv.json) { + console.log(`${`Built-in Atom ${options.argv.themes ? 'Themes' : 'Packages'}`.cyan} (${packagesMeta.length})`); + } + + return packagesMeta; } listInstalledPackages(options) { diff --git a/src/publish.js b/src/publish.js index 7c2bcbdb..5796681a 100644 --- a/src/publish.js +++ b/src/publish.js @@ -349,30 +349,29 @@ have published it.\ throw error; } + const gitCommand = await config.getSetting('git') ?? 'git'; return new Promise((resolve, reject) => { - config.getSetting('git', gitCommand => { - gitCommand ??= 'git'; - return this.spawn(gitCommand, ['add', 'package.json'], (code, stderr, stdout) => { + this.spawn(gitCommand, ['add', 'package.json'], (code, stderr, stdout) => { + stderr ??= ''; + stdout ??= ''; + if (code !== 0) { + this.logFailure(); + const addOutput = `${stdout}\n${stderr}`.trim(); + return void reject(`\`git add package.json\` failed: ${addOutput}`); + } + + this.spawn(gitCommand, ['commit', '-m', message], (code, stderr, stdout) => { stderr ??= ''; stdout ??= ''; - if (code !== 0) { + if (code === 0) { this.logFailure(); - const addOutput = `${stdout}\n${stderr}`.trim(); - return void reject(`\`git add package.json\` failed: ${addOutput}`); + const commitOutput = `${stdout}\n${stderr}`.trim(); + reject(`Failed to commit package.json: ${commitOutput}`); + return; } - - this.spawn(gitCommand, ['commit', '-m', message], (code, stderr, stdout) => { - stderr ??= ''; - stdout ??= ''; - if (code === 0) { - this.logSuccess(); - resolve(); - } else { - this.logFailure(); - const commitOutput = `${stdout}\n${stderr}`.trim(); - reject(`Failed to commit package.json: ${commitOutput}`); - } - }); + + this.logSuccess(); + resolve(); }); }); }); diff --git a/src/rebuild-module-cache.js b/src/rebuild-module-cache.js index bb79d660..7bd0e4c3 100644 --- a/src/rebuild-module-cache.js +++ b/src/rebuild-module-cache.js @@ -37,7 +37,7 @@ This command skips all linked packages.\ if (this.resourcePath) { return process.nextTick(() => callback(this.resourcePath)); } else { - return config.getResourcePath(resourcePath => { this.resourcePath = resourcePath; return callback(this.resourcePath); }); + return void config.getResourcePath().then(resourcePath => { this.resourcePath = resourcePath; return callback(this.resourcePath); }); } } diff --git a/src/rebuild.js b/src/rebuild.js index 04bc9513..d68d7007 100644 --- a/src/rebuild.js +++ b/src/rebuild.js @@ -59,22 +59,18 @@ All the modules will be rebuilt if no module names are specified.\ ); } - run(options) { + async run(options) { options = this.parseOptions(options.commandArgs); - return new Promise((resolve, _reject) => { - config.loadNpm((_error, npm) => { - this.npm = npm; - this.loadInstalledAtomMetadata().then(() => { - this.forkNpmRebuild(options).then(() => { - this.logSuccess(); - resolve(); - }, stderr => { - this.logFailure(); - resolve(stderr); //errors as return values atm - }); - }); - }); - }); + 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/upgrade.js b/src/upgrade.js index 2a0a81d2..cd865808 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -135,7 +135,7 @@ available updates.\ getLatestSha(pack, callback) { const repoPath = path.join(this.atomPackagesDirectory, pack.name); const repo = Git.open(repoPath); - return config.getSetting('git', command => { + return config.getSetting('git').then(command => { if (command == null) { command = 'git'; } const args = ['fetch', 'origin', repo.getShortHead()]; git.addGitToEnv(process.env); From ae43a469dbab585147b236b7bfec3567e3cad562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Wed, 27 Sep 2023 21:05:37 +0200 Subject: [PATCH 43/64] Asyncify and fix upgrade.js upgrade-spec.js has been reclaimed with all tests passing. Seems like the 'async' package has a nice async-friendly interface already. I don't quite like how getLatestVersion resolves with no value (undefined) for HTTP 404 and when the package is already the latest or missing... but for now, this is the setting that works. --- {spec2 => spec}/upgrade-spec.js | 0 src/upgrade.js | 251 ++++++++++++++++---------------- 2 files changed, 127 insertions(+), 124 deletions(-) rename {spec2 => spec}/upgrade-spec.js (100%) diff --git a/spec2/upgrade-spec.js b/spec/upgrade-spec.js similarity index 100% rename from spec2/upgrade-spec.js rename to spec/upgrade-spec.js diff --git a/src/upgrade.js b/src/upgrade.js index cd865808..c2ab15b2 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -19,6 +19,7 @@ const git = require('./git'); module.exports = class Upgrade extends Command { + static promiseBased = true; static commandNames = [ "upgrade", "outdated", "update" ]; constructor() { @@ -75,16 +76,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().then(callback, callback); } + + return await this.loadInstalledAtomMetadata(); } folderIsRepo(pack) { @@ -92,26 +95,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 +126,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').then(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 +168,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'); } + installCommands.push(callback => { + const commandArgs = pack.apmInstallSource?.type === 'git' + ? [pack.apmInstallSource.source] + : [`${pack.name}@${latestVersion}`]; + if (this.verbose) { commandArgs.unshift('--verbose'); } return new Install().run({callback, commandArgs}); - })))(pack, latestVersion); + }); } - 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 +220,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); } } From f16576b542fbbc34d1c7586b6067fc1113e77196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Wed, 27 Sep 2023 23:33:25 +0200 Subject: [PATCH 44/64] Rewrite use of `async` module to more Promise-based interface I adapted the test in one case; there is no point in sweating too much over undefined and null. Other than that, only refactored files have been modified, and all tests pass. --- spec/ci-spec.js | 4 ++-- src/ci.js | 12 ++++++------ src/develop.js | 25 ++++++++++++------------- src/star.js | 2 +- src/uninstall.js | 7 ++----- src/unstar.js | 2 +- 6 files changed, 24 insertions(+), 28 deletions(-) diff --git a/spec/ci-spec.js b/spec/ci-spec.js index 3329401c..f0aaba3a 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')); @@ -83,7 +83,7 @@ describe('apm ci', () => { }); 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/src/ci.js b/src/ci.js index ef9b2549..cea6a7ed 100644 --- a/src/ci.js +++ b/src/ci.js @@ -68,7 +68,7 @@ but cannot be used to install new packages or dependencies.\ ) } - run(options) { + async run(options) { const opts = this.parseOptions(options.commandArgs); const commands = []; @@ -76,10 +76,10 @@ but cannot be used to install new packages or dependencies.\ commands.push(cb => this.loadInstalledAtomMetadata().then(cb, cb)); commands.push(cb => this.installModules(opts).then(cb, cb)); const iteratee = (item, next) => item(next); - return new Promise((resolve, _reject) => - void async.mapSeries(commands, iteratee, err => - resolve(err || null) - ) - ); + try { + await async.mapSeries(commands, iteratee); + } catch (error) { + return error; // errors as return values atm + } } }; diff --git a/src/develop.js b/src/develop.js index 34bf0f32..25f3b174 100644 --- a/src/develop.js +++ b/src/develop.js @@ -105,7 +105,7 @@ cmd-shift-o to run the package out of the newly cloned repository.\ return new Link().run(linkOptions); } - run(options) { + async run(options) { const packageName = options.commandArgs.shift(); if (!((packageName != null ? packageName.length : undefined) > 0)) { @@ -119,17 +119,16 @@ cmd-shift-o to run the package out of the newly cloned repository.\ return this.linkPackage(packageDirectory, options); } - return new Promise((resolve, _reject) => { - this.getRepositoryUrl(packageName).then(repoUrl => { - const tasks = []; - tasks.push(callback => this.cloneRepository(repoUrl, packageDirectory, options).then(callback, callback)); - - tasks.push(callback => this.installDependencies(packageDirectory, options).then(callback)); - - tasks.push(callback => this.linkPackage(packageDirectory, options).then(callback)); - - return async.waterfall(tasks, resolve); - }, resolve); - }); + try { + const repoUrl = await this.getRepositoryUrl(packageName); + const tasks = []; + tasks.push(callback => this.cloneRepository(repoUrl, packageDirectory, options).then(callback, callback)); + tasks.push(callback => this.installDependencies(packageDirectory, options).then(callback)); + tasks.push(callback => this.linkPackage(packageDirectory, options).then(callback)); + + await async.waterfall(tasks); + } catch (error) { + return error; //errors as return values atm + } } } diff --git a/src/star.js b/src/star.js index 81fa926a..5d70724c 100644 --- a/src/star.js +++ b/src/star.js @@ -117,7 +117,7 @@ Run \`ppm stars\` to see all your starred packages.\ const commands = packageNames.map(packageName => { return callback => this.starPackage(packageName, starOptions).then(callback, callback); }); - async.waterfall(commands, resolve); + async.waterfall(commands).then(resolve); }); }); } diff --git a/src/uninstall.js b/src/uninstall.js index 6eef08c5..eb201ad0 100644 --- a/src/uninstall.js +++ b/src/uninstall.js @@ -108,10 +108,7 @@ Delete the installed package(s) from the ~/.pulsar/packages directory.\ } } - return new Promise((resolve, _reject) => - void async.eachSeries(uninstallsToRegister, (data, errorHandler) => - void this.registerUninstall(data).then(errorHandler), () => resolve(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/unstar.js b/src/unstar.js index 11529587..a2b1bb9d 100644 --- a/src/unstar.js +++ b/src/unstar.js @@ -70,7 +70,7 @@ Run \`ppm stars\` to see all your starred packages.\ const commands = packageNames.map(packageName => { return callback => this.starPackage(packageName, token).then(callback, callback); }); - return async.waterfall(commands, resolve); + return async.waterfall(commands).then(resolve); }); }); } From 9d75fed21c3160402857b4acf33e89b5fe022dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 29 Sep 2023 03:25:08 +0200 Subject: [PATCH 45/64] Promisification of install.js The situation is desperate but I needed to save my work. --- spec/ci-spec.js | 2 +- src/develop.js | 5 +- src/install.js | 518 ++++++++++++++++++++++++------------------------ src/stars.js | 4 +- src/upgrade.js | 2 +- 5 files changed, 268 insertions(+), 263 deletions(-) diff --git a/spec/ci-spec.js b/spec/ci-spec.js index f0aaba3a..6395e648 100644 --- a/spec/ci-spec.js +++ b/spec/ci-spec.js @@ -78,7 +78,7 @@ 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); diff --git a/src/develop.js b/src/develop.js index 25f3b174..dcb52f41 100644 --- a/src/develop.js +++ b/src/develop.js @@ -89,13 +89,10 @@ cmd-shift-o to run the package out of the newly cloned repository.\ } installDependencies(packageDirectory, options) { - return new Promise((resolve, _reject) => { process.chdir(packageDirectory); const installOptions = _.clone(options); - installOptions.callback = resolve; - return void new Install().run(installOptions); - }); + return new Install().run(installOptions); } linkPackage(packageDirectory, options) { diff --git a/src/install.js b/src/install.js index 8ccd7fd6..f82428c3 100644 --- a/src/install.js +++ b/src/install.js @@ -20,6 +20,7 @@ const {isDeprecatedPackage} = require('./deprecated-packages'); module.exports = class Install extends Command { + static promiseBased = true; static commandNames = [ "install", "i" ]; constructor() { @@ -67,9 +68,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 +96,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).then(next, 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(next => fs.cp(source, destination).then(next, next)); + commands.push(next => this.buildModuleCache(pack.name).then(next, next)); + commands.push(next => this.warmCompileCache(pack.name).then(next, next)); + + async.waterfall(commands).then(() => { + if (!options.argv.json) { this.logSuccess(); } + resolve({name: child, installPath: destination}); + }, error => { + this.logFailure(); + reject(error); + }); + }); }); } @@ -171,15 +172,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(...args).then(callback, callback); - } else { - return this.logCommandResults(...args).then(callback, callback); - } + 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 +208,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 +258,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 +281,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 Atom 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 +332,70 @@ 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) { + console.trace('DEBUG'); 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 +447,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 +464,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(...args).then(callback, callback); - }); + return new Promise((resolve, reject) => + void this.fork(this.atomNpmPath, buildArgs, buildOptions, (...args) => + void this.logCommandResults(...args).then(resolve, reject) + ) + ); } packageNamesFromPath(filePath) { @@ -478,45 +482,43 @@ Run ppm -v after installing Git to see what version has been detected.\ return this.sanitizePackageNames(packages.split(/\s/)); } - buildModuleCache(packageName, callback) { + 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()); + return new Promise((resolve, _reject) => + void rebuildCacheCommand.rebuild(packageDirectory, () => resolve()) // Ignore cache errors and just finish the install + ); } - warmCompileCache(packageName, callback) { + async warmCompileCache(packageName) { const packageDirectory = path.join(this.atomPackagesDirectory, packageName); - return this.getResourcePath().then(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().then(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,14 +556,14 @@ 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) => { + tasks.push((_data, next) => { const urls = this.getNormalizedGitUrls(packageUrl); - return this.cloneFirstValidGitUrl(urls, cloneDir, options, err => next(err, data)); + this.cloneFirstValidGitUrl(urls, cloneDir, options).then(next, next); }); tasks.push((data, next) => { @@ -574,7 +576,7 @@ Run ppm -v after installing Git to see what version has been detected.\ 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) { + return this.getRepositoryHeadSha(cloneDir, (err, sha) => { data.sha = sha; return next(err, data); }); @@ -585,16 +587,16 @@ Run ppm -v after installing Git to see what version has been detected.\ return this.installGitPackageDependencies(cloneDir, options, err => next(err, data)); }); - tasks.push(function(data, next) { + tasks.push((data, next) => { const metadataFilePath = CSON.resolve(path.join(cloneDir, 'package')); - return CSON.readFile(metadataFilePath, function(err, metadata) { + return CSON.readFile(metadataFilePath, (err, metadata) => { data.metadataFilePath = metadataFilePath; data.metadata = metadata; return next(err, data); }); }); - tasks.push(function(data, next) { + tasks.push((data, next) => { data.metadata.apmInstallSource = { type: "git", source: packageUrl, @@ -615,7 +617,7 @@ Run ppm -v after installing Git to see what version has been detected.\ }); const iteratee = (currentData, task, next) => task(currentData, next); - return async.reduce(tasks, {}, iteratee, callback); + return await async.reduce(tasks, {}, iteratee); } getNormalizedGitUrls(packageUrl) { @@ -635,60 +637,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).then(callback, callback); + 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().then(npm => { + try { + const npm = await config.loadNpm(); this.npm = npm; - const cb = () => { - return this.checkNativeBuildTools(callback); - }; - return this.loadInstalledAtomMetadata().then(cb, cb); - }); + await this.loadInstalledAtomMetadata(); + await this.checkNativeBuildTools(); + } catch (error) { + return error; //errors as return values atm + } return; } @@ -697,41 +695,43 @@ 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); - } + await this.installGitPackage(name, options, options.argv.branch || options.argv.tag); + return; + } + 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); - }); + ); } + 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); @@ -739,16 +739,26 @@ with Pulsar will be used.\ } const commands = []; - commands.push(callback => void config.loadNpm().then(npm => { this.npm = npm; return callback(); })); - commands.push(callback => this.loadInstalledAtomMetadata().then(() => callback(), () => 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/stars.js b/src/stars.js index 1c251822..418f89bd 100644 --- a/src/stars.js +++ b/src/stars.js @@ -77,9 +77,7 @@ List or install starred Atom packages and themes.\ if (packages.length === 0) { return; } const commandArgs = packages.map(({name}) => name); - return new Promise((resolve, _reject) => - void new Install().run({commandArgs, callback: resolve}) - ); + return new Install().run({commandArgs}); } logPackagesAsJson(packages) { diff --git a/src/upgrade.js b/src/upgrade.js index c2ab15b2..60e59c62 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -203,7 +203,7 @@ available updates.\ ? [pack.apmInstallSource.source] : [`${pack.name}@${latestVersion}`]; if (this.verbose) { commandArgs.unshift('--verbose'); } - return new Install().run({callback, commandArgs}); + new Install().run({commandArgs}).then(callback); }); } From e8dd06f38817a4a98de7f54736d4f3348cd1a9eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 29 Sep 2023 03:52:02 +0200 Subject: [PATCH 46/64] install.js reimagination of installGitPackage With this modification, the test can be run partially, without breaking anything else... --- spec/stars-spec.js | 2 +- src/install.js | 85 +++++++++++++++++----------------------------- 2 files changed, 32 insertions(+), 55 deletions(-) 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/src/install.js b/src/install.js index f82428c3..b3c7d90b 100644 --- a/src/install.js +++ b/src/install.js @@ -371,7 +371,6 @@ Run ppm -v after installing Git to see what version has been detected.\ // // return value - A Promise that rejects with an error or resolves without a value async installPackageDependencies(options) { - console.trace('DEBUG'); options = _.extend({}, options, {installGlobally: false}); const commands = []; const object = this.getPackageDependencies(options.cwd); @@ -561,63 +560,41 @@ Run ppm -v after installing Git to see what version has been detected.\ const cloneDir = temp.mkdirSync("atom-git-package-clone-"); - tasks.push((_data, next) => { - const urls = this.getNormalizedGitUrls(packageUrl); - this.cloneFirstValidGitUrl(urls, cloneDir, options).then(next, next); - }); - - 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, (err, sha) => { - data.sha = sha; - return next(err, data); - }); - } - }); - - tasks.push((data, next) => { - return this.installGitPackageDependencies(cloneDir, options, err => next(err, data)); - }); + const urls = this.getNormalizedGitUrls(packageUrl); + await this.cloneFirstValidGitUrl(urls, cloneDir, options); - tasks.push((data, next) => { - const metadataFilePath = CSON.resolve(path.join(cloneDir, 'package')); - return CSON.readFile(metadataFilePath, (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((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).then(_value => { - if (!options.argv.json) { this.logSuccess(); } - const json = {installPath: targetDir, metadata: data.metadata}; - next(null, json); - }, next); - }); + 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 await async.reduce(tasks, {}, iteratee); + 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) { From ede3fe988bfe71412b1e8267efbfd0f2c834693b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 29 Sep 2023 13:15:45 +0200 Subject: [PATCH 47/64] =?UTF-8?q?FIxing=20installation=20tests.=20At=20las?= =?UTF-8?q?t,=20ALL=20tests=20are=20passing,=20with=20slight=20undefined?= =?UTF-8?q?=20vs=20null=20modifications!=20=F0=9F=A5=B3=20That=20could=20b?= =?UTF-8?q?e=20reclaimed=20later=20on=20if=20it=20really=20matters.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {spec2 => spec}/install-spec.js | 26 +++++++++++++------------- src/install.js | 9 +++------ 2 files changed, 16 insertions(+), 19 deletions(-) rename {spec2 => spec}/install-spec.js (97%) diff --git a/spec2/install-spec.js b/spec/install-spec.js similarity index 97% rename from spec2/install-spec.js rename to spec/install-spec.js index 36d01a70..62a50aa3 100644 --- a/spec2/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/src/install.js b/src/install.js index b3c7d90b..09dd6229 100644 --- a/src/install.js +++ b/src/install.js @@ -676,8 +676,7 @@ Run ppm -v after installing Git to see what version has been detected.\ const gitPackageInfo = this.getHostedGitInfo(name); if (gitPackageInfo || (name.indexOf('file://') === 0)) { - await this.installGitPackage(name, options, options.argv.branch || options.argv.tag); - return; + return await this.installGitPackage(name, options, options.argv.branch || options.argv.tag); } if (name === '.') { await this.installDependencies(options); @@ -701,7 +700,7 @@ with Pulsar will be used.\ `.yellow ); } - await this.installRegisteredPackage({name, version}, options); + return await this.installRegisteredPackage({name, version}, options); }; if (packagesFilePath) { @@ -724,9 +723,7 @@ with Pulsar will be used.\ await this.loadInstalledAtomMetadata(); }); packageNames.forEach(packageName => - void commands.push(async () => { - await installPackage(packageName); - }) + void commands.push(async () => await installPackage(packageName)) ); const iteratee = async fn => await fn(); try { From 4f3f7e33d2c74b0914009581cea6d1b9b7395457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 29 Sep 2023 21:13:04 +0200 Subject: [PATCH 48/64] asyncification of dedupe.js This command had already been broken on master but hey, if it's part of the code base, why not at least try to keep superficially up-to-date? :) All tests passing. --- src/dedupe.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/dedupe.js b/src/dedupe.js index 21b29113..3da7d2e3 100644 --- a/src/dedupe.js +++ b/src/dedupe.js @@ -11,6 +11,7 @@ const fs = require('./fs'); module.exports = class Dedupe extends Command { + static promiseBased = true; static commandNames = [ "dedupe" ]; constructor() { @@ -35,11 +36,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(...args).then(callback, callback); + return new Promise((resolve, reject) => { + this.forkDedupeCommand(options, (...args) => { + this.logCommandResults(...args).then(resolve, reject); + }); }); } @@ -67,16 +70,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().then(callback, 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 + } } } From 92f7ae747d6ff5ee1890c6c7b1cd126986d22953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 29 Sep 2023 22:01:12 +0200 Subject: [PATCH 49/64] Asyncification of rebuild-module-cache.js This command is broken on master as well but install.js calls the rebuild method so I went ahead and asyncified it. It still doesn't work (I think the problem is related to asar and faulty resolution of "resource paths") but the tests are still passing. --- src/apm.js | 2 +- src/install.js | 6 ++-- src/rebuild-module-cache.js | 58 ++++++++++++++++++------------------- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/apm.js b/src/apm.js index 77476d3b..881eeba2 100644 --- a/src/apm.js +++ b/src/apm.js @@ -49,7 +49,7 @@ module.exports = { if ((path.basename(apmFolder) === 'ppm') && (path.basename(appFolder) === 'app')) { asarPath = `${appFolder}.asar`; if (fs.existsSync(asarPath)) { - return process.nextTick(() => resolve(asarPath)); + return void process.nextTick(() => resolve(asarPath)); } } diff --git a/src/install.js b/src/install.js index 09dd6229..d155c0d3 100644 --- a/src/install.js +++ b/src/install.js @@ -481,12 +481,10 @@ Run ppm -v after installing Git to see what version has been detected.\ return this.sanitizePackageNames(packages.split(/\s/)); } - buildModuleCache(packageName) { + async buildModuleCache(packageName) { const packageDirectory = path.join(this.atomPackagesDirectory, packageName); const rebuildCacheCommand = new RebuildModuleCache(); - return new Promise((resolve, _reject) => - void rebuildCacheCommand.rebuild(packageDirectory, () => resolve()) // Ignore cache errors and just finish the install - ); + await rebuildCacheCommand.rebuild(packageDirectory).catch(_ => {}); // Ignore cache errors and just finish the install } async warmCompileCache(packageName) { diff --git a/src/rebuild-module-cache.js b/src/rebuild-module-cache.js index 7bd0e4c3..31bae159 100644 --- a/src/rebuild-module-cache.js +++ b/src/rebuild-module-cache.js @@ -8,6 +8,7 @@ const fs = require('./fs'); module.exports = class RebuildModuleCache extends Command { + static promiseBased = true; static commandNames = [ "rebuild-module-cache" ]; constructor() { @@ -33,49 +34,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 void config.getResourcePath().then(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 + } } } From 0aaac88730ca8639ef4a4374651e51286cecc880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 29 Sep 2023 22:09:23 +0200 Subject: [PATCH 50/64] Eliminating all callbacks from ci.js This is a cosmetic phase of the iterative development. I'm eliminating as much of callbacks as possible to see what work is left. All tests passing. --- src/ci.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ci.js b/src/ci.js index cea6a7ed..96ca94b5 100644 --- a/src/ci.js +++ b/src/ci.js @@ -72,12 +72,14 @@ but cannot be used to install new packages or dependencies.\ const opts = this.parseOptions(options.commandArgs); const commands = []; - commands.push(callback => config.loadNpm().then(npm => { this.npm = npm; callback(); })); - commands.push(cb => this.loadInstalledAtomMetadata().then(cb, cb)); - commands.push(cb => this.installModules(opts).then(cb, cb)); - const iteratee = (item, next) => item(next); + 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.mapSeries(commands, iteratee); + await async.waterfall(commands); } catch (error) { return error; // errors as return values atm } From cfac2bf1968b6cd5beb649aecd3572845a78c659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 29 Sep 2023 22:12:34 +0200 Subject: [PATCH 51/64] Eliminating all callbacks from develop.js Similar to the last commit. All tests passing. --- src/develop.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/develop.js b/src/develop.js index dcb52f41..bfb7e5ae 100644 --- a/src/develop.js +++ b/src/develop.js @@ -119,9 +119,9 @@ cmd-shift-o to run the package out of the newly cloned repository.\ try { const repoUrl = await this.getRepositoryUrl(packageName); const tasks = []; - tasks.push(callback => this.cloneRepository(repoUrl, packageDirectory, options).then(callback, callback)); - tasks.push(callback => this.installDependencies(packageDirectory, options).then(callback)); - tasks.push(callback => this.linkPackage(packageDirectory, options).then(callback)); + 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)); await async.waterfall(tasks); } catch (error) { From 0a3e5c2f4213a1eab8298bcbc1a6a9608611b678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 29 Sep 2023 22:18:01 +0200 Subject: [PATCH 52/64] Eliminating all callbacks from star.js Similar to the previous two commits. All tests passing. It seems to me that probably only request.js and login.js are still the old way... --- src/star.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/star.js b/src/star.js index 5d70724c..2af50426 100644 --- a/src/star.js +++ b/src/star.js @@ -115,7 +115,7 @@ Run \`ppm stars\` to see all your starred packages.\ }; const commands = packageNames.map(packageName => { - return callback => this.starPackage(packageName, starOptions).then(callback, callback); + return async () => await this.starPackage(packageName, starOptions); }); async.waterfall(commands).then(resolve); }); From 8ba5e833e64c72e19d0a0b3f88d407af9615113d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 29 Sep 2023 22:20:24 +0200 Subject: [PATCH 53/64] Eliminating all callbacks from unstar.js Similar to the previous cosmetic changes. All tests still passing. --- src/unstar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unstar.js b/src/unstar.js index a2b1bb9d..e0fbf243 100644 --- a/src/unstar.js +++ b/src/unstar.js @@ -68,7 +68,7 @@ Run \`ppm stars\` to see all your starred packages.\ if (error != null) { return void resolve(error); } // error as return value atm const commands = packageNames.map(packageName => { - return callback => this.starPackage(packageName, token).then(callback, callback); + return async () => await this.starPackage(packageName, token); }); return async.waterfall(commands).then(resolve); }); From 316d9fe7bef5d2525b554d4a21c9b0b7f629f9c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 29 Sep 2023 23:27:54 +0200 Subject: [PATCH 54/64] Eliminating all callbacks from upgrade.js Similar to the previous ones. All tests passing. --- src/upgrade.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/upgrade.js b/src/upgrade.js index 60e59c62..49e777c7 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -198,12 +198,12 @@ available updates.\ async installUpdates(updates) { const installCommands = []; for (let {pack, latestVersion} of Array.from(updates)) { - installCommands.push(callback => { + installCommands.push(async () => { const commandArgs = pack.apmInstallSource?.type === 'git' ? [pack.apmInstallSource.source] : [`${pack.name}@${latestVersion}`]; if (this.verbose) { commandArgs.unshift('--verbose'); } - new Install().run({commandArgs}).then(callback); + await new Install().run({commandArgs}); }); } From ae90d473a379878c7e883ebc95eaee194c26a357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Fri, 29 Sep 2023 23:56:59 +0200 Subject: [PATCH 55/64] Promisification of request.js *without the main user-facing interface because that's deliberately not a Node-style callback. All tests are passing. --- src/package-converter.js | 33 +++++----- src/request.js | 138 ++++++++++++++++++++++++--------------- 2 files changed, 103 insertions(+), 68 deletions(-) diff --git a/src/package-converter.js b/src/package-converter.js index 46faaf91..9a1030fd 100644 --- a/src/package-converter.js +++ b/src/package-converter.js @@ -52,24 +52,23 @@ class PackageConverter { return downloadUrl += '/archive/master.tar.gz'; } - downloadBundle() { + async downloadBundle() { + const tempPath = temp.mkdirSync('atom-bundle-'); + const requestOptions = {url: this.getDownloadUrl()}; + const readStream = await request.createReadStream(requestOptions); return new Promise((resolve, reject) => { - const tempPath = temp.mkdirSync('atom-bundle-'); - const requestOptions = {url: this.getDownloadUrl()}; - return request.createReadStream(requestOptions, readStream => { - readStream.on('response', ({headers, statusCode}) => { - if (statusCode !== 200) { - reject(`Download failed (${headers.status})`); - } - }); - - return 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]); - await this.copyDirectories(sourcePath); - resolve(); - }); + readStream.on('response', ({headers, statusCode}) => { + if (statusCode !== 200) { + reject(`Download failed (${headers.status})`); + } + }); + + 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]); + await this.copyDirectories(sourcePath); + resolve(); }); }); } 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.`; From 85760539f28e84b75893f79ce6bd92c75fbccddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Sat, 30 Sep 2023 00:40:13 +0200 Subject: [PATCH 56/64] Asyncification of getTokenOrLogin This is the main reason I didn't want to touch Login before. It wasn't as scary as it first seemed. All tests passing, the only thing left is the Login command itself. --- src/login.js | 11 ++++- src/publish.js | 117 +++++++++++++++++++++++-------------------------- src/star.js | 26 +++++------ src/stars.js | 13 ++---- src/unstar.js | 17 ++++--- 5 files changed, 87 insertions(+), 97 deletions(-) diff --git a/src/login.js b/src/login.js index 308722c1..b2b8c2d5 100644 --- a/src/login.js +++ b/src/login.js @@ -19,8 +19,15 @@ class Login extends Command { this.saveToken = this.saveToken.bind(this); } - static getTokenOrLogin(callback) { - return void auth.getToken().then(token => void callback(null, token), _error => new Login().run({callback, commandArgs: []})); + static async getTokenOrLogin() { + try { + const token = await auth.getToken(); + return token; + } catch (error) { + return new Promise((resolve, reject) => + void new Login().run({callback: (error, value) => void(error != null ? reject(error) : resolve(value)), commandArgs: []}) + ); + } } parseOptions(argv) { diff --git a/src/publish.js b/src/publish.js index 5796681a..b80f32c2 100644 --- a/src/publish.js +++ b/src/publish.js @@ -138,25 +138,22 @@ have published it.\ // // return value - A Promise that can reject with an error or resolve to // a boolean value. - packageExists(packageName) { + async packageExists(packageName) { + const token = await Login.getTokenOrLogin(); + const requestSettings = { + url: `${config.getAtomPackagesUrl()}/${packageName}`, + json: true, + headers: { + authorization: token + } + }; return new Promise((resolve, reject) => { - Login.getTokenOrLogin((error, token) => { - if (error != null) { return void reject(error); } - - const requestSettings = { - url: `${config.getAtomPackagesUrl()}/${packageName}`, - json: true, - headers: { - authorization: token - } - }; - request.get(requestSettings, (error, response, body) => { - body ??= {}; - if (error != null) { - return void reject(error); - } - resolve(response.statusCode === 200); - }); + request.get(requestSettings, (error, response, body) => { + body ??= {}; + if (error != null) { + return void reject(error); + } + resolve(response.statusCode === 200); }); }); } @@ -182,24 +179,21 @@ have published it.\ } process.stdout.write(`Registering ${pack.name} `); - return new Promise((resolve, reject) => { - Login.getTokenOrLogin((error, token) => { - if (error != null) { - this.logFailure(); - reject(error); - return; - } - const requestSettings = { - url: config.getAtomPackagesUrl(), - json: true, - qs: { - repository - }, - headers: { - authorization: token - } - }; + 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) => { body ??= {}; if (error != null) { @@ -215,7 +209,10 @@ have published it.\ return resolve(true); }); }); - }); + } catch (error) { + this.logFailure(); + throw error; + } } // Create a new package version at the given Git tag. @@ -224,37 +221,31 @@ have published it.\ // tag - The string Git tag of the new version. // // return value - A Promise that rejects with an error or resolves without a value. - createPackageVersion(packageName, tag, options) { + 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 + } + }; return new Promise((resolve, reject) => { - Login.getTokenOrLogin((error, token) => { + request.post(requestSettings, (error, response, body) => { + body ??= {}; if (error != null) { - reject(error); - return; + return void reject(error); + } + if (response.statusCode !== 201) { + const message = request.getErrorMessage(body, error); + return void reject(`Creating new version failed: ${message}`); } - - const requestSettings = { - url: `${config.getAtomPackagesUrl()}/${packageName}/versions`, - json: true, - qs: { - tag, - rename: options.rename - }, - headers: { - authorization: token - } - }; - request.post(requestSettings, (error, response, body) => { - body ??= {}; - if (error != null) { - return void reject(error); - } - if (response.statusCode !== 201) { - const message = request.getErrorMessage(body, error); - return void reject(`Creating new version failed: ${message}`); - } - resolve(); - }); + resolve(); }); }); } diff --git a/src/star.js b/src/star.js index 2af50426..3de5a345 100644 --- a/src/star.js +++ b/src/star.js @@ -105,20 +105,18 @@ Run \`ppm stars\` to see all your starred packages.\ } } - return new Promise((resolve, _reject) => { - Login.getTokenOrLogin((error, token) => { - if (error != null) { return void resolve(error); } // error as return value - - const starOptions = { - ignoreUnpublishedPackages: options.argv.installed, - token - }; - - const commands = packageNames.map(packageName => { - return async () => await this.starPackage(packageName, starOptions); - }); - async.waterfall(commands).then(resolve); + try { + const token = await Login.getTokenOrLogin(); + const starOptions = { + ignoreUnpublishedPackages: options.argv.installed, + token + }; + const commands = packageNames.map(packageName => { + return async () => await this.starPackage(packageName, starOptions); }); - }); + return await async.waterfall(commands); + } catch (error) { + return error; // error as return value + } } } diff --git a/src/stars.js b/src/stars.js index 418f89bd..dde8ed71 100644 --- a/src/stars.js +++ b/src/stars.js @@ -33,7 +33,7 @@ List or install starred Atom packages and themes.\ return options.boolean('json').describe('json', 'Output packages as a JSON array'); } - getStarredPackages(user, atomVersion) { + async getStarredPackages(user, atomVersion) { const requestSettings = {json: true}; if (atomVersion) { requestSettings.qs = {engine: atomVersion}; } @@ -43,14 +43,9 @@ List or install starred Atom packages and themes.\ } requestSettings.url = `${config.getAtomApiUrl()}/stars`; - return new Promise((resolve, reject) => { - Login.getTokenOrLogin((error, token) => { - if (error != null) { return void reject(error); } - - requestSettings.headers = {authorization: token}; - resolve(this.requestStarredPackages(requestSettings)); - }); - }); + const token = await Login.getTokenOrLogin(); + requestSettings.headers = {authorization: token}; + return this.requestStarredPackages(requestSettings); } requestStarredPackages(requestSettings) { diff --git a/src/unstar.js b/src/unstar.js index e0fbf243..5c660e17 100644 --- a/src/unstar.js +++ b/src/unstar.js @@ -63,15 +63,14 @@ Run \`ppm stars\` to see all your starred packages.\ return "Please specify a package name to unstar"; // error as return value atm } - return new Promise((resolve, _reject) => { - Login.getTokenOrLogin((error, token) => { - if (error != null) { return void resolve(error); } // error as return value atm - - const commands = packageNames.map(packageName => { - return async () => await this.starPackage(packageName, token); - }); - return async.waterfall(commands).then(resolve); + try { + const token = await Login.getTokenOrLogin(); + const commands = packageNames.map(packageName => { + return async () => await this.starPackage(packageName, token); }); - }); + return await async.waterfall(commands); + } catch (error) { + return error; // error as return value atm + } } } From 5e8d10954cd61ab31bb1ea626818d982ca3e7f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Sat, 30 Sep 2023 13:21:44 +0200 Subject: [PATCH 57/64] Elimination of Q and (partial) asyncification of login.js All tests passing - but tests are too lenient because they don't actually test the prompt path... That seems to work, too. The interface is still unmodified because this was the only command that had separate error and return value. --- src/login.js | 60 +++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/src/login.js b/src/login.js index b2b8c2d5..8b5c284b 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'); @@ -14,9 +13,6 @@ class Login extends Command { constructor(...args) { super(...args); - this.welcomeMessage = this.welcomeMessage.bind(this); - this.getToken = this.getToken.bind(this); - this.saveToken = this.saveToken.bind(this); } static async getTokenOrLogin() { @@ -44,25 +40,35 @@ be used to identify you when publishing packages.\ return options.string('token').describe('token', 'Package API token'); } - run(options) { + async run(options) { const {callback} = 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); + let token = options.argv.token; + + try { + if (token == null) { + await this.welcomeMessage(); + await this.openURL(); + token = await this.getToken(); + } + await this.saveToken(token); + callback(null, token); + } catch (error) { + callback(error); + } } 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! @@ -75,31 +81,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; } - async saveToken({token}) { + async saveToken(token) { if (!token) { throw new Error("Token is required"); } process.stdout.write('Saving token to Keychain '); await auth.saveToken(token); this.logSuccess(); - return Q(token); } } From ba12df84f07ba9c3e590142346a0bca7a020e79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Sat, 30 Sep 2023 14:10:22 +0200 Subject: [PATCH 58/64] Fully rework login.js into async Tests (and manual check) passes. Now the command has no special return value, and getTokenOrLogin calls a method that can have one. --- src/command.js | 4 ---- src/login.js | 33 +++++++++++++++------------------ 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/command.js b/src/command.js index 63563e55..034b186d 100644 --- a/src/command.js +++ b/src/command.js @@ -8,10 +8,6 @@ const git = require('./git'); module.exports = class Command { - constructor() { - this.logCommandResults = this.logCommandResults.bind(this); - this.logCommandResultsIfFail = this.logCommandResultsIfFail.bind(this); - } spawn(command, args, optionsOrCallback, callbackOrMissing) { const [callback, options] = callbackOrMissing == null diff --git a/src/login.js b/src/login.js index 8b5c284b..b53cb27f 100644 --- a/src/login.js +++ b/src/login.js @@ -9,20 +9,15 @@ const Command = require('./command'); module.exports = class Login extends Command { + static promiseBased = true; static commandNames = [ "login" ]; - constructor(...args) { - super(...args); - } - static async getTokenOrLogin() { try { const token = await auth.getToken(); return token; } catch (error) { - return new Promise((resolve, reject) => - void new Login().run({callback: (error, value) => void(error != null ? reject(error) : resolve(value)), commandArgs: []}) - ); + return await new Login().obtainToken(); } } @@ -40,21 +35,23 @@ be used to identify you when publishing packages.\ return options.string('token').describe('token', 'Package API token'); } + 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) { - const {callback} = options; options = this.parseOptions(options.commandArgs); - let token = options.argv.token; - try { - if (token == null) { - await this.welcomeMessage(); - await this.openURL(); - token = await this.getToken(); - } - await this.saveToken(token); - callback(null, token); + await this.obtainToken(options.argv.token); } catch (error) { - callback(error); + return error; //errors as return values atm } } From 71278ead52a42227eda63d4145ba5f3a65461827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Sun, 1 Oct 2023 15:17:44 +0200 Subject: [PATCH 59/64] Fix --version flag The recent PR #100 made me want to check this command and I noticed I messed up at two places to make this work. Rather obvious fixes for a fortunate catch... --- src/apm-cli.js | 6 +++--- src/git.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/apm-cli.js b/src/apm-cli.js index c407dad5..dcd0d9f6 100644 --- a/src/apm-cli.js +++ b/src/apm-cli.js @@ -121,9 +121,9 @@ async function printVersions(args) { const npmVersion = require("npm/package.json").version ?? ""; const nodeVersion = process.versions.node ?? ""; - const pythonVersion = await getPythonVersion(); - const gitVersion = await git.getGitVersion(); - const atomVersion = await getAtomVersion(); + let pythonVersion = await getPythonVersion(); + let gitVersion = await git.getGitVersion(); + let atomVersion = await getAtomVersion(); let versions; if (args.json) { versions = { diff --git a/src/git.js b/src/git.js index 4747974f..c938e050 100644 --- a/src/git.js +++ b/src/git.js @@ -66,7 +66,7 @@ exports.getGitVersion = () => { userconfig: config.getUserConfigPath(), globalconfig: config.getGlobalConfigPath() }; - return new Promise((resolve, _reject => { + return new Promise((resolve, _reject) => { npm.load(npmOptions, () => { const git = npm.config.get('git') ?? 'git'; exports.addGitToEnv(process.env); @@ -85,5 +85,5 @@ exports.getGitVersion = () => { resolve(version); }); }); - })); + }); }; From 2ba286b3776b4de6d7b73045068a4873265cace5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Sun, 1 Oct 2023 17:35:41 +0200 Subject: [PATCH 60/64] Eliminating the promiseBased setting of commands Now that everything is Promise-based (and anything new should also be), it's pointless to keep an option for classes to avoid wrapping into Promises. Nothing should be wrapped anymore. --- src/apm-cli.js | 3 --- src/ci.js | 1 - src/clean.js | 1 - src/config.js | 1 - src/dedupe.js | 1 - src/develop.js | 1 - src/disable.js | 1 - src/docs.js | 1 - src/enable.js | 1 - src/featured.js | 1 - src/init.js | 1 - src/install.js | 1 - src/link.js | 1 - src/links.js | 1 - src/list.js | 1 - src/login.js | 1 - src/publish.js | 1 - src/rebuild-module-cache.js | 1 - src/rebuild.js | 1 - src/search.js | 1 - src/star.js | 1 - src/stars.js | 1 - src/test.js | 1 - src/uninstall.js | 1 - src/unlink.js | 1 - src/unpublish.js | 1 - src/unstar.js | 1 - src/upgrade.js | 1 - src/view.js | 1 - 29 files changed, 31 deletions(-) diff --git a/src/apm-cli.js b/src/apm-cli.js index dcd0d9f6..80fe3789 100644 --- a/src/apm-cli.js +++ b/src/apm-cli.js @@ -264,9 +264,6 @@ module.exports = { return errorHandler(); } else if ((Command = commands[command])) { const command = new Command(); - if (!Command.promiseBased) { - command.run = promisifiedRun(command.run); - } return command.run(options).then(errorHandler); } else { return errorHandler(`Unrecognized command: ${command}`); diff --git a/src/ci.js b/src/ci.js index 96ca94b5..96305da1 100644 --- a/src/ci.js +++ b/src/ci.js @@ -10,7 +10,6 @@ const Command = require('./command'); module.exports = class Ci extends Command { - static promiseBased = true; static commandNames = ["ci"]; constructor() { diff --git a/src/clean.js b/src/clean.js index bd82b174..141175a7 100644 --- a/src/clean.js +++ b/src/clean.js @@ -13,7 +13,6 @@ const fs = require('./fs'); module.exports = class Clean extends Command { - static promiseBased = true; static commandNames = ["clean", "prune"]; constructor() { diff --git a/src/config.js b/src/config.js index 5a74ca5b..149e87c1 100644 --- a/src/config.js +++ b/src/config.js @@ -7,7 +7,6 @@ const Command = require('./command'); module.exports = class Config extends Command { - static promiseBased = true; static commandNames = [ "config" ]; constructor() { diff --git a/src/dedupe.js b/src/dedupe.js index 3da7d2e3..dd83be5e 100644 --- a/src/dedupe.js +++ b/src/dedupe.js @@ -11,7 +11,6 @@ const fs = require('./fs'); module.exports = class Dedupe extends Command { - static promiseBased = true; static commandNames = [ "dedupe" ]; constructor() { diff --git a/src/develop.js b/src/develop.js index bfb7e5ae..5d8b20aa 100644 --- a/src/develop.js +++ b/src/develop.js @@ -15,7 +15,6 @@ const request = require('./request'); module.exports = class Develop extends Command { - static promiseBased = true; static commandNames = [ "dev", "develop" ]; constructor() { diff --git a/src/disable.js b/src/disable.js index 370214b0..2e77dd7f 100644 --- a/src/disable.js +++ b/src/disable.js @@ -10,7 +10,6 @@ const List = require('./list'); module.exports = class Disable extends Command { - static promiseBased = true; static commandNames = [ "disable" ]; parseOptions(argv) { diff --git a/src/docs.js b/src/docs.js index eaac33cd..34dcc794 100644 --- a/src/docs.js +++ b/src/docs.js @@ -7,7 +7,6 @@ const config = require('./apm'); module.exports = class Docs extends View { - static promiseBased = true; static commandNames = [ "docs", "home", "open" ]; parseOptions(argv) { diff --git a/src/enable.js b/src/enable.js index 4861138b..d481aaeb 100644 --- a/src/enable.js +++ b/src/enable.js @@ -9,7 +9,6 @@ const Command = require('./command'); module.exports = class Enable extends Command { - static promiseBased = true; static commandNames = [ "enable" ]; parseOptions(argv) { diff --git a/src/featured.js b/src/featured.js index 35df8da4..27c1a578 100644 --- a/src/featured.js +++ b/src/featured.js @@ -9,7 +9,6 @@ const tree = require('./tree'); module.exports = class Featured extends Command { - static promiseBased = true; static commandNames = [ "featured" ]; parseOptions(argv) { diff --git a/src/init.js b/src/init.js index 8e4dc232..ed078517 100644 --- a/src/init.js +++ b/src/init.js @@ -8,7 +8,6 @@ const fs = require('./fs'); module.exports = class Init extends Command { - static promiseBased = true; static commandNames = [ "init" ]; constructor() { diff --git a/src/install.js b/src/install.js index d155c0d3..a82b89bf 100644 --- a/src/install.js +++ b/src/install.js @@ -20,7 +20,6 @@ const {isDeprecatedPackage} = require('./deprecated-packages'); module.exports = class Install extends Command { - static promiseBased = true; static commandNames = [ "install", "i" ]; constructor() { diff --git a/src/link.js b/src/link.js index 840093f5..5b3492e6 100644 --- a/src/link.js +++ b/src/link.js @@ -10,7 +10,6 @@ const fs = require('./fs'); module.exports = class Link extends Command { - static promiseBased = true; static commandNames = [ "link", "ln" ]; parseOptions(argv) { diff --git a/src/links.js b/src/links.js index 215695ea..eb520d54 100644 --- a/src/links.js +++ b/src/links.js @@ -10,7 +10,6 @@ const tree = require('./tree'); module.exports = class Links extends Command { - static promiseBased = true; static commandNames = [ "linked", "links", "lns" ]; constructor() { diff --git a/src/list.js b/src/list.js index abde4a66..62110157 100644 --- a/src/list.js +++ b/src/list.js @@ -13,7 +13,6 @@ const {getRepository} = require("./packages"); module.exports = class List extends Command { - static promiseBased = true; static commandNames = [ "list", "ls" ]; constructor() { diff --git a/src/login.js b/src/login.js index b53cb27f..b6ecb687 100644 --- a/src/login.js +++ b/src/login.js @@ -9,7 +9,6 @@ const Command = require('./command'); module.exports = class Login extends Command { - static promiseBased = true; static commandNames = [ "login" ]; static async getTokenOrLogin() { diff --git a/src/publish.js b/src/publish.js index b80f32c2..4dfef937 100644 --- a/src/publish.js +++ b/src/publish.js @@ -15,7 +15,6 @@ const request = require('./request'); module.exports = class Publish extends Command { - static promiseBased = true; static commandNames = [ "publish" ]; constructor() { diff --git a/src/rebuild-module-cache.js b/src/rebuild-module-cache.js index 31bae159..abe3ce4a 100644 --- a/src/rebuild-module-cache.js +++ b/src/rebuild-module-cache.js @@ -8,7 +8,6 @@ const fs = require('./fs'); module.exports = class RebuildModuleCache extends Command { - static promiseBased = true; static commandNames = [ "rebuild-module-cache" ]; constructor() { diff --git a/src/rebuild.js b/src/rebuild.js index d68d7007..dbac1cf8 100644 --- a/src/rebuild.js +++ b/src/rebuild.js @@ -10,7 +10,6 @@ const fs = require('./fs'); module.exports = class Rebuild extends Command { - static promiseBased = true; static commandNames = [ "rebuild" ]; constructor() { diff --git a/src/search.js b/src/search.js index 7eed53d5..c6fb9e7a 100644 --- a/src/search.js +++ b/src/search.js @@ -10,7 +10,6 @@ const {isDeprecatedPackage} = require('./deprecated-packages'); module.exports = class Search extends Command { - static promiseBased = true; static commandNames = [ "search" ]; diff --git a/src/star.js b/src/star.js index 3de5a345..11fd437b 100644 --- a/src/star.js +++ b/src/star.js @@ -15,7 +15,6 @@ const request = require('./request'); module.exports = class Star extends Command { - static promiseBased = true; static commandNames = [ "star" ]; parseOptions(argv) { diff --git a/src/stars.js b/src/stars.js index dde8ed71..eb0f7bd6 100644 --- a/src/stars.js +++ b/src/stars.js @@ -11,7 +11,6 @@ const tree = require('./tree'); module.exports = class Stars extends Command { - static promiseBased = true; static commandNames = [ "stars", "starred" ]; parseOptions(argv) { diff --git a/src/test.js b/src/test.js index a91a4a03..938c5082 100644 --- a/src/test.js +++ b/src/test.js @@ -9,7 +9,6 @@ const fs = require('./fs'); module.exports = class Test extends Command { - static promiseBased = true; static commandNames = [ "test" ]; parseOptions(argv) { diff --git a/src/uninstall.js b/src/uninstall.js index eb201ad0..9d78c368 100644 --- a/src/uninstall.js +++ b/src/uninstall.js @@ -13,7 +13,6 @@ const request = require('./request'); module.exports = class Uninstall extends Command { - static promiseBased = true; static commandNames = [ "deinstall", "delete", "erase", "remove", "rm", "uninstall" ]; parseOptions(argv) { diff --git a/src/unlink.js b/src/unlink.js index c0c02d2d..1c1ad09d 100644 --- a/src/unlink.js +++ b/src/unlink.js @@ -10,7 +10,6 @@ const fs = require('./fs'); module.exports = class Unlink extends Command { - static promiseBased = true; static commandNames = [ "unlink" ]; constructor() { diff --git a/src/unpublish.js b/src/unpublish.js index f48c4833..c5eb73e4 100644 --- a/src/unpublish.js +++ b/src/unpublish.js @@ -12,7 +12,6 @@ const request = require('./request'); module.exports = class Unpublish extends Command { - static promiseBased = true; static commandNames = [ "unpublish" ]; parseOptions(argv) { diff --git a/src/unstar.js b/src/unstar.js index 5c660e17..60e75981 100644 --- a/src/unstar.js +++ b/src/unstar.js @@ -9,7 +9,6 @@ const request = require('./request'); module.exports = class Unstar extends Command { - static promiseBased = true; static commandNames = [ "unstar" ]; parseOptions(argv) { diff --git a/src/upgrade.js b/src/upgrade.js index 49e777c7..b7da09e8 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -19,7 +19,6 @@ const git = require('./git'); module.exports = class Upgrade extends Command { - static promiseBased = true; static commandNames = [ "upgrade", "outdated", "update" ]; constructor() { diff --git a/src/view.js b/src/view.js index 6352f29c..ec99bb07 100644 --- a/src/view.js +++ b/src/view.js @@ -10,7 +10,6 @@ const tree = require('./tree'); module.exports = class View extends Command { - static promiseBased = true; static commandNames = [ "view", "show" ]; parseOptions(argv) { From 0e865d2e4f43c239fb1ff3e5f79ae88bc9bbb13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Polg=C3=A1r=20M=C3=A1rton?= Date: Sun, 1 Oct 2023 17:57:37 +0200 Subject: [PATCH 61/64] Eliminating unnecessary Promise contructors in favor of async All tests still passing. Some unused code and leftover wrapping removed. --- src/apm-cli.js | 9 ----- src/disable.js | 32 +++++++----------- src/enable.js | 91 +++++++++++++++++++++++--------------------------- src/install.js | 6 ++-- 4 files changed, 57 insertions(+), 81 deletions(-) diff --git a/src/apm-cli.js b/src/apm-cli.js index 80fe3789..fe47a8ea 100644 --- a/src/apm-cli.js +++ b/src/apm-cli.js @@ -66,15 +66,6 @@ for (let commandClass of commandClasses) { } } -function promisifiedRun(commandRun) { - return function(options) { - return new Promise((resolve, _reject) => { - options.callback = resolve; - commandRun.call(this, options); - }); - }; -} - function parseOptions(args) { args ??= []; const options = yargs(args).wrap(Math.min(100, yargs.terminalWidth())); diff --git a/src/disable.js b/src/disable.js index 2e77dd7f..fa254006 100644 --- a/src/disable.js +++ b/src/disable.js @@ -39,32 +39,26 @@ Disables the named package(s).\ return corePackages.concat(devPackages, userPackages); } - run(options) { - return new Promise((resolve, _reject) => { - let settings; + 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) { - resolve("Could not find config.cson. Run Atom first?"); - return; + return 'Could not find config.cson. Run Atom first?'; //errors as return values atm } + let settings; try { settings = CSON.readFileSync(configFilePath); } catch (error) { - resolve(`Failed to load \`${configFilePath}\`: ${error.message}`); - return; + return `Failed to load \`${configFilePath}\`: ${error.message}`; //errors as return values atm } - return void this.getInstalledPackages().then(installedPackages => { - - - const installedPackageNames = (Array.from(installedPackages).map((pkg) => pkg.name)); - - // uninstalledPackages = (name for name in packageNames when !installedPackageNames[name]) + try { + 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 ')}`); @@ -74,8 +68,7 @@ Disables the named package(s).\ packageNames = _.difference(packageNames, uninstalledPackageNames); if (packageNames.length === 0) { - resolve("Please specify a package to disable"); - return; + return "Please specify a package to disable"; //errors as return values atm } const keyPath = '*.core.disabledPackages'; @@ -86,14 +79,13 @@ Disables the named package(s).\ try { CSON.writeFileSync(configFilePath, settings); } catch (error) { - resolve(`Failed to save \`${configFilePath}\`: ${error.message}`); - return; + return `Failed to save \`${configFilePath}\`: ${error.message}`; //errors as return values atm } console.log(`Disabled:\n ${packageNames.join('\n ')}`); this.logSuccess(); - resolve(); - }, error => void resolve(error)); - }); + } catch (error) { + return error; //errors as return values atm + } } } diff --git a/src/enable.js b/src/enable.js index d481aaeb..da820e96 100644 --- a/src/enable.js +++ b/src/enable.js @@ -23,54 +23,47 @@ Enables the named package(s).\ return options.alias('h', 'help').describe('help', 'Print this usage message'); } - run(options) { - return new Promise((resolve, _reject) => { - let settings; - options = this.parseOptions(options.commandArgs); - let packageNames = this.packageNamesFromArgv(options.argv); - - const configFilePath = CSON.resolve(path.join(config.getAtomDirectory(), 'config')); - if (!configFilePath) { - resolve("Could not find config.cson. Run Atom first?"); - return; - } - - try { - settings = CSON.readFileSync(configFilePath); - } catch (error) { - resolve(`Failed to load \`${configFilePath}\`: ${error.message}`); - return; - } - - const keyPath = '*.core.disabledPackages'; - const disabledPackages = _.valueForKeyPath(settings, keyPath) ?? []; - - const errorPackages = _.difference(packageNames, disabledPackages); - if (errorPackages.length > 0) { - console.log(`Not Disabled:\n ${errorPackages.join('\n ')}`); - } - - // can't enable a package that isn't disabled - packageNames = _.difference(packageNames, errorPackages); - - if (packageNames.length === 0) { - resolve("Please specify a package to enable"); - return; - } - - const result = _.difference(disabledPackages, packageNames); - _.setValueForKeyPath(settings, keyPath, result); - - try { - CSON.writeFileSync(configFilePath, settings); - } catch (error) { - resolve(`Failed to save \`${configFilePath}\`: ${error.message}`); - return; - } - - console.log(`Enabled:\n ${packageNames.join('\n ')}`); - this.logSuccess(); - resolve(); - }); + 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) { + return "Could not find config.cson. Run Atom first?"; //errors as retval atm + } + + let settings; + try { + settings = CSON.readFileSync(configFilePath); + } catch (error) { + return `Failed to load \`${configFilePath}\`: ${error.message}`; //errors as retval atm + } + + const keyPath = '*.core.disabledPackages'; + const disabledPackages = _.valueForKeyPath(settings, keyPath) ?? []; + + const errorPackages = _.difference(packageNames, disabledPackages); + if (errorPackages.length > 0) { + console.log(`Not Disabled:\n ${errorPackages.join('\n ')}`); + } + + // can't enable a package that isn't disabled + packageNames = _.difference(packageNames, errorPackages); + + if (packageNames.length === 0) { + return "Please specify a package to enable"; //errors as retval atm + } + + const result = _.difference(disabledPackages, packageNames); + _.setValueForKeyPath(settings, keyPath, result); + + try { + CSON.writeFileSync(configFilePath, settings); + } catch (error) { + return `Failed to save \`${configFilePath}\`: ${error.message}`; //errors as retval atm + } + + console.log(`Enabled:\n ${packageNames.join('\n ')}`); + this.logSuccess(); } } diff --git a/src/install.js b/src/install.js index a82b89bf..e17a9960 100644 --- a/src/install.js +++ b/src/install.js @@ -121,9 +121,9 @@ package names to install with optional versions using the const child = children[0]; const source = path.join(nodeModulesDirectory, child); const destination = path.join(this.atomPackagesDirectory, child); - commands.push(next => fs.cp(source, destination).then(next, next)); - commands.push(next => this.buildModuleCache(pack.name).then(next, next)); - commands.push(next => this.warmCompileCache(pack.name).then(next, next)); + 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(); } From 72f929d6f5b65412fc034f76055749236a790df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Polg=C3=A1r?= <37218286+2colours@users.noreply.github.com> Date: Wed, 18 Oct 2023 04:03:08 +0200 Subject: [PATCH 62/64] s/Atom/Pulsar/ on the go Co-authored-by: confused_techie --- src/disable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/disable.js b/src/disable.js index fa254006..428313cd 100644 --- a/src/disable.js +++ b/src/disable.js @@ -46,7 +46,7 @@ Disables the named package(s).\ const configFilePath = CSON.resolve(path.join(config.getAtomDirectory(), 'config')); if (!configFilePath) { - return 'Could not find config.cson. Run Atom first?'; //errors as return values atm + return 'Could not find config.cson. Run Pulsar first?'; //errors as return values atm } let settings; From e35010a9219f3e31c0948561367b1bfd4747e11d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Polg=C3=A1r?= <37218286+2colours@users.noreply.github.com> Date: Wed, 18 Oct 2023 04:05:41 +0200 Subject: [PATCH 63/64] s/Atom/Pulsar/ in install Co-authored-by: confused_techie --- src/install.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/install.js b/src/install.js index e17a9960..2cf68a43 100644 --- a/src/install.js +++ b/src/install.js @@ -285,7 +285,7 @@ Run ppm -v after installing Git to see what version has been detected.\ const pack = await this.requestPackage(packageName); packageVersion ??= this.getLatestCompatibleVersion(pack); if (!packageVersion) { - throw `No available version compatible with the installed Atom version: ${this.installedAtomVersion}`; + throw `No available version compatible with the installed Pulsar version: ${this.installedAtomVersion}`; } const {tarball} = pack.versions[packageVersion]?.dist ?? {}; if (!tarball) { From 4d0b6796ac01cf48d9429a328c4a5568a03d20fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Polg=C3=A1r?= <37218286+2colours@users.noreply.github.com> Date: Wed, 18 Oct 2023 04:10:26 +0200 Subject: [PATCH 64/64] s/Atom/Pulsar/ in enable Co-authored-by: confused_techie --- src/enable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/enable.js b/src/enable.js index da820e96..57a38c75 100644 --- a/src/enable.js +++ b/src/enable.js @@ -29,7 +29,7 @@ Enables the named package(s).\ const configFilePath = CSON.resolve(path.join(config.getAtomDirectory(), 'config')); if (!configFilePath) { - return "Could not find config.cson. Run Atom first?"; //errors as retval atm + return "Could not find config.cson. Run Pulsar first?"; //errors as retval atm } let settings;