ui5-test-runner 6.0.3 → 6.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Npm.js CHANGED
@@ -19,7 +19,7 @@ const npm = async (...arguments_) => {
19
19
  detached: true
20
20
  });
21
21
  };
22
- const validateRoot = async (proc, label) => {
22
+ const validateRoot = async (proc, label, requiresExistence) => {
23
23
  if (proc.code !== 0) {
24
24
  logger.fatal({ source: 'npm', message: `npm root ${label} failed with code ${proc.code}` });
25
25
  }
@@ -30,8 +30,11 @@ const validateRoot = async (proc, label) => {
30
30
  try {
31
31
  await FileSystem.access(value, FileSystem.constants.R_OK);
32
32
  }
33
- catch {
34
- logger.fatal({ source: 'npm', message: `npm root ${label} directory is not accessible: "${value}"` });
33
+ catch (error) {
34
+ const code = error.code;
35
+ if (requiresExistence || code !== 'ENOENT') {
36
+ logger.fatal({ source: 'npm', message: `npm root ${label} directory is not accessible: "${value}"` });
37
+ }
35
38
  }
36
39
  return value;
37
40
  };
@@ -39,8 +42,8 @@ const getRoots = memoize(async () => {
39
42
  const localRootProcess = await npm('root');
40
43
  const globalRootProcess = await npm('root', '--global');
41
44
  await Promise.all([localRootProcess.closed, globalRootProcess.closed]);
42
- const local = await validateRoot(localRootProcess, '(local)');
43
- const global = await validateRoot(globalRootProcess, '(global)');
45
+ const local = await validateRoot(localRootProcess, '(local)', false);
46
+ const global = await validateRoot(globalRootProcess, '(global)', true);
44
47
  logger.debug({ source: 'npm', message: 'roots', data: { local, global } });
45
48
  return { local, global };
46
49
  });
@@ -63,6 +66,17 @@ export class Npm {
63
66
  static dynamicImport(specifier) {
64
67
  return import(specifier);
65
68
  }
69
+ static resolveSpecifier(specifier) {
70
+ return import.meta.resolve(specifier);
71
+ }
72
+ static tryResolveSpecifier(specifier) {
73
+ try {
74
+ return this.resolveSpecifier(specifier);
75
+ }
76
+ catch {
77
+ return 'unknown location';
78
+ }
79
+ }
66
80
  static tryResolvePackageDir(moduleName, nodeModulesPath) {
67
81
  try {
68
82
  const packageJsonPath = Module.findPackageJSON(moduleName, `${nodeModulesPath}/`);
@@ -73,10 +87,13 @@ export class Npm {
73
87
  }
74
88
  }
75
89
  static async tryImportFromPath(configuration, moduleName, nodeModulesPath) {
90
+ if (this.tryResolvePackageDir(this.packageNameOf(moduleName), nodeModulesPath) === undefined) {
91
+ return undefined;
92
+ }
76
93
  try {
77
94
  const require = Module.createRequire(`${Url.pathToFileURL(configuration.cwd).href}/`);
78
95
  const resolved = require.resolve(moduleName, { paths: [nodeModulesPath] });
79
- return await this.dynamicImport(Url.pathToFileURL(resolved).href);
96
+ return { module: await this.dynamicImport(Url.pathToFileURL(resolved).href), resolved };
80
97
  }
81
98
  catch {
82
99
  return undefined;
@@ -86,19 +103,24 @@ export class Npm {
86
103
  for (const { path, label, checkVersion } of roots) {
87
104
  const result = await this.tryImportFromPath(configuration, moduleName, path);
88
105
  if (result !== undefined) {
89
- logger.debug({ source: 'npm', message: `Module ${moduleName} ${label}` });
106
+ logger.debug({ source: 'npm', message: `Module ${moduleName} ${label} (${result.resolved})` });
90
107
  if (checkVersion)
91
108
  void this.checkIfLatestVersion(configuration, moduleName);
92
- return result;
109
+ return result.module;
93
110
  }
94
111
  }
95
112
  return undefined;
96
113
  }
97
- static async import(configuration, moduleName) {
98
- logger.debug({ source: 'npm', message: `Importing module: ${moduleName}` });
114
+ static async tryImportLocally(configuration, moduleName) {
115
+ if (configuration.debugNpmNoLocal) {
116
+ return undefined;
117
+ }
99
118
  try {
100
119
  const module = await this.dynamicImport(moduleName);
101
- logger.debug({ source: 'npm', message: `Module ${moduleName} found locally` });
120
+ logger.debug({
121
+ source: 'npm',
122
+ message: `Module ${moduleName} found locally (${this.tryResolveSpecifier(moduleName)})`
123
+ });
102
124
  void this.checkIfLatestVersion(configuration, moduleName);
103
125
  return module;
104
126
  }
@@ -110,7 +132,14 @@ export class Npm {
110
132
  if (moduleName === this.packageNameOf(moduleName)) {
111
133
  logger.warn({ source: 'npm', message: `Module ${moduleName} not found locally` });
112
134
  }
135
+ return undefined;
113
136
  }
137
+ }
138
+ static async import(configuration, moduleName) {
139
+ logger.debug({ source: 'npm', message: `Importing module: ${moduleName}` });
140
+ const local = await this.tryImportLocally(configuration, moduleName);
141
+ if (local !== undefined)
142
+ return local;
114
143
  const { local: localRoot, global: globalRoot } = await getRoots();
115
144
  const extraRoots = [
116
145
  ...(configuration.alternateNpmPath
@@ -121,7 +150,9 @@ export class Npm {
121
150
  : [])
122
151
  ];
123
152
  const found = await this.tryImportFromRoots(configuration, moduleName, [
124
- { path: localRoot, label: 'found in local node_modules', checkVersion: true },
153
+ ...(configuration.debugNpmNoLocal
154
+ ? []
155
+ : [{ path: localRoot, label: 'found in local node_modules', checkVersion: true }]),
125
156
  { path: globalRoot, label: 'found globally', checkVersion: true },
126
157
  ...extraRoots
127
158
  ]);
@@ -144,18 +175,20 @@ export class Npm {
144
175
  if (reimportPath !== undefined) {
145
176
  const result = await this.tryImportFromPath(configuration, moduleName, reimportPath);
146
177
  if (result === undefined) {
147
- const message = `Module ${moduleName} could not be loaded after install`;
148
- logger.fatal({ source: 'npm', message });
178
+ return logger.fatal({ source: 'npm', message: `Module ${moduleName} could not be loaded after install` });
149
179
  }
150
- return result;
180
+ logger.debug({ source: 'npm', message: `Module ${moduleName} loaded after install (${result.resolved})` });
181
+ return result.module;
151
182
  }
152
183
  return await this.dynamicImport(moduleName);
153
184
  }
154
185
  static async resolvePackageDir(configuration, moduleName) {
155
186
  const { local, global: globalRoot } = await getRoots();
156
- const fromLocal = this.tryResolvePackageDir(moduleName, local);
157
- if (fromLocal)
158
- return fromLocal;
187
+ if (!configuration.debugNpmNoLocal) {
188
+ const fromLocal = this.tryResolvePackageDir(moduleName, local);
189
+ if (fromLocal)
190
+ return fromLocal;
191
+ }
159
192
  const fromGlobal = this.tryResolvePackageDir(moduleName, globalRoot);
160
193
  if (fromGlobal)
161
194
  return fromGlobal;
@@ -171,6 +204,17 @@ export class Npm {
171
204
  }
172
205
  throw new Error(`Cannot locate package directory for ${moduleName}`);
173
206
  }
207
+ static async resolvePackageBinCommand(configuration, moduleName, command) {
208
+ const packageDirectory = await this.resolvePackageDir(configuration, moduleName);
209
+ const { bin } = JSON.parse(await FileSystem.readFile(Path.join(packageDirectory, 'package.json'), 'utf8'));
210
+ const binRelativePath = typeof bin === 'string' ? bin : bin?.[command];
211
+ return binRelativePath
212
+ ? Path.join(packageDirectory, binRelativePath)
213
+ : logger.fatal({
214
+ source: 'npm',
215
+ message: `Unable to locate command "${command}" in ${moduleName} package.json "bin"`
216
+ });
217
+ }
174
218
  static packageNameOf(moduleName) {
175
219
  if (moduleName.startsWith('@')) {
176
220
  const [scope, package_] = moduleName.split('/', 2);
@@ -35,8 +35,8 @@ export const factory = async (configuration, signal) => {
35
35
  max: 0
36
36
  }
37
37
  });
38
- await Process.spawn('npx', 'playwright install chromium'.split(' '), {
39
- shell: true,
38
+ const cliPath = await Npm.resolvePackageBinCommand(configuration, 'playwright', 'playwright');
39
+ await Process.spawn('node', [cliPath, 'install', 'chromium'], {
40
40
  signal
41
41
  }).closed;
42
42
  browser = await chromium.launch(launchOptions);
@@ -45,8 +45,8 @@ export const factory = async (configuration, signal) => {
45
45
  max: 0
46
46
  }
47
47
  });
48
- await Process.spawn('npx', `puppeteer browsers install ${target}`.split(' '), {
49
- shell: true,
48
+ const cliPath = await Npm.resolvePackageBinCommand(configuration, 'puppeteer', 'puppeteer');
49
+ await Process.spawn('node', [cliPath, 'browsers', 'install', target], {
50
50
  signal
51
51
  }).closed;
52
52
  browser = await launch(launchOptions);
@@ -91,6 +91,17 @@ const extractConfigEntries = (parsed, configDirectory) => {
91
91
  }
92
92
  return { forcedKeys, configFileKeys, configFileObject };
93
93
  };
94
+ const substitutePlaceholder = async (merged, searchToken, valueToReplace) => {
95
+ for (const option of options) {
96
+ if (!(option.type === 'fs-entry' &&
97
+ typeof merged[option.name] === 'string' &&
98
+ merged[option.name].includes(searchToken))) {
99
+ continue;
100
+ }
101
+ const substituted = Path.join(valueToReplace, merged[option.name].split(searchToken, 2)[1]);
102
+ Object.assign(merged, { [option.name]: await validateValue(option, { ...merged, [option.name]: substituted }) });
103
+ }
104
+ };
94
105
  const finalizeConfiguration = async (merged) => {
95
106
  const hasExplicitReportDirectory = Object.hasOwn(merged, 'reportDir');
96
107
  for (const option of options) {
@@ -108,15 +119,8 @@ const finalizeConfiguration = async (merged) => {
108
119
  reportDir: await validateValue(reportDirectoryOption, { ...merged, reportDir: batchReportDirectory })
109
120
  });
110
121
  }
111
- for (const option of options) {
112
- if (!(option.type === 'fs-entry' &&
113
- typeof merged[option.name] === 'string' &&
114
- merged[option.name].includes('{{reportDir}}'))) {
115
- continue;
116
- }
117
- const substituted = Path.join(merged.reportDir, merged[option.name].split('{{reportDir}}', 2)[1]);
118
- Object.assign(merged, { [option.name]: await validateValue(option, { ...merged, [option.name]: substituted }) });
119
- }
122
+ await substitutePlaceholder(merged, '{{reportDir}}', merged.reportDir);
123
+ await substitutePlaceholder(merged, '{{tmpDir}}', Host.tmpdir());
120
124
  for (const derive of derivations) {
121
125
  derive(merged);
122
126
  }
@@ -249,6 +249,11 @@ export const options = [
249
249
  type: 'boolean',
250
250
  description: 'use local docs/ directory instead of fetching from GitHub (development only)'
251
251
  },
252
+ {
253
+ name: 'debugNpmNoLocal',
254
+ type: 'boolean',
255
+ description: 'Simulates a locally-absent package so the runner falls back to global or prefix resolution (development/testing only)'
256
+ },
252
257
  {
253
258
  name: 'driver',
254
259
  short: 'd',
@@ -378,7 +383,8 @@ export const options = [
378
383
  {
379
384
  name: 'npmInstallPrefix',
380
385
  type: 'fs-entry',
381
- description: 'path used as --prefix when npmInstall is set to prefix'
386
+ typeModifiers: new Set(['overwrite']),
387
+ description: 'Directory used as the npm --prefix install target when --npm-install is set to prefix'
382
388
  },
383
389
  {
384
390
  name: 'outputInterval',