cabloy 5.1.140 → 5.1.142

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 (142) hide show
  1. package/.cabloy-version +1 -1
  2. package/.github/workflows/playwright-e2e.yml +2 -2
  3. package/CHANGELOG.md +32 -0
  4. package/cabloy-docs/frontend/form-guide.md +47 -0
  5. package/cabloy-docs/frontend/markdown-guide.md +50 -17
  6. package/cabloy-docs/frontend/route-alias-guide.md +20 -0
  7. package/cabloy-docs/frontend/scripts.md +9 -11
  8. package/cabloy-docs/frontend/zova-form-under-the-hood.md +25 -0
  9. package/cabloy-docs/fullstack/framework-performance.md +3 -3
  10. package/cabloy-docs/fullstack/parallel-worktree-environment.md +1 -1
  11. package/cabloy-docs/fullstack/quickstart.md +5 -4
  12. package/cabloy-docs/reference/repo-scripts.md +29 -42
  13. package/e2e/config/playwright.config.ts +34 -0
  14. package/e2e/scripts/e2e.ts +2 -27
  15. package/e2e/scripts/runE2e.ts +62 -0
  16. package/e2e/scripts/runE2eArgs.test.ts +75 -0
  17. package/e2e/scripts/runE2eArgs.ts +158 -0
  18. package/e2e/specs/{a-commerce/commerce.spec.ts → a-commerce.spec.ts} +364 -33
  19. package/e2e/specs/{cabloy-basic/account.spec.ts → account.spec.ts} +1 -2
  20. package/e2e/specs/{cabloy-basic/basic.spec.ts → cabloy-basic.spec.ts} +6 -8
  21. package/oxlint.config.ts +3 -0
  22. package/package.json +3 -9
  23. package/scripts/upgrade.ts +63 -68
  24. package/vona/packages-utils/lint/package.json +1 -1
  25. package/vona/packages-utils/lint/src/oxc/lint.ts +1 -1
  26. package/vona/packages-utils/lint/src/oxc/lintVue.ts +1 -1
  27. package/vona/pnpm-lock.yaml +100 -657
  28. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundRecoveryView.tsx +7 -2
  29. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +31 -15
  30. package/vona/src/suite/a-home/modules/home-user/package.json +1 -0
  31. package/vona/src/suite/a-home/modules/home-user/src/service/account.ts +15 -2
  32. package/vona/src/suite/a-home/modules/home-user/test/accountActivation.test.ts +34 -4
  33. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/package.json +2 -1
  34. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/.metadata/index.ts +26 -0
  35. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/bean/imageScene.markdown.ts +13 -0
  36. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  37. package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
  38. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +23 -0
  39. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/refundOperation.ts +7 -0
  40. package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
  41. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
  42. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
  43. package/vona/src/suite-vendor/a-pay/package.json +5 -5
  44. package/zova/packages-utils/zova-jsx/package.json +2 -2
  45. package/zova/packages-zova/zova/package.json +3 -3
  46. package/zova/packages-zova/zova-core/package.json +1 -1
  47. package/zova/packages-zova/zova-core/src/bean/resource/locale/type.ts +1 -0
  48. package/zova/pnpm-lock.yaml +63 -18
  49. package/zova/src/front/config/config/config.cabloyBasicWeb.ts +3 -1
  50. package/zova/src/front/config/config/config.cabloyCommerce.ts +4 -2
  51. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/schemas.ts +0 -11
  52. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/types.ts +23 -170
  53. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/schemas.ts +0 -11
  54. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/types.ts +23 -170
  55. package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +64 -63
  56. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/schemas.ts +0 -11
  57. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/types.ts +23 -170
  58. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/api/openapi/schemas.ts +0 -11
  59. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/api/openapi/types.ts +23 -170
  60. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +0 -11
  61. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +29 -171
  62. package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionRefund/controller.tsx +413 -134
  63. package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +76 -0
  64. package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +67 -0
  65. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +1 -0
  66. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +1 -1
  67. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +7 -7
  68. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +46 -9
  69. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +50 -30
  70. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +0 -11
  71. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +23 -170
  72. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +0 -11
  73. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +23 -170
  74. package/zova/src/suite/cabloy-basic/modules/basic-captcha/src/component/formFieldCaptcha/controller.tsx +6 -2
  75. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/schemas.ts +0 -11
  76. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +23 -170
  77. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +0 -11
  78. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +23 -170
  79. package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +9 -15
  80. package/zova/src/suite/cabloy-basic/modules/basic-markdown/package.json +10 -2
  81. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/index.ts +11 -2
  82. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/locales.ts +7 -0
  83. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/controller.tsx +723 -5
  84. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/render.tsx +461 -2
  85. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/en-us.ts +59 -0
  86. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/zh-cn.ts +59 -0
  87. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/index.ts +1 -0
  88. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/codeBlockLanguages.ts +42 -0
  89. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/richTextContentStyle.ts +20 -0
  90. package/zova/src/suite/cabloy-basic/modules/basic-markdown/test/lib/codeBlockLanguages.test.ts +25 -0
  91. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  92. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +1 -0
  93. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  94. package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
  95. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/schemas.ts +0 -11
  96. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/types.ts +23 -170
  97. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +26 -12
  98. package/zova/src/suite-vendor/a-pay/modules/pay-mock/cli/openapi.config.ts +1 -1
  99. package/zova/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
  100. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/schemas.ts +0 -11
  101. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/types.ts +23 -170
  102. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/payMockMockPayment.ts +26 -0
  103. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/apiSchema/payMockMockPayment.ts +8 -1
  104. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/model/payMockPayment.ts +13 -1
  105. package/zova/src/suite-vendor/a-pay/package.json +3 -3
  106. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
  107. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +2 -0
  108. package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts +7 -6
  109. package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/package.json +1 -1
  110. package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/src/service/ssrHandler.ts +1 -1
  111. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  112. package/zova/src/suite-vendor/a-zova/package.json +4 -4
  113. package/e2e/config/playwright.basic.config.ts +0 -3
  114. package/e2e/config/playwright.commerce.config.ts +0 -3
  115. package/e2e/config/playwright.shared.config.ts +0 -38
  116. package/e2e/scripts/testE2eClean.ts +0 -56
  117. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/package.json +0 -56
  118. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/.metadata/index.ts +0 -404
  119. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/.metadata/locales.ts +0 -18
  120. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/.metadata/this.ts +0 -2
  121. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/bean/bean.paypal.ts +0 -99
  122. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/bean/event.paypalCancelOrder.ts +0 -15
  123. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/bean/event.paypalCaptureOrder.ts +0 -22
  124. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/bean/meta.version.ts +0 -24
  125. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/config/config.ts +0 -14
  126. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/config/errors.ts +0 -3
  127. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/config/locale/en-us.ts +0 -3
  128. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/config/locale/zh-cn.ts +0 -3
  129. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/controller/paypal.ts +0 -39
  130. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/dto/paypalOrderRecordOptions.tsx +0 -31
  131. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/dto/paypalOrderRecordPayload.tsx +0 -18
  132. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/entity/paypalRecord.tsx +0 -30
  133. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/index.ts +0 -3
  134. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/model/paypalRecord.ts +0 -10
  135. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/service/paypal.ts +0 -83
  136. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/types/index.ts +0 -1
  137. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/types/paypal.ts +0 -18
  138. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/tsconfig.build.json +0 -11
  139. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/tsconfig.json +0 -7
  140. package/vona/src/suite-vendor/a-paypal/package.json +0 -13
  141. package/vona/src/suite-vendor/a-paypal/tsconfig.base.json +0 -4
  142. package/vona/src/suite-vendor/a-paypal/tsconfig.json +0 -10
@@ -0,0 +1,62 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { createServer } from 'node:net';
3
+ import { resolve } from 'node:path';
4
+
5
+ import {
6
+ E2E_CONFIG_FILE,
7
+ E2E_LOCAL_BASE_URL,
8
+ E2E_PORT,
9
+ E2E_ROOT_DIR,
10
+ E2E_SPECS_DIR,
11
+ } from './e2e.ts';
12
+ import { combineGreps, parseE2eArgs } from './runE2eArgs.ts';
13
+
14
+ function run(command: string, args: string[]): void {
15
+ // eslint-disable-next-line no-console
16
+ console.log(`\n===> ${[command, ...args].join(' ')}`);
17
+ execFileSync(command, args, {
18
+ cwd: E2E_ROOT_DIR,
19
+ stdio: 'inherit',
20
+ });
21
+ }
22
+
23
+ async function assertPortAvailable(): Promise<void> {
24
+ const server = createServer();
25
+ await new Promise<void>((resolvePromise, reject) => {
26
+ server.once('error', reject);
27
+ server.listen(E2E_PORT, '127.0.0.1', () => {
28
+ server.close(error => {
29
+ if (error) reject(error);
30
+ else resolvePromise();
31
+ });
32
+ });
33
+ });
34
+ }
35
+
36
+ const parsed = parseE2eArgs(process.argv.slice(2), E2E_SPECS_DIR);
37
+ if (parsed.mode === 'clean' && process.env.E2E_BASE_URL) {
38
+ throw new Error(
39
+ 'The clean E2E run manages the local target and cannot be used with E2E_BASE_URL. Use test:e2e:fast for an external target.',
40
+ );
41
+ }
42
+
43
+ if (parsed.mode === 'clean') {
44
+ await assertPortAvailable();
45
+ run('npm', ['run', 'db:reset']);
46
+ }
47
+
48
+ const playwrightCommand = resolve(
49
+ E2E_ROOT_DIR,
50
+ 'node_modules',
51
+ '.bin',
52
+ process.platform === 'win32' ? 'playwright.cmd' : 'playwright',
53
+ );
54
+ const specPaths = parsed.specNames.map(name => resolve(E2E_SPECS_DIR, `${name}.spec.ts`));
55
+ const playwrightArgs = combineGreps(parsed.playwrightArgs, parsed.tags);
56
+ run(playwrightCommand, ['test', '--config', E2E_CONFIG_FILE, ...specPaths, ...playwrightArgs]);
57
+
58
+ // Keep this value referenced in the runner's startup output and make the local target explicit.
59
+ if (!process.env.E2E_BASE_URL) {
60
+ // eslint-disable-next-line no-console
61
+ console.log(`E2E target: ${E2E_LOCAL_BASE_URL}`);
62
+ }
@@ -0,0 +1,75 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { resolve } from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ import { buildTagGrep, combineGreps, parseE2eArgs } from './runE2eArgs.ts';
8
+
9
+ function withSpecs(callback: (specsDir: string) => void): void {
10
+ const tempDir = mkdtempSync(resolve(tmpdir(), 'cabloy-e2e-args-'));
11
+ try {
12
+ const specsDir = resolve(tempDir, 'specs');
13
+ mkdirSync(specsDir);
14
+ for (const name of ['account', 'a-commerce', 'cabloy-basic']) {
15
+ writeFileSync(resolve(specsDir, `${name}.spec.ts`), '');
16
+ }
17
+ callback(specsDir);
18
+ } finally {
19
+ rmSync(tempDir, { recursive: true, force: true });
20
+ }
21
+ }
22
+
23
+ test('parses mode, multiple specs, and repeatable tags', () => {
24
+ withSpecs(specsDir => {
25
+ assert.deepEqual(
26
+ parseE2eArgs(
27
+ ['--clean', 'cabloy-basic', 'account', '--tag', '@web', '--tag=@smoke'],
28
+ specsDir,
29
+ ),
30
+ {
31
+ mode: 'clean',
32
+ specNames: ['cabloy-basic', 'account'],
33
+ playwrightArgs: [],
34
+ tags: ['@web', '@smoke'],
35
+ },
36
+ );
37
+ });
38
+ });
39
+
40
+ test('allows no spec names and forwards Playwright filters', () => {
41
+ withSpecs(specsDir => {
42
+ assert.deepEqual(parseE2eArgs(['--fast', '--grep', '@flow'], specsDir), {
43
+ mode: 'fast',
44
+ specNames: [],
45
+ playwrightArgs: ['--grep', '@flow'],
46
+ tags: [],
47
+ });
48
+ });
49
+ });
50
+
51
+ test('rejects invalid or unknown specs and managed config overrides', () => {
52
+ withSpecs(specsDir => {
53
+ assert.throws(() => parseE2eArgs(['--fast', '../account'], specsDir), /Invalid E2E spec name/);
54
+ assert.throws(() => parseE2eArgs(['--fast', 'missing'], specsDir), /Unknown E2E spec/);
55
+ assert.throws(
56
+ () => parseE2eArgs(['--fast', '--config', 'other.ts'], specsDir),
57
+ /manages --config/,
58
+ );
59
+ });
60
+ });
61
+
62
+ test('requires exactly one run mode', () => {
63
+ withSpecs(specsDir => {
64
+ assert.throws(() => parseE2eArgs(['account'], specsDir), /Missing E2E mode/);
65
+ assert.throws(() => parseE2eArgs(['--clean', '--fast'], specsDir), /exactly one/);
66
+ });
67
+ });
68
+
69
+ test('combines tags with an existing grep expression', () => {
70
+ assert.equal(buildTagGrep(['@web', '@smoke']), '(?=.*@web)(?=.*@smoke)');
71
+ assert.deepEqual(combineGreps(['--grep', '@flow'], ['@web', '@smoke']), [
72
+ '--grep',
73
+ '(?=.*(?:@flow))(?=.*@web)(?=.*@smoke)',
74
+ ]);
75
+ });
@@ -0,0 +1,158 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+
4
+ export type E2eRunMode = 'clean' | 'fast';
5
+
6
+ export type ParsedE2eArgs = {
7
+ mode: E2eRunMode;
8
+ specNames: string[];
9
+ playwrightArgs: string[];
10
+ tags: string[];
11
+ };
12
+
13
+ const SPEC_NAME_PATTERN = /^[a-z0-9][\w-]*$/i;
14
+ const VALUE_OPTIONS = new Set([
15
+ '--browser',
16
+ '--config',
17
+ '--grep',
18
+ '--grep-invert',
19
+ '--global-timeout',
20
+ '--ignore-snapshots',
21
+ '--last-failed',
22
+ '--max-failures',
23
+ '--output',
24
+ '--pass-with-no-tests',
25
+ '--project',
26
+ '--repeat-each',
27
+ '--reporter',
28
+ '--shard',
29
+ '--timeout',
30
+ '--trace',
31
+ '--workers',
32
+ ]);
33
+
34
+ function optionName(arg: string): string {
35
+ const equalsIndex = arg.indexOf('=');
36
+ return equalsIndex === -1 ? arg : arg.slice(0, equalsIndex);
37
+ }
38
+
39
+ function escapeRegExp(value: string): string {
40
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
41
+ }
42
+
43
+ export function buildTagGrep(tags: string[]): string | undefined {
44
+ if (tags.length === 0) return undefined;
45
+ return tags.map(tag => `(?=.*${escapeRegExp(tag)})`).join('');
46
+ }
47
+
48
+ export function parseE2eArgs(args: string[], specsDir: string): ParsedE2eArgs {
49
+ let mode: E2eRunMode | undefined;
50
+ const remainingArgs: string[] = [];
51
+
52
+ for (const arg of args) {
53
+ if (arg === '--clean' || arg === '--fast') {
54
+ if (mode && mode !== arg.slice(2)) {
55
+ throw new Error('Choose exactly one E2E mode: --clean or --fast.');
56
+ }
57
+ if (mode === arg.slice(2)) {
58
+ throw new Error(`E2E mode ${arg} was provided more than once.`);
59
+ }
60
+ mode = arg.slice(2) as E2eRunMode;
61
+ } else {
62
+ remainingArgs.push(arg);
63
+ }
64
+ }
65
+
66
+ if (!mode) {
67
+ throw new Error('Missing E2E mode. Use --clean or --fast.');
68
+ }
69
+
70
+ const specNames: string[] = [];
71
+ const playwrightArgs: string[] = [];
72
+ const tags: string[] = [];
73
+ let optionStarted = false;
74
+ let pendingValueFor: string | undefined;
75
+
76
+ for (let index = 0; index < remainingArgs.length; index++) {
77
+ const arg = remainingArgs[index];
78
+
79
+ if (pendingValueFor) {
80
+ if (!arg || arg.startsWith('--')) {
81
+ throw new Error(`Missing value for ${pendingValueFor}.`);
82
+ }
83
+ if (pendingValueFor === '--tag') {
84
+ tags.push(arg);
85
+ } else {
86
+ playwrightArgs.push(arg);
87
+ }
88
+ pendingValueFor = undefined;
89
+ continue;
90
+ }
91
+
92
+ if (arg === '--tag' || arg.startsWith('--tag=')) {
93
+ optionStarted = true;
94
+ const value = arg.slice('--tag'.length).replace(/^=/, '');
95
+ if (value) {
96
+ tags.push(value);
97
+ } else {
98
+ pendingValueFor = '--tag';
99
+ }
100
+ continue;
101
+ }
102
+
103
+ if (arg === '--config' || arg.startsWith('--config=')) {
104
+ throw new Error('The E2E runner manages --config; do not provide it.');
105
+ }
106
+
107
+ if (!optionStarted && !arg.startsWith('-')) {
108
+ if (!SPEC_NAME_PATTERN.test(arg)) {
109
+ throw new Error(`Invalid E2E spec name: ${arg}`);
110
+ }
111
+ const specPath = resolve(specsDir, `${arg}.spec.ts`);
112
+ if (!existsSync(specPath)) {
113
+ throw new Error(`Unknown E2E spec: ${arg} (expected ${specPath}).`);
114
+ }
115
+ specNames.push(arg);
116
+ continue;
117
+ }
118
+
119
+ if (arg.startsWith('-')) {
120
+ optionStarted = true;
121
+ const name = optionName(arg);
122
+ if (VALUE_OPTIONS.has(name) && !arg.includes('=')) {
123
+ pendingValueFor = name;
124
+ }
125
+ playwrightArgs.push(arg);
126
+ continue;
127
+ }
128
+
129
+ playwrightArgs.push(arg);
130
+ }
131
+
132
+ if (pendingValueFor) {
133
+ throw new Error(`Missing value for ${pendingValueFor}.`);
134
+ }
135
+
136
+ return { mode, specNames, playwrightArgs, tags };
137
+ }
138
+
139
+ export function combineGreps(args: string[], tags: string[]): string[] {
140
+ const tagGrep = buildTagGrep(tags);
141
+ if (!tagGrep) return args;
142
+
143
+ const result = [...args];
144
+ const grepIndex = result.findIndex(arg => arg === '--grep' || arg.startsWith('--grep='));
145
+ if (grepIndex === -1) {
146
+ result.push('--grep', tagGrep);
147
+ return result;
148
+ }
149
+
150
+ if (result[grepIndex].includes('=')) {
151
+ const [name, value] = result[grepIndex].split(/=(.*)/s, 2);
152
+ result[grepIndex] = `${name}=(?=.*(?:${value}))${tagGrep}`;
153
+ } else {
154
+ const value = result[grepIndex + 1];
155
+ result[grepIndex + 1] = `(?=.*(?:${value}))${tagGrep}`;
156
+ }
157
+ return result;
158
+ }