ui5-test-runner 6.0.4 → 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}/`);
@@ -79,7 +93,7 @@ export class Npm {
79
93
  try {
80
94
  const require = Module.createRequire(`${Url.pathToFileURL(configuration.cwd).href}/`);
81
95
  const resolved = require.resolve(moduleName, { paths: [nodeModulesPath] });
82
- return await this.dynamicImport(Url.pathToFileURL(resolved).href);
96
+ return { module: await this.dynamicImport(Url.pathToFileURL(resolved).href), resolved };
83
97
  }
84
98
  catch {
85
99
  return undefined;
@@ -89,19 +103,24 @@ export class Npm {
89
103
  for (const { path, label, checkVersion } of roots) {
90
104
  const result = await this.tryImportFromPath(configuration, moduleName, path);
91
105
  if (result !== undefined) {
92
- logger.debug({ source: 'npm', message: `Module ${moduleName} ${label}` });
106
+ logger.debug({ source: 'npm', message: `Module ${moduleName} ${label} (${result.resolved})` });
93
107
  if (checkVersion)
94
108
  void this.checkIfLatestVersion(configuration, moduleName);
95
- return result;
109
+ return result.module;
96
110
  }
97
111
  }
98
112
  return undefined;
99
113
  }
100
- static async import(configuration, moduleName) {
101
- logger.debug({ source: 'npm', message: `Importing module: ${moduleName}` });
114
+ static async tryImportLocally(configuration, moduleName) {
115
+ if (configuration.debugNpmNoLocal) {
116
+ return undefined;
117
+ }
102
118
  try {
103
119
  const module = await this.dynamicImport(moduleName);
104
- 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
+ });
105
124
  void this.checkIfLatestVersion(configuration, moduleName);
106
125
  return module;
107
126
  }
@@ -113,7 +132,14 @@ export class Npm {
113
132
  if (moduleName === this.packageNameOf(moduleName)) {
114
133
  logger.warn({ source: 'npm', message: `Module ${moduleName} not found locally` });
115
134
  }
135
+ return undefined;
116
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;
117
143
  const { local: localRoot, global: globalRoot } = await getRoots();
118
144
  const extraRoots = [
119
145
  ...(configuration.alternateNpmPath
@@ -124,7 +150,9 @@ export class Npm {
124
150
  : [])
125
151
  ];
126
152
  const found = await this.tryImportFromRoots(configuration, moduleName, [
127
- { 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 }]),
128
156
  { path: globalRoot, label: 'found globally', checkVersion: true },
129
157
  ...extraRoots
130
158
  ]);
@@ -147,18 +175,20 @@ export class Npm {
147
175
  if (reimportPath !== undefined) {
148
176
  const result = await this.tryImportFromPath(configuration, moduleName, reimportPath);
149
177
  if (result === undefined) {
150
- const message = `Module ${moduleName} could not be loaded after install`;
151
- logger.fatal({ source: 'npm', message });
178
+ return logger.fatal({ source: 'npm', message: `Module ${moduleName} could not be loaded after install` });
152
179
  }
153
- return result;
180
+ logger.debug({ source: 'npm', message: `Module ${moduleName} loaded after install (${result.resolved})` });
181
+ return result.module;
154
182
  }
155
183
  return await this.dynamicImport(moduleName);
156
184
  }
157
185
  static async resolvePackageDir(configuration, moduleName) {
158
186
  const { local, global: globalRoot } = await getRoots();
159
- const fromLocal = this.tryResolvePackageDir(moduleName, local);
160
- if (fromLocal)
161
- return fromLocal;
187
+ if (!configuration.debugNpmNoLocal) {
188
+ const fromLocal = this.tryResolvePackageDir(moduleName, local);
189
+ if (fromLocal)
190
+ return fromLocal;
191
+ }
162
192
  const fromGlobal = this.tryResolvePackageDir(moduleName, globalRoot);
163
193
  if (fromGlobal)
164
194
  return fromGlobal;
@@ -174,6 +204,17 @@ export class Npm {
174
204
  }
175
205
  throw new Error(`Cannot locate package directory for ${moduleName}`);
176
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
+ }
177
218
  static packageNameOf(moduleName) {
178
219
  if (moduleName.startsWith('@')) {
179
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',