diff --git a/docs/usage.md b/docs/usage.md index f2dc68dd..93158bc6 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -24,6 +24,8 @@ Check additional information below. -b, --browser [๐Ÿ’ป๐Ÿ”—๐Ÿงช] Browser instantiation command (relative to cwd or use $/ for provided ones) (default: "$/puppeteer.js") --browser-args [๐Ÿ’ป๐Ÿ”—๐Ÿงช] Browser instantiation command parameters (use -- instead) + --alternate-npm-path [๐Ÿ’ป๐Ÿ”—] Alternate NPM path to look for packages (priority: local, + alternate, global) --no-npm-install [๐Ÿ’ป๐Ÿ”—๐Ÿงช] Prevent any NPM install (execution may fail if a dependency is missing) -bt, --browser-close-timeout [๐Ÿ’ป๐Ÿ”—๐Ÿงช] Maximum waiting time for browser close (default: 2000) diff --git a/package-lock.json b/package-lock.json index b8927d10..85eb3c4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ui5-test-runner", - "version": "4.1.1", + "version": "4.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ui5-test-runner", - "version": "4.1.1", + "version": "4.2.0", "license": "MIT", "dependencies": { "commander": "^12.0.0", @@ -28,7 +28,7 @@ "standard": "^17.1.0", "start-server-and-test": "^2.0.3", "typescript": "^5.3.3", - "ui5-tooling-transpile": "^3.3.4" + "ui5-tooling-transpile": "^3.3.5" }, "engines": { "node": ">=18" @@ -15356,9 +15356,9 @@ } }, "node_modules/ui5-tooling-transpile": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/ui5-tooling-transpile/-/ui5-tooling-transpile-3.3.4.tgz", - "integrity": "sha512-42J6VbWx+69w/YGmJdPUl/JWdnfkwV8h/JUS+SFjVw2Zbdl3FxwMIJHqRSrhhcgs3nNMiwoDdrO4LQXNDBkkWA==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/ui5-tooling-transpile/-/ui5-tooling-transpile-3.3.5.tgz", + "integrity": "sha512-cY7WT2Lsq0nhv4I6FTEb9P9QVzylcfk+tZ187IjBZzCOnknDpb8xP7GwL+UwRWFURawVKUGRa9bE+la09gHOYg==", "dev": true, "dependencies": { "@babel/core": "^7.23.6", @@ -27154,9 +27154,9 @@ "dev": true }, "ui5-tooling-transpile": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/ui5-tooling-transpile/-/ui5-tooling-transpile-3.3.4.tgz", - "integrity": "sha512-42J6VbWx+69w/YGmJdPUl/JWdnfkwV8h/JUS+SFjVw2Zbdl3FxwMIJHqRSrhhcgs3nNMiwoDdrO4LQXNDBkkWA==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/ui5-tooling-transpile/-/ui5-tooling-transpile-3.3.5.tgz", + "integrity": "sha512-cY7WT2Lsq0nhv4I6FTEb9P9QVzylcfk+tZ187IjBZzCOnknDpb8xP7GwL+UwRWFURawVKUGRa9bE+la09gHOYg==", "dev": true, "requires": { "@babel/core": "^7.23.6", diff --git a/package.json b/package.json index 759d6e49..83ca17b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ui5-test-runner", - "version": "4.1.1", + "version": "4.2.0", "description": "Standalone test runner for UI5", "main": "index.js", "bin": { @@ -78,7 +78,7 @@ "standard": "^17.1.0", "start-server-and-test": "^2.0.3", "typescript": "^5.3.3", - "ui5-tooling-transpile": "^3.3.4" + "ui5-tooling-transpile": "^3.3.5" }, "optionalDependencies": { "fsevents": "^2.3.3" diff --git a/src/job-mode.js b/src/job-mode.js index eee4ccbc..ba89b6a4 100644 --- a/src/job-mode.js +++ b/src/job-mode.js @@ -29,7 +29,8 @@ function buildAndCheckMode (job) { 'pageTimeout', 'browserCloseTimeout', 'failFast', - 'keepAlive' + 'keepAlive', + 'alternateNpmPath' ]) return 'capabilities' } diff --git a/src/job.js b/src/job.js index ad034900..a6fde4ed 100644 --- a/src/job.js +++ b/src/job.js @@ -102,6 +102,7 @@ function getCommand (cwd) { .option('-p, --parallel ', '[๐Ÿ’ป๐Ÿ”—๐Ÿงช] Number of parallel tests executions', 2) .option('-b, --browser ', '[๐Ÿ’ป๐Ÿ”—๐Ÿงช] Browser instantiation command (relative to cwd or use $/ for provided ones)', '$/puppeteer.js') .option('--browser-args ', '[๐Ÿ’ป๐Ÿ”—๐Ÿงช] Browser instantiation command parameters (use -- instead)') + .option('--alternate-npm-path ', '[๐Ÿ’ป๐Ÿ”—] Alternate NPM path to look for packages (priority: local, alternate, global)') .option('--no-npm-install', '[๐Ÿ’ป๐Ÿ”—๐Ÿงช] Prevent any NPM install (execution may fail if a dependency is missing)') .option('-bt, --browser-close-timeout ', '[๐Ÿ’ป๐Ÿ”—๐Ÿงช] Maximum waiting time for browser close', timeout, 2000) .option('-br, --browser-retry ', '[๐Ÿ’ป๐Ÿ”—๐Ÿงช] Browser instantiation retries : if the command fails unexpectedly, it is re-executed (0 means no retry)', 1) @@ -224,6 +225,9 @@ function finalize (job) { if (job.cache) { updateToAbsolute('cache') } + if (job.alternateNpmPath) { + checkAccess({ path: job.alternateNpmPath, label: 'Alternate NPM path' }) + } job.mode = buildAndCheckMode(job) if (job.mode === 'legacy') { checkAccess({ path: job.webapp, label: 'webapp folder' }) diff --git a/src/npm.js b/src/npm.js index e9727c1e..347f59f8 100644 --- a/src/npm.js +++ b/src/npm.js @@ -41,6 +41,38 @@ function resolveDependencyPath (name) { } } +async function findDependencyPath (job, name) { + if (!localRoot) { + [localRoot, globalRoot] = await Promise.all([ + npm(job, 'root'), + npm(job, 'root', '--global') + ]) + } + const localPath = join(localRoot, name) + if (await folderExists(localPath)) { + return [localPath, false] + } + if (job.alternateNpmPath) { + const alternatePath = join(job.alternateNpmPath, name) + if (await folderExists(alternatePath)) { + return [alternatePath, false] + } + } + const globalPath = join(globalRoot, name) + let justInstalled = false + if (!await folderExists(globalPath)) { + if (!job.npmInstall) { + throw UTRError.NPM_DEPENDENCY_NOT_FOUND(name) + } + const previousStatus = job.status + job.status = `Installing ${name}...` + await npm(job, 'install', name, '-g') + justInstalled = true + job.status = previousStatus + } + return [globalPath, justInstalled] +} + module.exports = { resolveDependencyPath, @@ -52,29 +84,7 @@ module.exports = { } catch (e) { } if (!modulePath) { - if (!localRoot) { - [localRoot, globalRoot] = await Promise.all([ - npm(job, 'root'), - npm(job, 'root', '--global') - ]) - } - const localPath = join(localRoot, name) - if (await folderExists(localPath)) { - modulePath = localPath - } else { - const globalPath = join(globalRoot, name) - if (!await folderExists(globalPath)) { - if (!job.npmInstall) { - throw UTRError.NPM_DEPENDENCY_NOT_FOUND(name) - } - const previousStatus = job.status - job.status = `Installing ${name}...` - await npm(job, 'install', name, '-g') - justInstalled = true - job.status = previousStatus - } - modulePath = globalPath - } + [modulePath, justInstalled] = await findDependencyPath(job, name) } const output = getOutput(job) const installedPackage = require(join(modulePath, 'package.json')) diff --git a/src/npm.spec.js b/src/npm.spec.js index ebf2b2dc..7baf14a4 100644 --- a/src/npm.spec.js +++ b/src/npm.spec.js @@ -29,7 +29,8 @@ describe('src/npm', () => { beforeEach(() => { job = fromObject(cwd, { reportDir, - coverage: false + coverage: false, + alternateNpmPath: join(cwd, 'alternate-npm') }) job.status = 'Testing' output = getOutput(job) @@ -45,6 +46,19 @@ describe('src/npm', () => { expect(output.packageNotLatest).not.toHaveBeenCalled() }) + it('detects already installed package in alternate folder', async () => { + mock({ + api: 'exec', + scriptPath: 'npm', + args: ['view', 'impossible', 'version'], + exec: async childProcess => childProcess.stdout.write('1.0.0\n'), + persist: true + }) + const path = await resolvePackage(job, 'impossible') + expect(output.resolvedPackage).toHaveBeenCalledTimes(1) + expect(output.packageNotLatest).not.toHaveBeenCalled() + }) + it('detects already installed global package (but warn as not the latest)', async () => { mock({ api: 'exec', diff --git a/test/project/alternate-npm/impossible/package.json b/test/project/alternate-npm/impossible/package.json new file mode 100644 index 00000000..1587a669 --- /dev/null +++ b/test/project/alternate-npm/impossible/package.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +}