ui5-test-runner 5.13.1 → 6.0.0-beta.2

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.
Files changed (186) hide show
  1. package/README.md +3 -2
  2. package/dist/Npm.js +80 -0
  3. package/dist/browsers/IBrowser.js +1 -0
  4. package/dist/browsers/factory.js +9 -0
  5. package/dist/browsers/puppeteer.js +158 -0
  6. package/dist/cli.js +20 -0
  7. package/dist/configuration/CommandLine.js +112 -0
  8. package/dist/configuration/Configuration.js +1 -0
  9. package/dist/configuration/ConfigurationValidator.js +79 -0
  10. package/dist/configuration/Option.js +1 -0
  11. package/dist/configuration/OptionValidationError.js +15 -0
  12. package/dist/configuration/indexedOptions.js +13 -0
  13. package/dist/configuration/options.js +191 -0
  14. package/dist/configuration/validators/OptionValidator.js +1 -0
  15. package/dist/configuration/validators/boolean.js +15 -0
  16. package/dist/configuration/validators/browser.js +11 -0
  17. package/dist/configuration/validators/fsEntry.js +70 -0
  18. package/dist/configuration/validators/index.js +20 -0
  19. package/dist/configuration/validators/integer.js +10 -0
  20. package/dist/configuration/validators/percent.js +17 -0
  21. package/dist/configuration/validators/regexp.js +20 -0
  22. package/dist/configuration/validators/string.js +7 -0
  23. package/dist/configuration/validators/timeout.js +24 -0
  24. package/dist/configuration/validators/url.js +8 -0
  25. package/dist/modes/ModeFunction.js +1 -0
  26. package/dist/modes/Modes.js +9 -0
  27. package/dist/modes/execute.js +27 -0
  28. package/dist/modes/help.js +3 -0
  29. package/dist/modes/log/ILogStorage.js +1 -0
  30. package/dist/modes/log/LogMetrics.js +9 -0
  31. package/dist/modes/log/LogReader.js +37 -0
  32. package/dist/modes/log/LogStorage.js +68 -0
  33. package/dist/modes/log/REserve.js +101 -0
  34. package/dist/modes/log/index.js +58 -0
  35. package/dist/modes/test/REserve.js +31 -0
  36. package/dist/modes/test/agent.js +8 -0
  37. package/dist/modes/test/browser.js +37 -0
  38. package/dist/modes/test/index.js +66 -0
  39. package/dist/modes/test/pageTask.js +145 -0
  40. package/dist/modes/test/report.js +3 -0
  41. package/dist/modes/test/server.js +109 -0
  42. package/dist/modes/version.js +11 -0
  43. package/dist/platform/Exit.js +139 -0
  44. package/dist/platform/FileSystem.js +13 -0
  45. package/dist/platform/Host.js +10 -0
  46. package/dist/platform/Http.js +38 -0
  47. package/dist/platform/Path.js +5 -0
  48. package/dist/platform/Process.js +133 -0
  49. package/dist/platform/Terminal.js +47 -0
  50. package/dist/platform/Thread.js +43 -0
  51. package/dist/platform/ZLib.js +7 -0
  52. package/dist/platform/assert.js +17 -0
  53. package/dist/platform/constants.js +5 -0
  54. package/dist/platform/environment.js +28 -0
  55. package/dist/platform/index.js +13 -0
  56. package/dist/platform/logger/ILogger.js +1 -0
  57. package/dist/platform/logger/allCompressed.js +54 -0
  58. package/dist/platform/logger/compress.js +277 -0
  59. package/dist/platform/logger/output/BaseLoggerOutput.js +158 -0
  60. package/dist/platform/logger/output/InteractiveLoggerOutput.js +102 -0
  61. package/dist/platform/logger/output/StaticLoggerOutput.js +32 -0
  62. package/dist/platform/logger/output/factory.js +10 -0
  63. package/dist/platform/logger/output.js +58 -0
  64. package/dist/platform/logger/proxy.js +6 -0
  65. package/dist/platform/logger/toInternalLogAttributes.js +22 -0
  66. package/dist/platform/logger/types.js +7 -0
  67. package/dist/platform/logger.js +138 -0
  68. package/dist/platform/mock.js +104 -0
  69. package/dist/platform/version.js +8 -0
  70. package/dist/platform/workerBootstrap.js +21 -0
  71. package/dist/reports/html.js +46 -0
  72. package/dist/types/AgentState.js +1 -0
  73. package/dist/types/CommonTestReportFormat.js +50 -0
  74. package/dist/types/IError.js +1 -0
  75. package/dist/types/IUserInterfaceController.js +1 -0
  76. package/dist/types/typeUtilities.js +1 -0
  77. package/dist/ui/agent.js +3 -0
  78. package/dist/ui/html-report.js +2 -0
  79. package/dist/ui/lib.js +1 -0
  80. package/dist/ui/log-viewer.js +2 -0
  81. package/dist/utils/node/Folder.js +28 -0
  82. package/dist/utils/node/FramedStreamReader.js +86 -0
  83. package/dist/utils/node/FramedStreamWriter.js +27 -0
  84. package/dist/utils/shared/ProgressBar.js +43 -0
  85. package/dist/utils/shared/TestReportBuilder.js +48 -0
  86. package/dist/utils/shared/memoize.js +19 -0
  87. package/dist/utils/shared/object.js +8 -0
  88. package/dist/utils/shared/parallelize.js +59 -0
  89. package/dist/utils/shared/string.js +23 -0
  90. package/dist/utils/shared/toIError.js +17 -0
  91. package/package.json +73 -50
  92. package/.releaserc +0 -5
  93. package/index.js +0 -175
  94. package/jest.config.json +0 -31
  95. package/src/add-test-pages.js +0 -67
  96. package/src/batch.js +0 -214
  97. package/src/browsers.js +0 -319
  98. package/src/capabilities/index.js +0 -204
  99. package/src/capabilities/tests/basic/iframe.html +0 -8
  100. package/src/capabilities/tests/basic/index.html +0 -12
  101. package/src/capabilities/tests/basic/index.js +0 -20
  102. package/src/capabilities/tests/basic/ui5.html +0 -24
  103. package/src/capabilities/tests/dynamic-include/index.js +0 -21
  104. package/src/capabilities/tests/dynamic-include/mix.html +0 -11
  105. package/src/capabilities/tests/dynamic-include/one.html +0 -11
  106. package/src/capabilities/tests/dynamic-include/post.js +0 -3
  107. package/src/capabilities/tests/dynamic-include/test.js +0 -1
  108. package/src/capabilities/tests/dynamic-include/two.html +0 -11
  109. package/src/capabilities/tests/index.js +0 -16
  110. package/src/capabilities/tests/local-storage/index.html +0 -16
  111. package/src/capabilities/tests/local-storage/index.js +0 -21
  112. package/src/capabilities/tests/screenshot/index.html +0 -23
  113. package/src/capabilities/tests/screenshot/index.js +0 -24
  114. package/src/capabilities/tests/scripts/coverage.html +0 -32
  115. package/src/capabilities/tests/scripts/iframe.html +0 -18
  116. package/src/capabilities/tests/scripts/index.js +0 -59
  117. package/src/capabilities/tests/scripts/qunit.html +0 -22
  118. package/src/capabilities/tests/scripts/testsuite.html +0 -10
  119. package/src/capabilities/tests/scripts/testsuite.js +0 -8
  120. package/src/capabilities/tests/timeout/index.html +0 -21
  121. package/src/capabilities/tests/timeout/index.js +0 -19
  122. package/src/capabilities/tests/traces/index.html +0 -18
  123. package/src/capabilities/tests/traces/index.js +0 -81
  124. package/src/capabilities/tests/ui5/focus.html +0 -89
  125. package/src/capabilities/tests/ui5/index.js +0 -39
  126. package/src/capabilities/tests/ui5/language.html +0 -50
  127. package/src/capabilities/tests/ui5/timezone.html +0 -27
  128. package/src/clean.js +0 -22
  129. package/src/cors.js +0 -21
  130. package/src/coverage.js +0 -384
  131. package/src/csv-reader.js +0 -36
  132. package/src/csv-writer.js +0 -55
  133. package/src/defaults/.nycrc.json +0 -4
  134. package/src/defaults/browser.js +0 -217
  135. package/src/defaults/happy-dom.js +0 -123
  136. package/src/defaults/jsdom/compatibility.js +0 -163
  137. package/src/defaults/jsdom/debug.js +0 -23
  138. package/src/defaults/jsdom/resource-loader.js +0 -44
  139. package/src/defaults/jsdom/sap.ui.test.matchers.visible.js +0 -39
  140. package/src/defaults/jsdom.js +0 -95
  141. package/src/defaults/json-report.js +0 -36
  142. package/src/defaults/junit-xml-report.js +0 -90
  143. package/src/defaults/playwright.js +0 -142
  144. package/src/defaults/puppeteer.js +0 -124
  145. package/src/defaults/report/common.js +0 -38
  146. package/src/defaults/report/decompress.js +0 -19
  147. package/src/defaults/report/default.html +0 -99
  148. package/src/defaults/report/main.js +0 -69
  149. package/src/defaults/report/progress.js +0 -60
  150. package/src/defaults/report/styles.css +0 -66
  151. package/src/defaults/report.js +0 -91
  152. package/src/defaults/scan-ui5.js +0 -26
  153. package/src/defaults/selenium-webdriver/chrome.js +0 -39
  154. package/src/defaults/selenium-webdriver/edge.js +0 -24
  155. package/src/defaults/selenium-webdriver/firefox.js +0 -30
  156. package/src/defaults/selenium-webdriver.js +0 -129
  157. package/src/defaults/text-report.js +0 -108
  158. package/src/defaults/webdriverio.js +0 -80
  159. package/src/end.js +0 -62
  160. package/src/endpoints.js +0 -219
  161. package/src/error.js +0 -54
  162. package/src/get-job-progress.js +0 -78
  163. package/src/handle.js +0 -43
  164. package/src/if.js +0 -10
  165. package/src/inject/jest2qunit.js +0 -289
  166. package/src/inject/opa-iframe-coverage.js +0 -22
  167. package/src/inject/post.js +0 -141
  168. package/src/inject/qunit-hooks.js +0 -107
  169. package/src/inject/qunit-redirect.js +0 -65
  170. package/src/inject/ui5-coverage.js +0 -33
  171. package/src/job-mode.js +0 -65
  172. package/src/job.js +0 -493
  173. package/src/npm.js +0 -136
  174. package/src/options.js +0 -95
  175. package/src/output.js +0 -739
  176. package/src/parallelize.js +0 -63
  177. package/src/qunit-hooks.js +0 -219
  178. package/src/report.js +0 -89
  179. package/src/reserve.js +0 -25
  180. package/src/start.js +0 -133
  181. package/src/symbols.js +0 -8
  182. package/src/tests.js +0 -183
  183. package/src/timeout.js +0 -53
  184. package/src/tools.js +0 -179
  185. package/src/ui5.js +0 -199
  186. package/src/unhandled.js +0 -32
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # UI5 Test runner
1
+ # UI5 Test runner 6️⃣ β
2
2
 
3
3
  [![Node.js CI](https://github.com/ArnaudBuchholz/ui5-test-runner/actions/workflows/node.js.yml/badge.svg)](https://github.com/ArnaudBuchholz/ui5-test-runner/actions/workflows/node.js.yml)
4
4
  [![Package Quality](https://npm.packagequality.com/shield/ui5-test-runner.svg)](https://packagequality.com/#?package=ui5-test-runner)
@@ -19,7 +19,7 @@ A self-sufficient test runner for UI5 applications enabling parallel execution o
19
19
 
20
20
  ## 💿 How to install
21
21
 
22
- * Works with [Node.js](https://nodejs.org/en/download/) >= 18
22
+ * Works with [Node.js](https://nodejs.org/en/download/) >= 24
23
23
  * Local installation
24
24
  * `npm install --save-dev ui5-test-runner`
25
25
  * Trigger either with `npx ui5-test-runner` or through an npm script invoking `ui5-test-runner`
@@ -36,6 +36,7 @@ A self-sufficient test runner for UI5 applications enabling parallel execution o
36
36
 
37
37
  | Version | Reason |
38
38
  |-|-|
39
+ | **6**.0.0 | • Drop support of Node.js < 24 |
39
40
  | **5**.0.0 | • Some coverage reports now includes **all** files, leading to a potential decrease of coverage |
40
41
  | **4**.0.0 | • Drop support of Node.js 16 |
41
42
  | **3**.0.0 | • Drop support of Node.js 14 |
package/dist/Npm.js ADDED
@@ -0,0 +1,80 @@
1
+ import { logger, FileSystem, Http, Path, Process } from './platform/index.js';
2
+ import { memoize } from './utils/shared/memoize.js';
3
+ const getNpmCliPath = memoize(async () => {
4
+ const npmChildProcess = Process.spawn('npm', [], {
5
+ shell: true
6
+ });
7
+ await npmChildProcess.closed;
8
+ const error = new Error('Unable to initialize NPM');
9
+ const match = /^npm@([^ ]+) (.*)$/gm.exec(npmChildProcess.stdout);
10
+ if (!match) {
11
+ logger.fatal({ source: 'npm', message: 'Unable to match NPM output', error });
12
+ throw error;
13
+ }
14
+ const [, semver, path] = match;
15
+ if (!semver || !path) {
16
+ logger.fatal({ source: 'npm', message: 'Failed to parse NPM output', error, data: { semver, path } });
17
+ throw error;
18
+ }
19
+ logger.debug({ source: 'npm', message: `npm@${semver} ${path}` });
20
+ return Path.join(path, 'bin/npm-cli.js');
21
+ });
22
+ const npm = async (...arguments_) => {
23
+ const npmCliPath = await getNpmCliPath();
24
+ return Process.spawn('node', [npmCliPath, ...arguments_], {
25
+ detached: true
26
+ });
27
+ };
28
+ const getRoots = memoize(async () => {
29
+ const localRootProcess = await npm('root');
30
+ const globalRootProcess = await npm('root', '--global');
31
+ await Promise.all([localRootProcess.closed, globalRootProcess.closed]);
32
+ const local = localRootProcess.stdout.trim();
33
+ const global = globalRootProcess.stdout.trim();
34
+ logger.debug({ source: 'npm', message: 'Roots', data: { local, global } });
35
+ return {
36
+ local: localRootProcess.stdout.trim(),
37
+ global: globalRootProcess.stdout.trim()
38
+ };
39
+ });
40
+ export const Npm = {
41
+ async getLatestVersion(moduleName) {
42
+ try {
43
+ const response = await Http.get(`https://registry.npmjs.org/${moduleName}/latest`);
44
+ const { version } = JSON.parse(response);
45
+ return version;
46
+ }
47
+ catch (error) {
48
+ throw new Error(`Unable to fetch latest version of ${moduleName} from NPM registry`, {
49
+ cause: error
50
+ });
51
+ }
52
+ },
53
+ async checkIfLatestVersion(moduleName, isLocal) {
54
+ try {
55
+ const { local, global } = await getRoots();
56
+ const { version: installedVersion } = JSON.parse(await FileSystem.readFile(Path.join(isLocal ? local : global, moduleName, 'package.json'), 'utf8'));
57
+ logger.info({ source: 'npm', message: `Installed version of ${moduleName} is ${installedVersion}` });
58
+ const latestVersion = await Npm.getLatestVersion(moduleName);
59
+ if (latestVersion !== installedVersion) {
60
+ logger.warn({ source: 'npm', message: `[PKGVRS] Latest version of ${moduleName} is ${latestVersion}` });
61
+ }
62
+ }
63
+ catch (error) {
64
+ logger.error({ source: 'npm', message: 'Failed in checkIfLatestVersion', error });
65
+ }
66
+ },
67
+ async import(moduleName) {
68
+ logger.debug({ source: 'npm', message: `Npm.import(${moduleName})` });
69
+ try {
70
+ const module = (await import(moduleName));
71
+ logger.debug({ source: 'npm', message: `Module ${moduleName} found locally` });
72
+ void this.checkIfLatestVersion(moduleName, true);
73
+ return module;
74
+ }
75
+ catch {
76
+ logger.warn({ source: 'npm', message: `Module ${moduleName} not found locally` });
77
+ }
78
+ throw new Error('Not implemented');
79
+ }
80
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { factory as puppeteerFactory } from './puppeteer.js';
2
+ const factories = {
3
+ puppeteer: puppeteerFactory
4
+ };
5
+ export const BrowserFactory = {
6
+ async build(browser) {
7
+ return await factories[browser]();
8
+ }
9
+ };
@@ -0,0 +1,158 @@
1
+ import { logger, Exit, Process } from '../platform/index.js';
2
+ import { Npm } from '../Npm.js';
3
+ import { agentLogPrefix } from '../types/AgentState.js';
4
+ export const factory = async () => {
5
+ const puppeteer = await Npm.import('puppeteer');
6
+ const { launch } = puppeteer;
7
+ let browser;
8
+ const abortController = new AbortController();
9
+ const { signal } = abortController;
10
+ const task = Exit.registerAsyncTask({
11
+ name: 'puppeteer',
12
+ async stop() {
13
+ abortController.abort();
14
+ await browser?.close();
15
+ }
16
+ });
17
+ let openedPages = 0;
18
+ const launchAndInstallIfNeeded = async (settings) => {
19
+ const launchOptions = {
20
+ headless: !settings.visible,
21
+ defaultViewport: null,
22
+ handleSIGINT: false,
23
+ signal,
24
+ args: ['--start-maximized']
25
+ };
26
+ try {
27
+ browser = await launch(launchOptions);
28
+ }
29
+ catch (error) {
30
+ if (error instanceof Error && error.message.startsWith('Could not find Chrome')) {
31
+ logger.info({
32
+ source: 'progress',
33
+ message: 'Installing chrome (puppeteer)',
34
+ pageId: undefined,
35
+ data: {
36
+ value: 1,
37
+ max: 0
38
+ }
39
+ });
40
+ await Process.spawn('npx', 'puppeteer browsers install chrome'.split(' '), {
41
+ shell: true,
42
+ signal
43
+ }).closed;
44
+ browser = await launch(launchOptions);
45
+ }
46
+ else {
47
+ throw error;
48
+ }
49
+ }
50
+ logger.debug({ source: 'puppeteer', message: 'setup completed' });
51
+ return {
52
+ screenshotFormat: '.png'
53
+ };
54
+ };
55
+ return {
56
+ async setup(settings) {
57
+ logger.debug({ source: 'puppeteer', message: 'setup', data: settings });
58
+ try {
59
+ return await launchAndInstallIfNeeded(settings);
60
+ }
61
+ catch (error) {
62
+ logger.error({ source: 'puppeteer', message: 'setup failed', error });
63
+ task[Symbol.dispose]();
64
+ throw error;
65
+ }
66
+ },
67
+ async newWindow(settings) {
68
+ logger.debug({ source: 'puppeteer', message: 'newWindow', data: settings });
69
+ let page;
70
+ if (++openedPages === 1) {
71
+ const pages = await browser?.pages(true);
72
+ page = pages?.[0];
73
+ }
74
+ else {
75
+ page = await browser?.newPage({
76
+ type: 'window'
77
+ });
78
+ }
79
+ for (const script of settings.scripts) {
80
+ await page?.evaluateOnNewDocument(script);
81
+ }
82
+ const { pageId } = settings;
83
+ page
84
+ ?.on('console', (message) => {
85
+ const LOG_TYPES = {
86
+ error: 'error',
87
+ warn: 'warn',
88
+ debug: 'debug'
89
+ };
90
+ const logType = LOG_TYPES[message.type()] ?? 'info';
91
+ let source;
92
+ let messageText = message.text();
93
+ if (messageText.startsWith(agentLogPrefix)) {
94
+ source = 'browser/agent';
95
+ messageText = messageText.slice(agentLogPrefix.length);
96
+ }
97
+ else {
98
+ source = 'browser/console';
99
+ }
100
+ logger[logType]({
101
+ source,
102
+ message: messageText,
103
+ pageId,
104
+ data: { type: message.type() }
105
+ });
106
+ })
107
+ ?.on('response', (response) => {
108
+ const request = response.request();
109
+ const statusType = Math.floor(response.status() / 100);
110
+ const LOG_TYPES = [null, null, null, null, 'warn', 'error'];
111
+ const logType = LOG_TYPES[statusType] ?? 'info';
112
+ logger[logType]({
113
+ source: 'browser/network',
114
+ message: request.url(),
115
+ pageId,
116
+ data: {
117
+ request: {
118
+ method: request.method(),
119
+ headers: request.headers()
120
+ },
121
+ response: {
122
+ status: response.status(),
123
+ headers: response.headers()
124
+ }
125
+ }
126
+ });
127
+ });
128
+ await page?.goto(settings.url);
129
+ logger.debug({ source: 'puppeteer', message: 'newWindow completed', data: settings });
130
+ return {
131
+ async eval(script) {
132
+ return await page?.evaluate(script);
133
+ },
134
+ screenshot() {
135
+ throw new Error('Not implemented');
136
+ },
137
+ async close() {
138
+ try {
139
+ await page?.close();
140
+ }
141
+ catch (error) {
142
+ logger.error({ source: 'puppeteer', message: 'page.close failed', error });
143
+ }
144
+ }
145
+ };
146
+ },
147
+ async shutdown() {
148
+ logger.debug({ source: 'puppeteer', message: 'shutdown' });
149
+ try {
150
+ await browser?.close();
151
+ }
152
+ catch (error) {
153
+ logger.error({ source: 'puppeteer', message: 'browser.close failed', error });
154
+ }
155
+ task[Symbol.dispose]();
156
+ }
157
+ };
158
+ };
package/dist/cli.js ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import { Exit, Host } from './platform/index.js';
3
+ import { version } from './platform/version.js';
4
+ import { CommandLine } from './configuration/CommandLine.js';
5
+ import { execute } from './modes/execute.js';
6
+ const main = async () => {
7
+ const cliVersion = await version();
8
+ const cliName = cliVersion.split('@')[0];
9
+ const indexOfCli = process.argv.findIndex((value) => /[\\/]cli(\.[tj]s)?$/.exec(value) || value === cliName);
10
+ const configuration = await CommandLine.buildConfigurationFrom(Host.cwd(), process.argv.slice(indexOfCli + 1));
11
+ await execute(configuration);
12
+ };
13
+ main()
14
+ .catch((error) => {
15
+ console.error(error);
16
+ Exit.code = -1;
17
+ })
18
+ .finally(async () => {
19
+ await Exit.shutdown();
20
+ });
@@ -0,0 +1,112 @@
1
+ import { indexedOptions } from './indexedOptions.js';
2
+ import { ConfigurationValidator } from './ConfigurationValidator.js';
3
+ import { OptionValidationError } from './OptionValidationError.js';
4
+ import { looksLikeAnUrl } from './validators/url.js';
5
+ const setOption = (configuration, option, value) => {
6
+ const name = option.name;
7
+ if (value === undefined) {
8
+ if (option.type === 'boolean') {
9
+ Object.assign(configuration, {
10
+ [name]: true
11
+ });
12
+ }
13
+ else if (option.multiple !== true) {
14
+ configuration.errors.push(new OptionValidationError(option, 'Missing value'));
15
+ }
16
+ }
17
+ else {
18
+ if (option.multiple) {
19
+ if (!(option.name in configuration)) {
20
+ Object.assign(configuration, {
21
+ [name]: []
22
+ });
23
+ }
24
+ configuration[name].push(value);
25
+ }
26
+ else {
27
+ Object.assign(configuration, {
28
+ [name]: value
29
+ });
30
+ }
31
+ }
32
+ };
33
+ const switchOption = (configuration, currentOption, name) => {
34
+ if (currentOption) {
35
+ setOption(configuration, currentOption);
36
+ }
37
+ const option = indexedOptions[name];
38
+ if (!option) {
39
+ throw OptionValidationError.createUnknown(name);
40
+ }
41
+ return option;
42
+ };
43
+ const positionalOption = {
44
+ name: 'positional',
45
+ description: 'Any argument not prefixed with an option',
46
+ type: 'string'
47
+ };
48
+ const handlePositional = (configuration, value) => {
49
+ if (looksLikeAnUrl(value)) {
50
+ setOption(configuration, indexedOptions.url, value);
51
+ return;
52
+ }
53
+ const shortcuts = ['capabilities', 'version', 'help'];
54
+ for (const shortcut of shortcuts) {
55
+ if (value === shortcut) {
56
+ setOption(configuration, indexedOptions[shortcut]);
57
+ return;
58
+ }
59
+ }
60
+ configuration.errors.push(new OptionValidationError(positionalOption, `Unable to process: ${value}`));
61
+ };
62
+ const traverseArguments = (configuration, argv) => {
63
+ let currentOption;
64
+ for (const argument of argv) {
65
+ if (argument.startsWith('--')) {
66
+ currentOption = switchOption(configuration, currentOption, argument.slice(2));
67
+ continue;
68
+ }
69
+ if (argument.startsWith('-')) {
70
+ currentOption = switchOption(configuration, currentOption, argument.slice(1));
71
+ continue;
72
+ }
73
+ if (currentOption === undefined) {
74
+ handlePositional(configuration, argument);
75
+ }
76
+ else {
77
+ setOption(configuration, currentOption, argument);
78
+ if (currentOption.multiple !== true) {
79
+ currentOption = undefined;
80
+ }
81
+ }
82
+ }
83
+ if (currentOption) {
84
+ setOption(configuration, currentOption);
85
+ }
86
+ };
87
+ export const CommandLine = {
88
+ async buildConfigurationFrom(cwd, argv) {
89
+ const configuration = { cwd, errors: [] };
90
+ traverseArguments(configuration, argv);
91
+ const { errors, ...configWithoutErrors } = configuration;
92
+ let validatedConfiguration;
93
+ try {
94
+ validatedConfiguration = await ConfigurationValidator.validate(configWithoutErrors);
95
+ }
96
+ catch (error) {
97
+ if (errors.length > 0 && error instanceof AggregateError) {
98
+ errors.push(...error.errors);
99
+ }
100
+ else {
101
+ errors.push(error);
102
+ }
103
+ }
104
+ if (errors.length === 1) {
105
+ throw errors[0];
106
+ }
107
+ else if (errors.length > 0) {
108
+ throw new AggregateError(errors, 'Multiple errors occurred');
109
+ }
110
+ return validatedConfiguration;
111
+ }
112
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
1
+ import { options, defaults } from './options.js';
2
+ import { indexedOptions } from './indexedOptions.js';
3
+ import { validators } from './validators/index.js';
4
+ import { OptionValidationError } from './OptionValidationError.js';
5
+ import { Modes } from '../modes/Modes.js';
6
+ const assertIfConfiguration = (value) => {
7
+ const errors = [];
8
+ for (const key of Object.keys(value)) {
9
+ const option = indexedOptions[key];
10
+ if (option) {
11
+ if (key === option.short) {
12
+ errors.push(new OptionValidationError(option, 'Do not use short name'));
13
+ }
14
+ else if (key !== option.name) {
15
+ errors.push(new OptionValidationError(option, 'Do not use kebab-case'));
16
+ }
17
+ }
18
+ else {
19
+ errors.push(OptionValidationError.createUnknown(key));
20
+ }
21
+ }
22
+ if (errors.length > 1) {
23
+ throw new AggregateError(errors, 'Unknown keys');
24
+ }
25
+ const [error] = errors;
26
+ if (error) {
27
+ throw error;
28
+ }
29
+ };
30
+ const validateValue = async (option, configuration) => {
31
+ const value = configuration[option.name];
32
+ if ('multiple' in option) {
33
+ const validatedValues = [];
34
+ if (Array.isArray(value)) {
35
+ for (const valueItem of value) {
36
+ validatedValues.push(await validators[option.type](option, valueItem, configuration));
37
+ }
38
+ }
39
+ else {
40
+ validatedValues.push(await validators[option.type](option, value, configuration));
41
+ }
42
+ return validatedValues;
43
+ }
44
+ return await validators[option.type](option, value, configuration);
45
+ };
46
+ export const ConfigurationValidator = {
47
+ merge(configuration) {
48
+ return Promise.resolve(configuration);
49
+ },
50
+ computeMode(configuration) {
51
+ if (configuration.help) {
52
+ return Modes.help;
53
+ }
54
+ if (configuration.version) {
55
+ return Modes.version;
56
+ }
57
+ if (configuration.log) {
58
+ return Modes.log;
59
+ }
60
+ if (configuration.url) {
61
+ return Modes.remote;
62
+ }
63
+ return Modes.legacy;
64
+ },
65
+ async validate(configuration) {
66
+ const withDefaults = Object.assign(Object.create(defaults), configuration);
67
+ assertIfConfiguration(withDefaults);
68
+ const merged = await this.merge(withDefaults);
69
+ merged.mode = this.computeMode(merged);
70
+ for (const option of options) {
71
+ if (Object.hasOwnProperty.call(merged, option.name) || (merged[option.name] && option.type === 'fs-entry')) {
72
+ Object.assign(merged, {
73
+ [option.name]: await validateValue(option, merged)
74
+ });
75
+ }
76
+ }
77
+ return merged;
78
+ }
79
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ export class OptionValidationError extends Error {
2
+ static createUnknown(name) {
3
+ return new OptionValidationError({ name, type: 'string', description: 'unknown' }, 'Unknown option');
4
+ }
5
+ _option;
6
+ constructor(option, message = 'Invalid value', cause) {
7
+ super(message);
8
+ this._option = option;
9
+ this.name = 'OptionValidationError';
10
+ this.cause = cause;
11
+ }
12
+ get option() {
13
+ return this._option;
14
+ }
15
+ }
@@ -0,0 +1,13 @@
1
+ import { options } from './options.js';
2
+ export const indexedOptions = {};
3
+ for (const option of options) {
4
+ const { name } = option;
5
+ indexedOptions[name] = option;
6
+ if ('short' in option) {
7
+ indexedOptions[option.short] = option;
8
+ }
9
+ const kebabCase = name.replaceAll(/[A-Z]/g, (letter) => `-${letter.toLocaleLowerCase()}`);
10
+ if (name !== kebabCase) {
11
+ indexedOptions[kebabCase] = option;
12
+ }
13
+ }