Skip to content
This repository has been archived by the owner on Aug 6, 2024. It is now read-only.

Fix upload command #62

Merged
merged 1 commit into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 57 additions & 46 deletions lib/commands/upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
case 'auto':
return suppliedValue;
default:
console.error(Chalk.white.bgRed.bold(`Invalid value '${suppliedValue}' for option --license-check-required.`));

Check warning on line 24 in lib/commands/upload.js

View workflow job for this annotation

GitHub Actions / lint

Line 24 exceeds the maximum line length of 120
process.exit(-1);

return undefined;
Expand All @@ -36,13 +36,13 @@
.option('-R, --no-release', 'Set this option to not submit the uploaded binary for review.')
.option(
'--store-ioncube-encode <on|off|auto>',
'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',
)
.option(
'--license-check-required <on|off|auto>',
'Whether the Store should check for a \'checkLicense\' method in the released binary (default is \'auto\', which retains previous release\'s setting)',

Check warning on line 45 in lib/commands/upload.js

View workflow job for this annotation

GitHub Actions / lint

Line 45 exceeds the maximum line length of 120
parseOnOffAutoOption,
'auto',
)
Expand All @@ -64,6 +64,9 @@

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);
Expand Down Expand Up @@ -100,15 +103,6 @@
);
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) {
Expand All @@ -119,31 +113,15 @@
}
}

// 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}`));
}
Expand All @@ -154,25 +132,58 @@
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);
Expand All @@ -186,10 +197,10 @@
// 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));

Expand Down
111 changes: 93 additions & 18 deletions lib/shopwareStoreCommander.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand Down Expand Up @@ -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,
Comment on lines +177 to +178

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just skip sending the parameter then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request sent by the Shopware account app contained this in its payload. Without it I got an HTTP 400.

},
);

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 },
);
}

/**
Expand All @@ -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;
}

/**
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Loading