Add support to fetch tool versions from packagist

This commit is contained in:
Shivam Mathur 2023-06-12 10:55:01 +05:30
parent 0be6fa1958
commit 6cc8b47c6f
No known key found for this signature in database
GPG Key ID: 3E13E4C8591ACC2A
8 changed files with 390 additions and 32 deletions

View File

@ -0,0 +1,50 @@
import * as packagist from '../src/packagist';
import nock = require('nock');
describe('search function', () => {
const mockResponse = {
packages: {
'test-package': [
{
require: {
php: '8.0.0'
},
version: '1.0.0'
},
{
version: '2.0.0'
}
]
}
};
test('should return the version if matching php version is found', async () => {
nock('https://repo.packagist.org')
.get('/p2/test-package.json')
.reply(200, mockResponse);
const result = await packagist.search('test-package', '8.0');
expect(result).toBe('1.0.0');
});
test('should return null if no matching php version is found', async () => {
nock('https://repo.packagist.org')
.get('/p2/test-package.json')
.reply(200, mockResponse);
const result = await packagist.search('test-package', '5.6');
expect(result).toBeNull();
});
test('should return null if fetch fails', async () => {
nock('https://repo.packagist.org').get('/p2/test-package.json').reply(404);
const result = await packagist.search('test-package', '8.0');
expect(result).toBeNull();
});
test('should return null if the response is empty', async () => {
nock('https://repo.packagist.org')
.get('/p2/test-package.json')
.reply(200, {error: true, data: '[]'});
const result = await packagist.search('test-package', '8.0');
expect(result).toBeNull();
});
});

View File

@ -70,6 +70,22 @@ jest.mock('../src/fetch', () => ({
) )
})); }));
jest.mock('../src/packagist', () => ({
search: jest
.fn()
.mockImplementation(
async (
package_name: string,
php_version: string
): Promise<string | null> => {
if (package_name === 'phpunit/phpunit') {
return php_version + '.0';
}
return null;
}
)
}));
describe('Tools tests', () => { describe('Tools tests', () => {
it.each` it.each`
token | version token | version
@ -387,7 +403,7 @@ describe('Tools tests', () => {
'add_tool https://github.com/phpDocumentor/phpDocumentor/releases/latest/download/phpDocumentor.phar phpDocumentor "--version"', 'add_tool https://github.com/phpDocumentor/phpDocumentor/releases/latest/download/phpDocumentor.phar phpDocumentor "--version"',
'add_composer_tool phplint phplint overtrue/', 'add_composer_tool phplint phplint overtrue/',
'add_tool https://github.com/phpstan/phpstan/releases/latest/download/phpstan.phar phpstan "-V"', 'add_tool https://github.com/phpstan/phpstan/releases/latest/download/phpstan.phar phpstan "-V"',
'add_tool https://phar.phpunit.de/phpunit.phar phpunit "--version"', 'add_tool https://phar.phpunit.de/phpunit-7.4.0.phar phpunit "--version"',
'add_pecl', 'add_pecl',
'add_tool https://www.phing.info/get/phing-latest.phar phing "-v"', 'add_tool https://www.phing.info/get/phing-latest.phar phing "-v"',
'add_composer_tool phinx phinx robmorgan/ scoped', 'add_composer_tool phinx phinx robmorgan/ scoped',
@ -513,7 +529,7 @@ describe('Tools tests', () => {
it.each` it.each`
tools_csv | script tools_csv | script
${'none'} | ${''} ${'none'} | ${''}
${'none, phpunit'} | ${'\nstep_log "Setup Tools"\nadd_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-stable.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-stable.phar,https://getcomposer.org/composer-stable.phar composer latest\n\nadd_tool https://phar.phpunit.de/phpunit.phar phpunit "--version"'} ${'none, phpunit'} | ${'\nstep_log "Setup Tools"\nadd_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-stable.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-stable.phar,https://getcomposer.org/composer-stable.phar composer latest\n\nadd_tool https://phar.phpunit.de/phpunit-7.4.0.phar phpunit "--version"'}
${'composer:preview'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-preview.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-preview.phar,https://getcomposer.org/composer-preview.phar composer preview'} ${'composer:preview'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-preview.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-preview.phar,https://getcomposer.org/composer-preview.phar composer preview'}
${'composer, composer:v1'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-1.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-1.phar,https://getcomposer.org/composer-1.phar composer'} ${'composer, composer:v1'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-1.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-1.phar,https://getcomposer.org/composer-1.phar composer'}
${'composer:v1, composer:preview, composer:snapshot'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-snapshot.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-snapshot.phar,https://getcomposer.org/composer.phar composer snapshot'} ${'composer:v1, composer:preview, composer:snapshot'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-snapshot.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-snapshot.phar,https://getcomposer.org/composer.phar composer snapshot'}
@ -543,13 +559,13 @@ describe('Tools tests', () => {
it.each` it.each`
tools_csv | php_version | resolved tools_csv | php_version | resolved
${'phpunit'} | ${'8.2'} | ${'/phpunit.phar'} ${'phpunit'} | ${'8.2'} | ${'/phpunit-8.2.0.phar'}
${'phpunit'} | ${'8.1'} | ${'/phpunit.phar'} ${'phpunit'} | ${'8.1'} | ${'/phpunit-8.1.0.phar'}
${'phpunit'} | ${'8.0'} | ${'/phpunit-9.6.8.phar'} ${'phpunit'} | ${'8.0'} | ${'/phpunit-8.0.0.phar'}
${'phpunit'} | ${'7.3'} | ${'/phpunit-9.6.8.phar'} ${'phpunit'} | ${'7.3'} | ${'/phpunit-7.3.0.phar'}
${'phpunit'} | ${'7.2'} | ${'/phpunit-8.5.33.phar'} ${'phpunit'} | ${'7.2'} | ${'/phpunit-7.2.0.phar'}
${'phpunit'} | ${'7.1'} | ${'/phpunit-7.5.20.phar'} ${'phpunit'} | ${'7.1'} | ${'/phpunit-7.1.0.phar'}
${'phpunit'} | ${'7.0'} | ${'/phpunit-6.5.14.phar'} ${'phpunit'} | ${'7.0'} | ${'/phpunit-7.0.0.phar'}
`( `(
'checking error: $tools_csv', 'checking error: $tools_csv',
async ({tools_csv, php_version, resolved}) => { async ({tools_csv, php_version, resolved}) => {

268
dist/index.js vendored
View File

@ -592,6 +592,64 @@ exports.run = run;
/***/ }), /***/ }),
/***/ 5151:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.search = void 0;
const cv = __importStar(__nccwpck_require__(4773));
const fetch = __importStar(__nccwpck_require__(2387));
async function search(package_name, php_version) {
const response = await fetch.fetch(`https://repo.packagist.org/p2/${package_name}.json`);
if (response.error || response.data === '[]') {
return null;
}
const data = JSON.parse(response['data']);
if (data && data.packages) {
const versions = data.packages[package_name];
versions.sort((a, b) => cv.compareVersions(b.version, a.version));
const result = versions.find((versionData) => {
if (versionData?.require?.php) {
return versionData?.require?.php
.split('|')
.some(require => require && cv.satisfies(php_version + '.0', require));
}
return false;
});
return result ? result.version : null;
}
return null;
}
exports.search = search;
//# sourceMappingURL=packagist.js.map
/***/ }),
/***/ 7740: /***/ 7740:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
@ -628,6 +686,7 @@ exports.addTools = exports.functionRecord = exports.getData = exports.addWPCLI =
const path_1 = __importDefault(__nccwpck_require__(1017)); const path_1 = __importDefault(__nccwpck_require__(1017));
const fs_1 = __importDefault(__nccwpck_require__(7147)); const fs_1 = __importDefault(__nccwpck_require__(7147));
const fetch = __importStar(__nccwpck_require__(2387)); const fetch = __importStar(__nccwpck_require__(2387));
const packagist = __importStar(__nccwpck_require__(5151));
const utils = __importStar(__nccwpck_require__(918)); const utils = __importStar(__nccwpck_require__(918));
async function getSemverVersion(data) { async function getSemverVersion(data) {
const search = data['version_prefix'] + data['version']; const search = data['version_prefix'] + data['version'];
@ -885,16 +944,10 @@ async function addPhive(data) {
} }
exports.addPhive = addPhive; exports.addPhive = addPhive;
async function addPHPUnitTools(data) { async function addPHPUnitTools(data) {
if (data['version'] == 'latest') { if (data['version'] === 'latest') {
if (/7\.3|8\.0/.test(data['php_version'])) { data['version'] =
data['version'] = '9.6.8'; (await packagist.search(data['repository'], data['php_version'])) ??
} else if (/7\.[2-3]/.test(data['php_version'])) { 'latest';
data['version'] = '8.5.33';
} else if (/7\.[1-3]/.test(data['php_version'])) {
data['version'] = '7.5.20';
} else if (/7\.[0-2]/.test(data['php_version'])) {
data['version'] = '6.5.14';
}
} }
data['url'] = await getPharUrl(data); data['url'] = await getPharUrl(data);
return await addArchive(data); return await addArchive(data);
@ -4314,6 +4367,201 @@ function copyFile(srcFile, destFile, force) {
} }
//# sourceMappingURL=io.js.map //# sourceMappingURL=io.js.map
/***/ }),
/***/ 4773:
/***/ (function(__unused_webpack_module, exports) {
(function (global, factory) {
true ? factory(exports) :
0;
})(this, (function (exports) { 'use strict';
/**
* Compare [semver](https://semver.org/) version strings to find greater, equal or lesser.
* This library supports the full semver specification, including comparing versions with different number of digits like `1.0.0`, `1.0`, `1`, and pre-release versions like `1.0.0-alpha`.
* @param v1 - First version to compare
* @param v2 - Second version to compare
* @returns Numeric value compatible with the [Array.sort(fn) interface](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Parameters).
*/
const compareVersions = (v1, v2) => {
// validate input and split into segments
const n1 = validateAndParse(v1);
const n2 = validateAndParse(v2);
// pop off the patch
const p1 = n1.pop();
const p2 = n2.pop();
// validate numbers
const r = compareSegments(n1, n2);
if (r !== 0)
return r;
// validate pre-release
if (p1 && p2) {
return compareSegments(p1.split('.'), p2.split('.'));
}
else if (p1 || p2) {
return p1 ? -1 : 1;
}
return 0;
};
/**
* Validate [semver](https://semver.org/) version strings.
*
* @param version Version number to validate
* @returns `true` if the version number is a valid semver version number, `false` otherwise.
*
* @example
* ```
* validate('1.0.0-rc.1'); // return true
* validate('1.0-rc.1'); // return false
* validate('foo'); // return false
* ```
*/
const validate = (version) => typeof version === 'string' && /^[v\d]/.test(version) && semver.test(version);
/**
* Compare [semver](https://semver.org/) version strings using the specified operator.
*
* @param v1 First version to compare
* @param v2 Second version to compare
* @param operator Allowed arithmetic operator to use
* @returns `true` if the comparison between the firstVersion and the secondVersion satisfies the operator, `false` otherwise.
*
* @example
* ```
* compare('10.1.8', '10.0.4', '>'); // return true
* compare('10.0.1', '10.0.1', '='); // return true
* compare('10.1.1', '10.2.2', '<'); // return true
* compare('10.1.1', '10.2.2', '<='); // return true
* compare('10.1.1', '10.2.2', '>='); // return false
* ```
*/
const compare = (v1, v2, operator) => {
// validate input operator
assertValidOperator(operator);
// since result of compareVersions can only be -1 or 0 or 1
// a simple map can be used to replace switch
const res = compareVersions(v1, v2);
return operatorResMap[operator].includes(res);
};
/**
* Match [npm semver](https://docs.npmjs.com/cli/v6/using-npm/semver) version range.
*
* @param version Version number to match
* @param range Range pattern for version
* @returns `true` if the version number is within the range, `false` otherwise.
*
* @example
* ```
* satisfies('1.1.0', '^1.0.0'); // return true
* satisfies('1.1.0', '~1.0.0'); // return false
* ```
*/
const satisfies = (version, range) => {
// handle multiple comparators
if (range.includes('||')) {
return range.split('||').some((r) => satisfies(version, r));
}
else if (range.includes(' ')) {
return range
.trim()
.replace(/\s{2,}/g, ' ')
.split(' ')
.every((r) => satisfies(version, r));
}
// if no range operator then "="
const m = range.match(/^([<>=~^]+)/);
const op = m ? m[1] : '=';
// if gt/lt/eq then operator compare
if (op !== '^' && op !== '~')
return compare(version, range, op);
// else range of either "~" or "^" is assumed
const [v1, v2, v3, , vp] = validateAndParse(version);
const [r1, r2, r3, , rp] = validateAndParse(range);
const v = [v1, v2, v3];
const r = [r1, r2 !== null && r2 !== void 0 ? r2 : 'x', r3 !== null && r3 !== void 0 ? r3 : 'x'];
// validate pre-release
if (rp) {
if (!vp)
return false;
if (compareSegments(v, r) !== 0)
return false;
if (compareSegments(vp.split('.'), rp.split('.')) === -1)
return false;
}
// first non-zero number
const nonZero = r.findIndex((v) => v !== '0') + 1;
// pointer to where segments can be >=
const i = op === '~' ? 2 : nonZero > 1 ? nonZero : 1;
// before pointer must be equal
if (compareSegments(v.slice(0, i), r.slice(0, i)) !== 0)
return false;
// after pointer must be >=
if (compareSegments(v.slice(i), r.slice(i)) === -1)
return false;
return true;
};
const semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
const validateAndParse = (version) => {
if (typeof version !== 'string') {
throw new TypeError('Invalid argument expected string');
}
const match = version.match(semver);
if (!match) {
throw new Error(`Invalid argument not valid semver ('${version}' received)`);
}
match.shift();
return match;
};
const isWildcard = (s) => s === '*' || s === 'x' || s === 'X';
const tryParse = (v) => {
const n = parseInt(v, 10);
return isNaN(n) ? v : n;
};
const forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
const compareStrings = (a, b) => {
if (isWildcard(a) || isWildcard(b))
return 0;
const [ap, bp] = forceType(tryParse(a), tryParse(b));
if (ap > bp)
return 1;
if (ap < bp)
return -1;
return 0;
};
const compareSegments = (a, b) => {
for (let i = 0; i < Math.max(a.length, b.length); i++) {
const r = compareStrings(a[i] || '0', b[i] || '0');
if (r !== 0)
return r;
}
return 0;
};
const operatorResMap = {
'>': [1],
'>=': [0, 1],
'=': [0],
'<=': [-1, 0],
'<': [-1],
};
const allowedOperators = Object.keys(operatorResMap);
const assertValidOperator = (op) => {
if (typeof op !== 'string') {
throw new TypeError(`Invalid operator type, expected string but got ${typeof op}`);
}
if (allowedOperators.indexOf(op) === -1) {
throw new Error(`Invalid operator, expected one of ${allowedOperators.join('|')}`);
}
};
exports.compare = compare;
exports.compareVersions = compareVersions;
exports.satisfies = satisfies;
exports.validate = validate;
}));
//# sourceMappingURL=index.js.map
/***/ }), /***/ }),
/***/ 4294: /***/ 4294:

13
package-lock.json generated
View File

@ -11,7 +11,8 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1", "@actions/exec": "^1.1.1",
"@actions/io": "^1.1.3" "@actions/io": "^1.1.3",
"compare-versions": "^6.0.0-rc.1"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.1", "@types/jest": "^29.5.1",
@ -2024,6 +2025,11 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "dev": true
}, },
"node_modules/compare-versions": {
"version": "6.0.0-rc.1",
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.0.0-rc.1.tgz",
"integrity": "sha512-cFhkjbGY1jLFWIV7KegECbfuyYPxSGvgGkdkfM+ibboQDoPwg2FRHm5BSNTOApiauRBzJIQH7qvOJs2sW5ueKQ=="
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -7229,6 +7235,11 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "dev": true
}, },
"compare-versions": {
"version": "6.0.0-rc.1",
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.0.0-rc.1.tgz",
"integrity": "sha512-cFhkjbGY1jLFWIV7KegECbfuyYPxSGvgGkdkfM+ibboQDoPwg2FRHm5BSNTOApiauRBzJIQH7qvOJs2sW5ueKQ=="
},
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",

View File

@ -36,7 +36,8 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1", "@actions/exec": "^1.1.1",
"@actions/io": "^1.1.3" "@actions/io": "^1.1.3",
"compare-versions": "^6.0.0-rc.1"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.1", "@types/jest": "^29.5.1",

View File

@ -267,7 +267,7 @@
}, },
"phpunit": { "phpunit": {
"type": "custom-function", "type": "custom-function",
"repository": "sebastianbergmann/phpunit", "repository": "phpunit/phpunit",
"domain": "https://phar.phpunit.de", "domain": "https://phar.phpunit.de",
"function": "phpunit", "function": "phpunit",
"version_prefix": "", "version_prefix": "",

37
src/packagist.ts Normal file
View File

@ -0,0 +1,37 @@
import * as cv from 'compare-versions';
import * as fetch from './fetch';
type RS = Record<string, string>;
type RSRS = Record<string, RS>;
export async function search(
package_name: string,
php_version: string
): Promise<string | null> {
const response = await fetch.fetch(
`https://repo.packagist.org/p2/${package_name}.json`
);
if (response.error || response.data === '[]') {
return null;
}
const data = JSON.parse(response['data']);
if (data && data.packages) {
const versions = data.packages[package_name];
versions.sort((a: RS, b: RS) => cv.compareVersions(b.version, a.version));
const result = versions.find((versionData: RSRS) => {
if (versionData?.require?.php) {
return versionData?.require?.php
.split('|')
.some(
require => require && cv.satisfies(php_version + '.0', require)
);
}
return false;
});
return result ? result.version : null;
}
return null;
}

View File

@ -1,6 +1,7 @@
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import * as fetch from './fetch'; import * as fetch from './fetch';
import * as packagist from './packagist';
import * as utils from './utils'; import * as utils from './utils';
type RS = Record<string, string>; type RS = Record<string, string>;
@ -392,16 +393,10 @@ export async function addPhive(data: RS): Promise<string> {
* @param data * @param data
*/ */
export async function addPHPUnitTools(data: RS): Promise<string> { export async function addPHPUnitTools(data: RS): Promise<string> {
if (data['version'] == 'latest') { if (data['version'] === 'latest') {
if (/7\.3|8\.0/.test(data['php_version'])) { data['version'] =
data['version'] = '9.6.8'; (await packagist.search(data['repository'], data['php_version'])) ??
} else if (/7\.[2-3]/.test(data['php_version'])) { 'latest';
data['version'] = '8.5.33';
} else if (/7\.[1-3]/.test(data['php_version'])) {
data['version'] = '7.5.20';
} else if (/7\.[0-2]/.test(data['php_version'])) {
data['version'] = '6.5.14';
}
} }
data['url'] = await getPharUrl(data); data['url'] = await getPharUrl(data);
return await addArchive(data); return await addArchive(data);