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

Update "use compser version constraint logic" feature branch #66

Closed
Show file tree
Hide file tree
Changes from 18 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
29 changes: 20 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ name: CI

on: [push]

# Automatically cancel previous runs for the same ref (i.e. branch)
concurrency:
group: ${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '16'
node-version-file: 'package.json'
cache: 'npm'
cache-dependency-path: 'package-lock.json'
- run: npm ci
- run: npm run eslint
- run: npm run cspell
Expand All @@ -19,10 +26,14 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- uses: JS-DevTools/npm-publish@v1
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
token: ${{ secrets.NPM_ACCESS_TOKEN }}
node-version-file: 'package.json'
cache: 'npm'
cache-dependency-path: 'package-lock.json'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
* Really adds support for Shopware's 4-digit version numbers.
* For evaluating composer version constraints the composer semver constraint logic is now used.

## 4.0.0

### Breaking changes

* Drops support of Node.js < v18.

### Bug fixes

* Fixes `upload` command.

## 3.1.0

* Enables support for Node.js > v14.
Expand Down
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,
},
);

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
Loading