From 901220149360e405aab94cd1fefdd494f5ff9bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20Mu=CC=88nnich?= Date: Mon, 6 May 2024 18:00:21 +0200 Subject: [PATCH] Fix `upload` command Fixes #61 --- lib/commands/upload.js | 103 +++++++++++++++++-------------- lib/shopwareStoreCommander.js | 111 ++++++++++++++++++++++++++++------ 2 files changed, 150 insertions(+), 64 deletions(-) diff --git a/lib/commands/upload.js b/lib/commands/upload.js index af0e55c..957428c 100755 --- a/lib/commands/upload.js +++ b/lib/commands/upload.js @@ -36,7 +36,7 @@ Program .option('-R, --no-release', 'Set this option to not submit the uploaded binary for review.') .option( '--store-ioncube-encode ', - 'Whether the Store should automatically ionCube-encode the released binary (default is \'auto\', which retains previous release\'s setting)', + 'Deprecated: Shopware no longer supports ionCube encryption.', parseOnOffAutoOption, 'auto', ) @@ -64,6 +64,9 @@ if (typeof Program.opts().username === 'undefined') { async function main() { const pluginZipFilePath = path.resolve(process.cwd(), Program.args[0]); + if (Program.opts().storeIoncubeEncode !== undefined) { + console.error(Chalk.yellow.bold('Warning: Option \'--store-ioncube-encode\' is deprecated because Shopware no longer supports ionCube encryption. Ignoring.')); + } try { const password = await getPassword(Program); const commander = new ShopwareStoreCommander(Program.opts().username, password); @@ -100,15 +103,6 @@ async function main() { ); const latestReleasedBinary = (releasedBinaries.length > 0) ? releasedBinaries[0] : null; - if (Program.opts().storeIoncubeEncode === 'auto') { - // Set the option based on the latest released binary, if possible - if (latestReleasedBinary) { - Program.opts().storeIoncubeEncode = latestReleasedBinary.ionCubeEncrypted; - } else { - Program.opts().storeIoncubeEncode = false; - console.error(Chalk.yellow.bold('Warning: Cannot automatically determine value for option \'--store-ioncube-encode\', because no valid, released binary exists which it could have been derived from. Using \'false\' instead.')); - } - } if (Program.opts().licenseCheckRequired === 'auto') { // Set the option based on the latest released binary, if possible if (latestReleasedBinary) { @@ -119,31 +113,15 @@ async function main() { } } - // Make sure that the version of the passed binary does not exist yet - const conflictingBinary = remotePlugin.binaries.find( - binary => binary.version.length > 0 && binary.version === localPlugin.version, - ); - if (conflictingBinary) { - if (Program.opts().force) { - remotePlugin = await commander.updatePluginBinary(remotePlugin, conflictingBinary, pluginZipFilePath); - } else { - throw new Error(`The binary version ${conflictingBinary.version} you're trying to upload already exists for plugin ${remotePlugin.name}`); - } - } else { - // Upload the binary - remotePlugin = await commander.uploadPluginBinary(remotePlugin, pluginZipFilePath); - } - - // Update the uploaded binary using the plugin info - console.error(`Set version to ${(localPlugin.version)}`); - remotePlugin.latestBinary.version = localPlugin.version; - remotePlugin.latestBinary.changelogs.forEach((changelog) => { - const lang = changelog.locale.name.split('_').shift(); - console.error(`Set changelog for language '${lang}'`); + console.error(`Setting version to ${(localPlugin.version)}...`); + const { supportedLocales } = await commander.getAccountData(); + const changelogs = supportedLocales.map((locale) => { + const language = locale.split('_').shift(); + console.error(`Preparing changelog for language '${language}'...`); // Try to find a changelog let changelogText = ''; try { - changelogText = localPlugin.releaseNotes[lang].toHtml(); + changelogText = localPlugin.releaseNotes[language].toHtml(); } catch (e) { console.error(Chalk.yellow.bold(`\u{26A0} ${e.message}`)); } @@ -154,25 +132,58 @@ async function main() { changelogText += '\u{0020}'; } - changelog.text = changelogText; + return { + locale, + text: changelogText, + }; }); - const shopwareVersions = commander.statics.softwareVersions; - remotePlugin.latestBinary.compatibleSoftwareVersions = shopwareVersions.filter( - version => version.selectable && localPlugin.isCompatibleWithShopwareVersion(version.name), - ); - const compatibleVersionStrings = remotePlugin.latestBinary.compatibleSoftwareVersions.map(version => version.name); - if (compatibleVersionStrings.length > 0) { - console.error(`Set shopware version compatibility: ${compatibleVersionStrings.join(', ')}`); + const compatibleShopwareVersions = commander.statics.softwareVersions + .filter(version => version.selectable && localPlugin.isCompatibleWithShopwareVersion(version.name)) + .map(version => version.name); + if (compatibleShopwareVersions.length > 0) { + console.error(`Setting shopware version compatibility: ${compatibleShopwareVersions.join(', ')}`); } else { console.error( Chalk.yellow.bold('\u{26A0} Warning: The plugin\'s compatibility constraints don\'t match any available shopware versions!'), ); } - remotePlugin.latestBinary.ionCubeEncrypted = Program.opts().storeIoncubeEncode; - remotePlugin.latestBinary.licenseCheckRequired = Program.opts().licenseCheckRequired; - remotePlugin = await commander.savePluginBinary(remotePlugin, remotePlugin.latestBinary); - const uploadSuccessMessage = `New version ${remotePlugin.latestBinary.version} of plugin ${remotePlugin.name} uploaded! \u{2705}`; + // Make sure that the version of the passed binary does not exist yet + const conflictingBinary = remotePlugin.binaries.find( + binary => binary.version.length > 0 && binary.version === localPlugin.version, + ); + let existingBinary; + if (!conflictingBinary) { + await commander.validatePluginBinaryFile(remotePlugin, pluginZipFilePath); + existingBinary = await commander.createPluginBinary( + remotePlugin, + localPlugin.version, + changelogs, + compatibleShopwareVersions, + ); + } else if (Program.opts().force) { + existingBinary = conflictingBinary; + } else { + throw new Error(`The binary version ${conflictingBinary.version} you're trying to upload already exists for plugin ${remotePlugin.name}`); + } + + let updatedBinary = await commander.uploadPluginBinaryFile( + remotePlugin, + existingBinary, + pluginZipFilePath, + ); + + // Always update the binary after uploading to set the licenseCheckRequired flag because it is not settable + // during creation + updatedBinary = await commander.updatePluginBinary( + remotePlugin, + updatedBinary, + changelogs, + compatibleShopwareVersions, + Program.opts().licenseCheckRequired, + ); + + const uploadSuccessMessage = `New version ${updatedBinary.version} of plugin ${remotePlugin.name} uploaded! \u{2705}`; console.error(Chalk.green.bold(uploadSuccessMessage)); if (!Program.opts().release) { util.showGrowlIfEnabled(uploadSuccessMessage); @@ -186,10 +197,10 @@ async function main() { // Check review status const review = remotePlugin.reviews[remotePlugin.reviews.length - 1]; if (review.status.name !== 'approved') { - throw new Error(`The review of ${remotePlugin.name} v${remotePlugin.latestBinary.version} finished with status '${review.status.name}':\n\n${review.comment}`); + throw new Error(`The review of ${remotePlugin.name} v${updatedBinary.version} finished with status '${review.status.name}':\n\n${review.comment}`); } - const successMessage = `Review succeeded! Version ${remotePlugin.latestBinary.version} of plugin ${remotePlugin.name} is now available in the store. \u{1F389}`; + const successMessage = `Review succeeded! Version ${updatedBinary.version} of plugin ${remotePlugin.name} is now available in the store. \u{1F389}`; util.showGrowlIfEnabled(successMessage); console.error(Chalk.green.bold(successMessage)); diff --git a/lib/shopwareStoreCommander.js b/lib/shopwareStoreCommander.js index aa9c1a6..cc9234d 100644 --- a/lib/shopwareStoreCommander.js +++ b/lib/shopwareStoreCommander.js @@ -59,6 +59,7 @@ module.exports = class ShopwareStoreCommander { // Save producer ID (required to load e.g. plugins) this.accountData = { producerId: producers.data[0].id, + supportedLocales: producers.data[0].supportedLanguages.map(language => language.name), }; const plugins = await this.client.get('plugins', { @@ -126,21 +127,82 @@ module.exports = class ShopwareStoreCommander { /** * @param {Object} plugin - * @param {String} filePath + * @param {String} version + * @param {Array} changelogs + * @param {Array} compatibleShopwareVersions * @return {Object} */ - async uploadPluginBinary(plugin, filePath) { - const binaryName = filePath.split(/(\\|\/)/g).pop(); - this.logEmitter.emit('info', `Uploading binary ${binaryName} for plugin ${plugin.name}...`); + async createPluginBinary(plugin, version, changelogs, compatibleShopwareVersions) { + this.logEmitter.emit('info', `Creating binary version ${version} of plugin ${plugin.name}...`); - const { data, headers } = await getPostDataFromFile(filePath); - const res = await this.client.post(`plugins/${plugin.id}/binaries`, data, { headers }); + const accountData = await this.getAccountData(); + const { data: newBinary } = await this.client.post( + `producers/${accountData.producerId}/plugins/${plugin.id}/binaries`, + { + version, + changelogs, + softwareVersions: compatibleShopwareVersions, + }, + ); - // Add the binary info to the plugin - plugin.binaries = res.data; + const matchingBinaryIndex = plugin.binaries.findIndex(existingBinary => existingBinary.id === newBinary.id); + if (matchingBinaryIndex === -1) { + plugin.binaries.push(newBinary); + } else { + plugin.binaries[matchingBinaryIndex] = newBinary; + } plugin.latestBinary = plugin.binaries[plugin.binaries.length - 1]; - return plugin; + return newBinary; + } + + /** + * @param {Object} plugin + * @param {Object} binary + * @param {Array} changelogs + * @param {Array} compatibleShopwareVersions + * @param {Boolean} licenseCheckRequired + * @return {Object} + */ + async updatePluginBinary(plugin, binary, changelogs, compatibleShopwareVersions, licenseCheckRequired) { + this.logEmitter.emit('info', `Updating binary version ${binary.version} of plugin ${plugin.name}...`); + + const accountData = await this.getAccountData(); + const { data: updatedBinary } = await this.client.put( + `producers/${accountData.producerId}/plugins/${plugin.id}/binaries/${binary.id}`, + { + changelogs, + softwareVersions: compatibleShopwareVersions, + licenseCheckRequired, + // ionCube encryption is no longer supported + ionCubeEncrypted: false, + }, + ); + + const matchingBinaryIndex = plugin.binaries.findIndex(existingBinary => existingBinary.id === binary.id); + if (matchingBinaryIndex !== -1) { + plugin.binaries[matchingBinaryIndex] = updatedBinary; + plugin.latestBinary = plugin.binaries[plugin.binaries.length - 1]; + } + + return updatedBinary; + } + + /** + * @param {Object} plugin + * @param {String} filePath + */ + async validatePluginBinaryFile(plugin, filePath) { + const binaryName = filePath.split(/(\\|\/)/g).pop(); + this.logEmitter.emit('info', `Validating binary ${binaryName} for plugin ${plugin.name}...`); + + const { data, headers } = await getPostDataFromFile(filePath); + const accountData = await this.getAccountData(); + await this.client.post( + `producers/${accountData.producerId}/plugins/${plugin.id}/binaries/validate`, + data, + { headers }, + ); } /** @@ -149,18 +211,25 @@ module.exports = class ShopwareStoreCommander { * @param {String} filePath * @return {Object} */ - async updatePluginBinary(plugin, binary, filePath) { + async uploadPluginBinaryFile(plugin, binary, filePath) { const binaryName = filePath.split(/(\\|\/)/g).pop(); - this.logEmitter.emit('info', `Uploading updated binary ${binaryName} for plugin ${plugin.name}...`); + this.logEmitter.emit('info', `Uploading binary ${binaryName} for plugin ${plugin.name}...`); const { data, headers } = await getPostDataFromFile(filePath); - const res = await this.client.post(`plugins/${plugin.id}/binaries/${binary.id}/file`, data, { headers }); + const accountData = await this.getAccountData(); + const { data: updatedBinary } = await this.client.post( + `producers/${accountData.producerId}/plugins/${plugin.id}/binaries/${binary.id}/file`, + data, + { headers }, + ); - // Add the binary info to the plugin - plugin.binaries = res.data; - plugin.latestBinary = plugin.binaries[plugin.binaries.length - 1]; + const matchingBinaryIndex = plugin.binaries.findIndex(existingBinary => existingBinary.id === binary.id); + if (matchingBinaryIndex !== -1) { + plugin.binaries[matchingBinaryIndex] = updatedBinary; + plugin.latestBinary = plugin.binaries[plugin.binaries.length - 1]; + } - return plugin; + return updatedBinary; } /** @@ -171,7 +240,11 @@ module.exports = class ShopwareStoreCommander { async savePluginBinary(plugin, binary) { this.logEmitter.emit('info', `Saving binary version ${binary.version} of plugin ${plugin.name}...`); - const res = await this.client.put(`plugins/${plugin.id}/binaries/${binary.id}`, binary); + const accountData = await this.getAccountData(); + const res = await this.client.put( + `producers/${accountData.producerId}/plugins/${plugin.id}/binaries/${binary.id}`, + binary, + ); // Save the updated data locally binary.changelogs = res.data.changelogs; binary.compatibleSoftwareVersions = res.data.compatibleSoftwareVersions; @@ -274,8 +347,10 @@ module.exports = class ShopwareStoreCommander { async loadExtraPluginFields(plugin, fields) { plugin.scsLoadedExtraFields = plugin.scsLoadedExtraFields || []; // Load all extra fields + const accountData = await this.getAccountData(); const extraFieldPromises = fields.map(async (field) => { - const res = await this.client.get(`plugins/${plugin.id}/${field}`); + const path = (field === 'binaries') ? `producers/${accountData.producerId}/plugins/${plugin.id}/${field}` : `plugins/${plugin.id}/${field}`; + const res = await this.client.get(path); plugin[field] = res.data; // Mark the extra field as loaded if (plugin.scsLoadedExtraFields.indexOf(field) === -1) {