@testspectra/cli 1.1.7 → 1.1.8-rc.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.
- package/bin/.testspectra-runner.hash +1 -1
- package/bin/spectra.js +0 -0
- package/bin/testspectra-runner.exe +0 -0
- package/demo-app/assets/index-BhlW-eXG.css +1 -0
- package/demo-app/assets/index-DOtRypCa.js +154 -0
- package/demo-app/index.html +2 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/commands/__tests__/run-e2e.test.js +270 -29
- package/dist/commands/__tests__/run.test.js +92 -16
- package/dist/commands/demo.js +17 -7
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +154 -149
- package/dist/commands/lint.d.ts +5 -0
- package/dist/commands/lint.js +55 -0
- package/dist/commands/refactor.d.ts +2 -1
- package/dist/commands/refactor.js +129 -35
- package/dist/commands/run.d.ts +2 -3
- package/dist/commands/run.js +93 -28
- package/dist/commands/skills.d.ts +9 -0
- package/dist/commands/skills.js +28 -0
- package/dist/commands/sync-types.js +1 -0
- package/dist/commands/test.d.ts +17 -0
- package/dist/commands/test.js +104 -0
- package/dist/commands/watch.js +9 -3
- package/dist/config/loader.js +1 -1
- package/dist/config/schema.d.ts +5 -114
- package/dist/config/schema.js +1 -17
- package/dist/generator/__tests__/tsconfig-isolation.test.js +6 -2
- package/dist/generator/__tests__/type-generator.test.js +1 -1
- package/dist/generator/tsconfig-generator.js +44 -16
- package/dist/generator/type-generator.d.ts +24 -1
- package/dist/generator/type-generator.js +104 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +76 -40
- package/dist/linter/__tests__/anonymous-export.test.js +184 -0
- package/dist/linter/__tests__/component-test-imports.test.d.ts +1 -0
- package/dist/linter/__tests__/component-test-imports.test.js +115 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
- package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
- package/dist/linter/__tests__/spec-schema.test.js +369 -0
- package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
- package/dist/linter/__tests__/step-parity.test.js +372 -0
- package/dist/linter/discovery.d.ts +30 -0
- package/dist/linter/discovery.js +249 -0
- package/dist/linter/extractor.d.ts +67 -0
- package/dist/linter/extractor.js +245 -0
- package/dist/linter/index.d.ts +7 -0
- package/dist/linter/index.js +7 -0
- package/dist/linter/parser.d.ts +24 -0
- package/dist/linter/parser.js +219 -0
- package/dist/linter/schemas/spec.schema.d.ts +83 -0
- package/dist/linter/schemas/spec.schema.js +150 -0
- package/dist/linter/schemas/suite.schema.d.ts +59 -0
- package/dist/linter/schemas/suite.schema.js +83 -0
- package/dist/linter/spec-linter.d.ts +23 -0
- package/dist/linter/spec-linter.js +630 -0
- package/dist/linter/types.d.ts +28 -0
- package/dist/linter/types.js +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.js +698 -0
- package/dist/refactor/refactor-engine.d.ts +25 -0
- package/dist/refactor/refactor-engine.js +374 -42
- package/dist/reporter/semantic-formatter.js +22 -2
- package/dist/reporter/types.d.ts +1 -1
- package/dist/runner/bridge.d.ts +1 -1
- package/dist/runner/bridge.js +34 -21
- package/dist/runner/reporter.d.ts +2 -0
- package/dist/runner/reporter.js +11 -3
- package/dist/utils/__tests__/api-server.test.d.ts +1 -0
- package/dist/utils/__tests__/api-server.test.js +62 -0
- package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
- package/dist/utils/__tests__/demo-server.test.js +35 -0
- package/dist/utils/api-server.d.ts +19 -0
- package/dist/utils/api-server.js +179 -0
- package/dist/utils/demo-server.d.ts +5 -0
- package/dist/utils/demo-server.js +8 -1
- package/package.json +29 -25
- package/templates/default/CLAUDE.md +16 -11
- package/templates/default/global-hooks/after/mobile.hook.ts +3 -3
- package/templates/default/global-hooks/after/web.hook.ts +3 -3
- package/templates/default/global-hooks/before/mobile.hook.ts +3 -3
- package/templates/default/global-hooks/before/web.hook.ts +3 -3
- package/templates/default/package.json +3 -3
- package/templates/default/page-objects/AuthPage/mobile.ts +5 -0
- package/templates/default/page-objects/AuthPage/web.ts +10 -0
- package/templates/default/page-objects/MatchersPage/mobile.ts +70 -46
- package/templates/default/page-objects/MatchersPage/web.ts +43 -37
- package/templates/default/page-objects/NavigationPage/mobile.ts +14 -14
- package/templates/default/page-objects/NavigationPage/web.ts +8 -8
- package/templates/default/page-objects/PlaygroundPage/mobile.ts +42 -36
- package/templates/default/page-objects/PlaygroundPage/web.ts +31 -31
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -11
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -12
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -27
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -16
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -12
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -24
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -0
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
- package/templates/default/specs/ApiInterception/suite.md +10 -9
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
- package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
- package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
- package/templates/default/specs/ApiSeeding/suite.md +9 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -0
- package/templates/default/specs/Authentication/suite.md +9 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +9 -9
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +17 -12
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -0
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -0
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +19 -12
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +11 -11
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +17 -12
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
- package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -0
- package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -0
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
- package/templates/default/specs/DeepLink/suite.md +9 -0
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +11 -11
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +17 -12
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +29 -29
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +19 -12
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
- package/templates/default/specs/ElementMatchers/suite.md +10 -9
- package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
- package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
- package/templates/default/specs/EnvironmentConfig/suite.md +9 -0
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
- package/templates/default/specs/PermissionRationale/suite.md +9 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +6 -6
- package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +18 -12
- package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
- package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +6 -6
- package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +18 -12
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
- package/templates/default/spectra.config.ts +52 -68
- package/templates/default/support/actions/fillCredentials/mobile.action.ts +4 -4
- package/templates/default/support/actions/fillCredentials/web.action.ts +4 -4
- package/templates/default/support/actions/seedSession/mobile.action.ts +9 -0
- package/templates/default/support/actions/seedSession/web.action.ts +13 -0
- package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +5 -5
- package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +5 -5
- package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +6 -6
- package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +6 -6
- package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db +0 -0
- package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db-shm +0 -0
- package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db-wal +0 -0
- package/templates/nx/.nx/workspace-data/d/daemon.log +1577 -168
- package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
- package/templates/nx/.nx/workspace-data/file-map.json +464 -0
- package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
- package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
- package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
- package/templates/nx/.nx/workspace-data/project-graph.json +14389 -0
- package/templates/nx/CLAUDE.md +10 -10
- package/templates/nx/package.json +3 -3
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -3
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -27
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -3
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -24
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -3
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -3
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +17 -17
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -3
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -3
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -3
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
- package/templates/nx/pnpm-lock.yaml +6663 -6663
- package/templates/nx/pnpm-workspace.yaml +9 -9
- package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +33 -33
- package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +91 -37
- package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +5 -5
- package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +8 -8
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +26 -26
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +31 -31
- package/templates/nx/shared/testing/project.json +19 -19
- package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +4 -4
- package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +4 -4
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +3 -3
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +5 -5
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +4 -4
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +6 -6
- package/templates/nx/spectra.config.ts +52 -68
- package/templates/react-component/.cursorrules +17 -0
- package/templates/react-component/.vscode/extensions.json +5 -0
- package/templates/react-component/CLAUDE.md +14 -0
- package/templates/react-component/README.md +69 -0
- package/templates/react-component/fixtures/component-mock.json +12 -0
- package/templates/react-component/global-hooks/after/web.hook.ts +3 -0
- package/templates/react-component/global-hooks/before/web.hook.ts +3 -0
- package/templates/react-component/package.json +27 -0
- package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -0
- package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -0
- package/templates/react-component/page-objects/InputComponent/web.ts +13 -0
- package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -0
- package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -0
- package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -0
- package/templates/react-component/specs/BadgeComponent/suite.md +12 -0
- package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -0
- package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +28 -0
- package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -0
- package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +22 -0
- package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -0
- package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -0
- package/templates/react-component/specs/ButtonComponent/suite.md +12 -0
- package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -0
- package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +28 -0
- package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -0
- package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -0
- package/templates/react-component/specs/InputComponent/suite.md +12 -0
- package/templates/react-component/spectra.config.ts +27 -0
- package/templates/react-component/src/components/Badge/Badge.tsx +60 -0
- package/templates/react-component/src/components/Button/Button.tsx +57 -0
- package/templates/react-component/src/components/Input/Input.tsx +41 -0
- package/templates/react-component/src/test-utils.tsx +23 -0
- package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -0
- package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -0
- package/templates/react-component/tsconfig.json +8 -0
- package/bin/testspectra-runner +0 -0
- package/demo-app/assets/index-DPfv5_EF.js +0 -116
- package/demo-app/assets/index-gIwu2EMV.css +0 -1
- package/dist/commands/__tests__/cloud-run.test.js +0 -93
- package/dist/commands/cloud-run.d.ts +0 -13
- package/dist/commands/cloud-run.js +0 -120
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -17
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -17
- /package/dist/{commands/__tests__/cloud-run.test.d.ts → linter/__tests__/anonymous-export.test.d.ts} +0 -0
package/dist/commands/demo.js
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import * as p from '@clack/prompts';
|
|
3
3
|
import { DemoServer } from '../utils/demo-server.js';
|
|
4
|
+
import { DemoApiServer } from '../utils/api-server.js';
|
|
4
5
|
export async function demoCommand(options) {
|
|
5
6
|
const port = options.port ? parseInt(options.port, 10) : 5173;
|
|
6
7
|
p.intro(chalk.bold.cyan('🌐 TestSpectra Demo Web App'));
|
|
7
8
|
const server = new DemoServer(port);
|
|
9
|
+
const apiServer = new DemoApiServer();
|
|
8
10
|
try {
|
|
9
11
|
const { url } = await server.start();
|
|
10
12
|
p.log.success(chalk.green(`Demo web app is live at: ${chalk.bold.underline(url)}`));
|
|
13
|
+
// The web app fetches its API sections from the separate self-hosted API server, so start it
|
|
14
|
+
// too — otherwise the API demo sections show only their offline fallback data.
|
|
15
|
+
try {
|
|
16
|
+
const { url: apiUrl } = await apiServer.start();
|
|
17
|
+
p.log.success(chalk.green(`Demo API server is live at: ${chalk.bold.underline(apiUrl)}`));
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
p.log.warn(chalk.yellow(`Could not start demo API server: ${err.message}`));
|
|
21
|
+
}
|
|
11
22
|
p.log.info(chalk.dim("You can now run tests against this server in another terminal (e.g. 'spectra run -t web')."));
|
|
12
|
-
p.log.info(chalk.dim('Press Ctrl+C to stop the
|
|
23
|
+
p.log.info(chalk.dim('Press Ctrl+C to stop the servers.'));
|
|
13
24
|
// Handle termination gracefully
|
|
14
|
-
|
|
25
|
+
const shutdown = async () => {
|
|
15
26
|
await server.stop();
|
|
27
|
+
await apiServer.stop();
|
|
16
28
|
process.exit(0);
|
|
17
|
-
}
|
|
18
|
-
process.on('
|
|
19
|
-
|
|
20
|
-
process.exit(0);
|
|
21
|
-
});
|
|
29
|
+
};
|
|
30
|
+
process.on('SIGINT', shutdown);
|
|
31
|
+
process.on('SIGTERM', shutdown);
|
|
22
32
|
await new Promise(() => { });
|
|
23
33
|
}
|
|
24
34
|
catch (err) {
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -215,10 +215,15 @@ export async function initCommand(options = {}) {
|
|
|
215
215
|
hint: 'Interactive workspace feature discovery & centralized config',
|
|
216
216
|
},
|
|
217
217
|
{
|
|
218
|
-
label: 'Standard Project',
|
|
218
|
+
label: 'Standard E2E Project',
|
|
219
219
|
value: 'default',
|
|
220
220
|
hint: 'Standalone single-suite E2E testing project',
|
|
221
221
|
},
|
|
222
|
+
{
|
|
223
|
+
label: 'React Component Testing',
|
|
224
|
+
value: 'react-component',
|
|
225
|
+
hint: 'Real-browser React component testing with Vite & Testing Library',
|
|
226
|
+
},
|
|
222
227
|
],
|
|
223
228
|
});
|
|
224
229
|
if (p.isCancel(templateChoice)) {
|
|
@@ -552,135 +557,135 @@ export async function initCommand(options = {}) {
|
|
|
552
557
|
// 7. Generate Ambient Types in ROOT only
|
|
553
558
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
554
559
|
ensureVsCodeExtensions(cwd);
|
|
555
|
-
const archDocContent = `# TestSpectra Enterprise Monorepo Architecture
|
|
556
|
-
|
|
557
|
-
This workspace uses TestSpectra's **"Centralized Configuration, Distributed Implementation"** model for automated cross-platform testing.
|
|
558
|
-
|
|
559
|
-
---
|
|
560
|
-
|
|
561
|
-
## 1. Directory & File Overview
|
|
562
|
-
|
|
563
|
-
### Centralized Root Elements
|
|
564
|
-
- \`./spectra.config.ts\`: Single source of truth for runtime configurations (Base URL, Appium devices, browser targets, timeouts).
|
|
565
|
-
- \`./.testspectra/\`: Local test project cache, runtime metadata, auto-generated master TSConfig (\`.testspectra/tsconfig.json\`), platform sub-configs (\`.testspectra/tsconfig/\`), and universal ambient types (\`.testspectra/types/\`). Global binaries and drivers are managed at \`~/.testspectra/drivers/\`.
|
|
566
|
-
- \`./nx.json\`: Nx target defaults with execution caching and inputs.
|
|
567
|
-
- \`./pnpm-workspace.yaml\`: Monorepo package glob definitions.
|
|
568
|
-
- \`./tsconfig.json\`: Solution-style TypeScript orchestrator referencing \`./.testspectra/tsconfig.json\`.
|
|
569
|
-
|
|
570
|
-
### Centralized Shared Testing Library (\`./${sharedTestingRelPath}\`)
|
|
571
|
-
Provides cross-feature reusable test entities with **100% zero-import global resolution**:
|
|
572
|
-
- \`page-objects/\`: Shared Page Object models (e.g. NavigationBar, AppHeader).
|
|
573
|
-
- \`steps/\`: Cross-feature business step flows (e.g. \`Step.loginAsAdmin()\`).
|
|
574
|
-
- \`actions/\`: Custom atomic actions (e.g. \`Spectra.dismissBanner()\`).
|
|
575
|
-
- \`fixtures/\`: Common test data and environment configurations (e.g. \`Fixture.appConfig\`).
|
|
576
|
-
|
|
577
|
-
### Distributed Feature E2E Modules (${featureE2eDirs.length} Features)
|
|
560
|
+
const archDocContent = `# TestSpectra Enterprise Monorepo Architecture
|
|
561
|
+
|
|
562
|
+
This workspace uses TestSpectra's **"Centralized Configuration, Distributed Implementation"** model for automated cross-platform testing.
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## 1. Directory & File Overview
|
|
567
|
+
|
|
568
|
+
### Centralized Root Elements
|
|
569
|
+
- \`./spectra.config.ts\`: Single source of truth for runtime configurations (Base URL, Appium devices, browser targets, timeouts).
|
|
570
|
+
- \`./.testspectra/\`: Local test project cache, runtime metadata, auto-generated master TSConfig (\`.testspectra/tsconfig.json\`), platform sub-configs (\`.testspectra/tsconfig/\`), and universal ambient types (\`.testspectra/types/\`). Global binaries and drivers are managed at \`~/.testspectra/drivers/\`.
|
|
571
|
+
- \`./nx.json\`: Nx target defaults with execution caching and inputs.
|
|
572
|
+
- \`./pnpm-workspace.yaml\`: Monorepo package glob definitions.
|
|
573
|
+
- \`./tsconfig.json\`: Solution-style TypeScript orchestrator referencing \`./.testspectra/tsconfig.json\`.
|
|
574
|
+
|
|
575
|
+
### Centralized Shared Testing Library (\`./${sharedTestingRelPath}\`)
|
|
576
|
+
Provides cross-feature reusable test entities with **100% zero-import global resolution**:
|
|
577
|
+
- \`page-objects/\`: Shared Page Object models (e.g. NavigationBar, AppHeader).
|
|
578
|
+
- \`steps/\`: Cross-feature business step flows (e.g. \`Step.loginAsAdmin()\`).
|
|
579
|
+
- \`actions/\`: Custom atomic actions (e.g. \`Spectra.dismissBanner()\`).
|
|
580
|
+
- \`fixtures/\`: Common test data and environment configurations (e.g. \`Fixture.appConfig\`).
|
|
581
|
+
|
|
582
|
+
### Distributed Feature E2E Modules (${featureE2eDirs.length} Features)
|
|
578
583
|
${featureE2eDirs
|
|
579
584
|
.map((d) => {
|
|
580
585
|
const rel = path.relative(cwd, d);
|
|
581
586
|
const name = path.basename(path.dirname(d));
|
|
582
587
|
const e2eName = `${name}-${e2eFolderName}`;
|
|
583
|
-
return `- \`./${rel}\` (**${name}**, Nx Project: \`${e2eName}\`):
|
|
584
|
-
- \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
|
|
585
|
-
- \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
|
|
588
|
+
return `- \`./${rel}\` (**${name}**, Nx Project: \`${e2eName}\`):
|
|
589
|
+
- \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
|
|
590
|
+
- \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
|
|
586
591
|
- \`project.json\`: Nx targets (\`e2e\`, \`type-check\`) supporting configurations (\`--configuration=android\`, \`--configuration=ios\`, \`--configuration=headless\`).`;
|
|
587
592
|
})
|
|
588
|
-
.join('\n')}
|
|
589
|
-
|
|
590
|
-
---
|
|
591
|
-
|
|
592
|
-
## 2. Zero-Import Consumption Flow & Mechanics
|
|
593
|
-
|
|
594
|
-
TestSpectra completely eliminates boilerplate \`import\` statements across all test specs, steps, and page objects.
|
|
595
|
-
|
|
596
|
-
### How It Works:
|
|
597
|
-
1. **Centralized Ambient Aggregation**:
|
|
598
|
-
TestSpectra Language Service Plugin & CLI automatically scan:
|
|
599
|
-
- **Local Feature Entities**: \`<feature>/${e2eFolderName}/page-objects/\`, \`<feature>/${e2eFolderName}/steps/\`, \`<feature>/${e2eFolderName}/actions/\`
|
|
600
|
-
- **Shared Library Entities**: \`./${sharedTestingRelPath}/page-objects/\`, \`./${sharedTestingRelPath}/steps/\`, \`./${sharedTestingRelPath}/actions/\`, \`./${sharedTestingRelPath}/fixtures/\`
|
|
601
|
-
2. **Ambient Typing Generation**:
|
|
602
|
-
These are compiled into \`.testspectra/types/web.d.ts\`, \`android.d.ts\`, \`ios.d.ts\`, \`fixtures.d.ts\`.
|
|
603
|
-
3. **Instant IDE Autocomplete & Zero Clutter**:
|
|
604
|
-
Specs consume both local feature objects and shared library utilities globally.
|
|
605
|
-
|
|
606
|
-
### Code Example:
|
|
607
|
-
\`\`\`typescript
|
|
608
|
-
// packages/features/auth/e2e/specs/Auth/TC-AUTH-01/web.test.ts
|
|
609
|
-
// 🌟 NOTICE: 100% Zero-Import & Flat Execution! (No import statement, no describe wrapper)
|
|
610
|
-
|
|
611
|
-
it("should navigate via shared header and login with local POM", async () => {
|
|
612
|
-
// 1. Consumed from Shared Testing Library (${sharedTestingRelPath}/page-objects/NavigationBar)
|
|
613
|
-
await NavigationBar.goToLogin();
|
|
614
|
-
|
|
615
|
-
// 2. Consumed from Shared Testing Library (${sharedTestingRelPath}/steps/loginAsAdmin)
|
|
616
|
-
await Step.loginAsAdmin();
|
|
617
|
-
|
|
618
|
-
// 3. Consumed from Local Feature POM (page-objects/LoginPage)
|
|
619
|
-
await LoginPage.flashAlert.shouldBeVisible();
|
|
620
|
-
await LoginPage.flashAlert.shouldContainText("Welcome");
|
|
621
|
-
|
|
622
|
-
// 4. Consumed from Shared Testing Library Custom Action (${sharedTestingRelPath}/actions/dismissBanner)
|
|
623
|
-
await Spectra.dismissBanner();
|
|
624
|
-
});
|
|
625
|
-
\`\`\`
|
|
626
|
-
|
|
627
|
-
---
|
|
628
|
-
|
|
629
|
-
## 3. How to Run Tests
|
|
630
|
-
|
|
631
|
-
You can execute tests either from the **Monorepo Root via Nx** or directly inside each **Feature Directory via CLI**:
|
|
632
|
-
|
|
633
|
-
### Option A: From Root Monorepo via Nx (Recommended for CI & Team Workflows)
|
|
634
|
-
|
|
635
|
-
\`\`\`bash
|
|
636
|
-
# 1. Run E2E for a SPECIFIC feature module (e.g. auth)
|
|
637
|
-
pnpm nx run auth-${e2eFolderName}:e2e
|
|
638
|
-
|
|
639
|
-
# 2. Run E2E for a specific feature on ANDROID or IOS
|
|
640
|
-
pnpm nx run auth-${e2eFolderName}:e2e --configuration=android
|
|
641
|
-
pnpm nx run auth-${e2eFolderName}:e2e --configuration=ios
|
|
642
|
-
|
|
643
|
-
# 3. Run E2E for a specific feature in HEADLESS browser mode
|
|
644
|
-
pnpm nx run auth-${e2eFolderName}:e2e --configuration=headless
|
|
645
|
-
|
|
646
|
-
# 4. Run ALL feature E2E suites across the entire monorepo in parallel
|
|
647
|
-
pnpm nx run-many -t e2e
|
|
648
|
-
|
|
649
|
-
# 5. Run only E2E tests for features modified in current Git branch / PR
|
|
650
|
-
pnpm nx affected -t e2e
|
|
651
|
-
\`\`\`
|
|
652
|
-
|
|
653
|
-
### Option B: From Inside the Feature Directory (Direct CLI)
|
|
654
|
-
|
|
655
|
-
\`\`\`bash
|
|
656
|
-
# Navigate to the feature e2e folder
|
|
657
|
-
cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/') : `modules/auth/${e2eFolderName}`}
|
|
658
|
-
|
|
659
|
-
# Run all specs in this feature
|
|
660
|
-
pnpm spectra run
|
|
661
|
-
|
|
662
|
-
# Run a specific test case file
|
|
663
|
-
pnpm spectra run specs/Auth/TC-AUTH-01/web.test.ts
|
|
664
|
-
|
|
665
|
-
# Target Android device or emulator
|
|
666
|
-
pnpm spectra run --target android
|
|
667
|
-
|
|
668
|
-
# Run in headed / interactive browser mode
|
|
669
|
-
pnpm spectra run --no-headless
|
|
670
|
-
\`\`\`
|
|
671
|
-
|
|
672
|
-
---
|
|
673
|
-
|
|
674
|
-
## 4. Type Checking & Validation
|
|
675
|
-
|
|
676
|
-
\`\`\`bash
|
|
677
|
-
# Type check all E2E projects via Nx
|
|
678
|
-
pnpm nx run-many -t type-check
|
|
679
|
-
|
|
680
|
-
# Or standard TypeScript project references build from root
|
|
681
|
-
pnpm type-check
|
|
682
|
-
# or: npx tsc -b
|
|
683
|
-
\`\`\`
|
|
593
|
+
.join('\n')}
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## 2. Zero-Import Consumption Flow & Mechanics
|
|
598
|
+
|
|
599
|
+
TestSpectra completely eliminates boilerplate \`import\` statements across all test specs, steps, and page objects.
|
|
600
|
+
|
|
601
|
+
### How It Works:
|
|
602
|
+
1. **Centralized Ambient Aggregation**:
|
|
603
|
+
TestSpectra Language Service Plugin & CLI automatically scan:
|
|
604
|
+
- **Local Feature Entities**: \`<feature>/${e2eFolderName}/page-objects/\`, \`<feature>/${e2eFolderName}/steps/\`, \`<feature>/${e2eFolderName}/actions/\`
|
|
605
|
+
- **Shared Library Entities**: \`./${sharedTestingRelPath}/page-objects/\`, \`./${sharedTestingRelPath}/steps/\`, \`./${sharedTestingRelPath}/actions/\`, \`./${sharedTestingRelPath}/fixtures/\`
|
|
606
|
+
2. **Ambient Typing Generation**:
|
|
607
|
+
These are compiled into \`.testspectra/types/web.d.ts\`, \`android.d.ts\`, \`ios.d.ts\`, \`fixtures.d.ts\`.
|
|
608
|
+
3. **Instant IDE Autocomplete & Zero Clutter**:
|
|
609
|
+
Specs consume both local feature objects and shared library utilities globally.
|
|
610
|
+
|
|
611
|
+
### Code Example:
|
|
612
|
+
\`\`\`typescript
|
|
613
|
+
// packages/features/auth/e2e/specs/Auth/TC-AUTH-01/web.test.ts
|
|
614
|
+
// 🌟 NOTICE: 100% Zero-Import & Flat Execution! (No import statement, no describe wrapper)
|
|
615
|
+
|
|
616
|
+
it("should navigate via shared header and login with local POM", async () => {
|
|
617
|
+
// 1. Consumed from Shared Testing Library (${sharedTestingRelPath}/page-objects/NavigationBar)
|
|
618
|
+
await NavigationBar.goToLogin();
|
|
619
|
+
|
|
620
|
+
// 2. Consumed from Shared Testing Library (${sharedTestingRelPath}/steps/loginAsAdmin)
|
|
621
|
+
await Step.loginAsAdmin();
|
|
622
|
+
|
|
623
|
+
// 3. Consumed from Local Feature POM (page-objects/LoginPage)
|
|
624
|
+
await LoginPage.flashAlert.shouldBeVisible();
|
|
625
|
+
await LoginPage.flashAlert.shouldContainText("Welcome");
|
|
626
|
+
|
|
627
|
+
// 4. Consumed from Shared Testing Library Custom Action (${sharedTestingRelPath}/actions/dismissBanner)
|
|
628
|
+
await Spectra.dismissBanner();
|
|
629
|
+
});
|
|
630
|
+
\`\`\`
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
## 3. How to Run Tests
|
|
635
|
+
|
|
636
|
+
You can execute tests either from the **Monorepo Root via Nx** or directly inside each **Feature Directory via CLI**:
|
|
637
|
+
|
|
638
|
+
### Option A: From Root Monorepo via Nx (Recommended for CI & Team Workflows)
|
|
639
|
+
|
|
640
|
+
\`\`\`bash
|
|
641
|
+
# 1. Run E2E for a SPECIFIC feature module (e.g. auth)
|
|
642
|
+
pnpm nx run auth-${e2eFolderName}:e2e
|
|
643
|
+
|
|
644
|
+
# 2. Run E2E for a specific feature on ANDROID or IOS
|
|
645
|
+
pnpm nx run auth-${e2eFolderName}:e2e --configuration=android
|
|
646
|
+
pnpm nx run auth-${e2eFolderName}:e2e --configuration=ios
|
|
647
|
+
|
|
648
|
+
# 3. Run E2E for a specific feature in HEADLESS browser mode
|
|
649
|
+
pnpm nx run auth-${e2eFolderName}:e2e --configuration=headless
|
|
650
|
+
|
|
651
|
+
# 4. Run ALL feature E2E suites across the entire monorepo in parallel
|
|
652
|
+
pnpm nx run-many -t e2e
|
|
653
|
+
|
|
654
|
+
# 5. Run only E2E tests for features modified in current Git branch / PR
|
|
655
|
+
pnpm nx affected -t e2e
|
|
656
|
+
\`\`\`
|
|
657
|
+
|
|
658
|
+
### Option B: From Inside the Feature Directory (Direct CLI)
|
|
659
|
+
|
|
660
|
+
\`\`\`bash
|
|
661
|
+
# Navigate to the feature e2e folder
|
|
662
|
+
cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/') : `modules/auth/${e2eFolderName}`}
|
|
663
|
+
|
|
664
|
+
# Run all specs in this feature
|
|
665
|
+
pnpm spectra run
|
|
666
|
+
|
|
667
|
+
# Run a specific test case file
|
|
668
|
+
pnpm spectra run specs/Auth/TC-AUTH-01/web.test.ts
|
|
669
|
+
|
|
670
|
+
# Target Android device or emulator
|
|
671
|
+
pnpm spectra run --target android
|
|
672
|
+
|
|
673
|
+
# Run in headed / interactive browser mode
|
|
674
|
+
pnpm spectra run --no-headless
|
|
675
|
+
\`\`\`
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## 4. Type Checking & Validation
|
|
680
|
+
|
|
681
|
+
\`\`\`bash
|
|
682
|
+
# Type check all E2E projects via Nx
|
|
683
|
+
pnpm nx run-many -t type-check
|
|
684
|
+
|
|
685
|
+
# Or standard TypeScript project references build from root
|
|
686
|
+
pnpm type-check
|
|
687
|
+
# or: npx tsc -b
|
|
688
|
+
\`\`\`
|
|
684
689
|
`;
|
|
685
690
|
fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), archDocContent, 'utf-8');
|
|
686
691
|
// 8. Ensure root .gitignore ignores .testspectra/ and *.tsbuildinfo
|
|
@@ -736,32 +741,32 @@ pnpm type-check
|
|
|
736
741
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
737
742
|
ensureVsCodeExtensions(cwd);
|
|
738
743
|
// Standalone Architecture Documentation
|
|
739
|
-
const standaloneArchDoc = `# TestSpectra Project Architecture
|
|
740
|
-
|
|
741
|
-
This project is built with the TestSpectra testing framework.
|
|
742
|
-
|
|
743
|
-
## Structure & File Map
|
|
744
|
-
- \`./spectra.config.ts\`: Typed runner configuration.
|
|
745
|
-
- \`./specs/\`: Layered test cases (\`specs/<Suite>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
|
|
746
|
-
- \`./page-objects/\`: Page Object models per platform.
|
|
747
|
-
- \`./steps/\`: Business flows accessible globally via \`Step.*\`.
|
|
748
|
-
- \`./actions/\`: Atomic actions accessible globally via \`Spectra.*\`.
|
|
749
|
-
- \`./fixtures/\`: JSON fixtures accessible globally via \`Fixture.*\`.
|
|
750
|
-
- \`./global-hooks/\`: Global suite lifecycle hooks.
|
|
751
|
-
- \`./.testspectra/\`: Auto-generated ambient typings and local runtime cache.
|
|
752
|
-
|
|
753
|
-
## Execution
|
|
754
|
-
\`\`\`bash
|
|
755
|
-
# Run web test suite
|
|
756
|
-
pnpm test
|
|
757
|
-
|
|
758
|
-
# Run Android / iOS
|
|
759
|
-
pnpm spectra run --target android
|
|
760
|
-
pnpm spectra run --target ios
|
|
761
|
-
|
|
762
|
-
# Type check
|
|
763
|
-
pnpm type-check
|
|
764
|
-
\`\`\`
|
|
744
|
+
const standaloneArchDoc = `# TestSpectra Project Architecture
|
|
745
|
+
|
|
746
|
+
This project is built with the TestSpectra testing framework.
|
|
747
|
+
|
|
748
|
+
## Structure & File Map
|
|
749
|
+
- \`./spectra.config.ts\`: Typed runner configuration.
|
|
750
|
+
- \`./specs/\`: Layered test cases (\`specs/<Suite>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
|
|
751
|
+
- \`./page-objects/\`: Page Object models per platform.
|
|
752
|
+
- \`./steps/\`: Business flows accessible globally via \`Step.*\`.
|
|
753
|
+
- \`./actions/\`: Atomic actions accessible globally via \`Spectra.*\`.
|
|
754
|
+
- \`./fixtures/\`: JSON fixtures accessible globally via \`Fixture.*\`.
|
|
755
|
+
- \`./global-hooks/\`: Global suite lifecycle hooks.
|
|
756
|
+
- \`./.testspectra/\`: Auto-generated ambient typings and local runtime cache.
|
|
757
|
+
|
|
758
|
+
## Execution
|
|
759
|
+
\`\`\`bash
|
|
760
|
+
# Run web test suite
|
|
761
|
+
pnpm test
|
|
762
|
+
|
|
763
|
+
# Run Android / iOS
|
|
764
|
+
pnpm spectra run --target android
|
|
765
|
+
pnpm spectra run --target ios
|
|
766
|
+
|
|
767
|
+
# Type check
|
|
768
|
+
pnpm type-check
|
|
769
|
+
\`\`\`
|
|
765
770
|
`;
|
|
766
771
|
fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), standaloneArchDoc, 'utf-8');
|
|
767
772
|
// Safely update root README.md (do not overwrite existing content)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { SpecLinter } from '../linter/spec-linter.js';
|
|
4
|
+
export async function lintCommand(targetPath, options = {}) {
|
|
5
|
+
const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
|
|
6
|
+
try {
|
|
7
|
+
const result = SpecLinter.validateWorkspace(cwd, targetPath);
|
|
8
|
+
if (options.json) {
|
|
9
|
+
console.log(JSON.stringify(result, null, 2));
|
|
10
|
+
if (!result.valid || (options.strict && result.stats.warningCount > 0)) {
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
console.log(chalk.bold('\n🔍 TestSpectra Spec & Code Linter\n'));
|
|
16
|
+
// Group diagnostics by file
|
|
17
|
+
const diagnosticsByFile = new Map();
|
|
18
|
+
for (const diag of result.diagnostics) {
|
|
19
|
+
const relPath = path.relative(cwd, diag.filePath);
|
|
20
|
+
const list = diagnosticsByFile.get(relPath) || [];
|
|
21
|
+
list.push(diag);
|
|
22
|
+
diagnosticsByFile.set(relPath, list);
|
|
23
|
+
}
|
|
24
|
+
if (diagnosticsByFile.size === 0) {
|
|
25
|
+
console.log(chalk.green(` ✓ ${result.stats.totalSpecs} specs and ${result.stats.totalSuites} suites verified.`));
|
|
26
|
+
console.log(chalk.green.bold('\n✓ All spec schemas are valid. 0 lint errors found.\n'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
for (const [file, diags] of diagnosticsByFile.entries()) {
|
|
30
|
+
console.log(chalk.bold(` ${file}`));
|
|
31
|
+
for (const diag of diags) {
|
|
32
|
+
const icon = diag.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
|
|
33
|
+
const codeTag = chalk.gray(`[${diag.code}]`);
|
|
34
|
+
const loc = chalk.dim(`:${diag.line}`);
|
|
35
|
+
console.log(` ${icon} ${codeTag} ${diag.message} ${loc}`);
|
|
36
|
+
}
|
|
37
|
+
console.log();
|
|
38
|
+
}
|
|
39
|
+
const { errorCount, warningCount } = result.stats;
|
|
40
|
+
if (errorCount > 0) {
|
|
41
|
+
console.log(chalk.red.bold(`✖ ${errorCount} lint error${errorCount > 1 ? 's' : ''}${warningCount > 0 ? ` and ${warningCount} warning${warningCount > 1 ? 's' : ''}` : ''} found.\n`));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
else if (warningCount > 0) {
|
|
45
|
+
console.log(chalk.yellow.bold(`⚠ ${warningCount} lint warning${warningCount > 1 ? 's' : ''} found.\n`));
|
|
46
|
+
if (options.strict) {
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error(chalk.red(`\n✖ Fatal error during linting: ${err?.message || err}\n`));
|
|
53
|
+
process.exit(2);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RefactorResult } from '../refactor/refactor-engine.js';
|
|
1
2
|
export interface RefactorCommandOptions {
|
|
2
3
|
cwd?: string;
|
|
3
4
|
dryRun?: boolean;
|
|
@@ -5,4 +6,4 @@ export interface RefactorCommandOptions {
|
|
|
5
6
|
from?: string;
|
|
6
7
|
to?: string;
|
|
7
8
|
}
|
|
8
|
-
export declare function refactorCommand(entityType?: string, oldName?: string, newName?: string, options?: RefactorCommandOptions): Promise<
|
|
9
|
+
export declare function refactorCommand(entityType?: string, oldName?: string, newName?: string, options?: RefactorCommandOptions): Promise<RefactorResult>;
|
|
@@ -5,52 +5,142 @@ export async function refactorCommand(entityType, oldName, newName, options = {}
|
|
|
5
5
|
let targetType = 'page-object';
|
|
6
6
|
let fromSymbol = oldName || '';
|
|
7
7
|
let toSymbol = newName || '';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (options.from.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
// 1. Path-based refactoring via --from and --to flags
|
|
9
|
+
if (options.from || options.to) {
|
|
10
|
+
if (!options.from || !options.to) {
|
|
11
|
+
const errorMsg = 'Both --from and --to must be specified for path-based refactoring.';
|
|
12
|
+
const result = {
|
|
13
|
+
status: 'failed',
|
|
14
|
+
entityType: 'page-object',
|
|
15
|
+
oldSymbol: options.from || '',
|
|
16
|
+
newSymbol: options.to || '',
|
|
17
|
+
modifiedFiles: [],
|
|
18
|
+
occurrencesReplaced: 0,
|
|
19
|
+
error: errorMsg,
|
|
20
|
+
};
|
|
21
|
+
if (options.json) {
|
|
22
|
+
console.log(JSON.stringify(result, null, 2));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.error(`\x1b[31m✗ ${errorMsg}\x1b[0m`);
|
|
26
|
+
}
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return result;
|
|
14
29
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
const pathResult = await RefactorEngine.renameFromPaths({
|
|
31
|
+
workspaceRoot,
|
|
32
|
+
fromPath: options.from,
|
|
33
|
+
toPath: options.to,
|
|
34
|
+
dryRun: options.dryRun,
|
|
35
|
+
});
|
|
36
|
+
if (options.json) {
|
|
37
|
+
console.log(JSON.stringify(pathResult, null, 2));
|
|
38
|
+
if (pathResult.status === 'failed') {
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
return pathResult;
|
|
19
42
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
if (pathResult.status === 'success') {
|
|
44
|
+
const modeStr = options.dryRun ? '[Dry-Run] ' : '';
|
|
45
|
+
console.log(`\x1b[32m✓ ${modeStr}Refactored ${pathResult.entityType} "${pathResult.oldSymbol}" → "${pathResult.newSymbol}"\x1b[0m`);
|
|
46
|
+
console.log(` Modified files: ${pathResult.modifiedFiles.length}`);
|
|
47
|
+
console.log(` Occurrences replaced: ${pathResult.occurrencesReplaced}`);
|
|
48
|
+
for (const f of pathResult.modifiedFiles) {
|
|
49
|
+
console.log(` ↳ ${f}`);
|
|
50
|
+
}
|
|
24
51
|
}
|
|
25
|
-
else
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
toSymbol = path.parse(options.to).name;
|
|
52
|
+
else {
|
|
53
|
+
console.error(`\x1b[31m✗ Refactoring failed: ${pathResult.error}\x1b[0m`);
|
|
54
|
+
process.exitCode = 1;
|
|
29
55
|
}
|
|
56
|
+
return pathResult;
|
|
30
57
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
58
|
+
// 2. Also support positional path syntax: spectra refactor <fromPath> <toPath>
|
|
59
|
+
if (entityType && oldName && !newName && RefactorEngine.parseEntityFromPath(entityType)) {
|
|
60
|
+
const pathResult = await RefactorEngine.renameFromPaths({
|
|
61
|
+
workspaceRoot,
|
|
62
|
+
fromPath: entityType,
|
|
63
|
+
toPath: oldName,
|
|
64
|
+
dryRun: options.dryRun,
|
|
65
|
+
});
|
|
66
|
+
if (options.json) {
|
|
67
|
+
console.log(JSON.stringify(pathResult, null, 2));
|
|
68
|
+
if (pathResult.status === 'failed') {
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
}
|
|
71
|
+
return pathResult;
|
|
35
72
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
73
|
+
if (pathResult.status === 'success') {
|
|
74
|
+
const modeStr = options.dryRun ? '[Dry-Run] ' : '';
|
|
75
|
+
console.log(`\x1b[32m✓ ${modeStr}Refactored ${pathResult.entityType} "${pathResult.oldSymbol}" → "${pathResult.newSymbol}"\x1b[0m`);
|
|
76
|
+
console.log(` Modified files: ${pathResult.modifiedFiles.length}`);
|
|
77
|
+
console.log(` Occurrences replaced: ${pathResult.occurrencesReplaced}`);
|
|
78
|
+
for (const f of pathResult.modifiedFiles) {
|
|
79
|
+
console.log(` ↳ ${f}`);
|
|
80
|
+
}
|
|
39
81
|
}
|
|
40
|
-
else
|
|
41
|
-
|
|
82
|
+
else {
|
|
83
|
+
console.error(`\x1b[31m✗ Refactoring failed: ${pathResult.error}\x1b[0m`);
|
|
84
|
+
process.exitCode = 1;
|
|
42
85
|
}
|
|
43
|
-
|
|
44
|
-
|
|
86
|
+
return pathResult;
|
|
87
|
+
}
|
|
88
|
+
// 3. Positional syntax: spectra refactor <entityType> <oldName> <newName>
|
|
89
|
+
if (!entityType || !oldName || !newName) {
|
|
90
|
+
const errorMsg = 'Usage: spectra refactor <page-object|step|action|fixture> <oldName> <newName> or spectra refactor --from <path> --to <path>';
|
|
91
|
+
const result = {
|
|
92
|
+
status: 'failed',
|
|
93
|
+
entityType: 'page-object',
|
|
94
|
+
oldSymbol: oldName || '',
|
|
95
|
+
newSymbol: newName || '',
|
|
96
|
+
modifiedFiles: [],
|
|
97
|
+
occurrencesReplaced: 0,
|
|
98
|
+
error: errorMsg,
|
|
99
|
+
};
|
|
100
|
+
if (options.json) {
|
|
101
|
+
console.log(JSON.stringify(result, null, 2));
|
|
45
102
|
}
|
|
46
|
-
else
|
|
47
|
-
|
|
103
|
+
else {
|
|
104
|
+
console.error(errorMsg);
|
|
105
|
+
}
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
const normalized = entityType.toLowerCase().replace(/_/g, '-');
|
|
110
|
+
if (['page-object', 'po', 'page', 'pageobject'].includes(normalized)) {
|
|
111
|
+
targetType = 'page-object';
|
|
112
|
+
}
|
|
113
|
+
else if (['step', 'steps'].includes(normalized)) {
|
|
114
|
+
targetType = 'step';
|
|
115
|
+
}
|
|
116
|
+
else if (['action', 'actions'].includes(normalized)) {
|
|
117
|
+
targetType = 'action';
|
|
118
|
+
}
|
|
119
|
+
else if (['fixture', 'fixtures'].includes(normalized)) {
|
|
120
|
+
targetType = 'fixture';
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const errorMsg = `Unknown refactor entity type: "${entityType}". Valid types: page-object, step, action, fixture.`;
|
|
124
|
+
const result = {
|
|
125
|
+
status: 'failed',
|
|
126
|
+
entityType: 'page-object',
|
|
127
|
+
oldSymbol: oldName,
|
|
128
|
+
newSymbol: newName,
|
|
129
|
+
modifiedFiles: [],
|
|
130
|
+
occurrencesReplaced: 0,
|
|
131
|
+
error: errorMsg,
|
|
132
|
+
};
|
|
133
|
+
if (options.json) {
|
|
134
|
+
console.log(JSON.stringify(result, null, 2));
|
|
48
135
|
}
|
|
49
136
|
else {
|
|
50
|
-
console.error(
|
|
51
|
-
process.exit(1);
|
|
137
|
+
console.error(`\x1b[31m✗ ${errorMsg}\x1b[0m`);
|
|
52
138
|
}
|
|
139
|
+
process.exitCode = 1;
|
|
140
|
+
return result;
|
|
53
141
|
}
|
|
142
|
+
fromSymbol = oldName;
|
|
143
|
+
toSymbol = newName;
|
|
54
144
|
const result = await RefactorEngine.renameSymbol({
|
|
55
145
|
workspaceRoot,
|
|
56
146
|
type: targetType,
|
|
@@ -60,7 +150,10 @@ export async function refactorCommand(entityType, oldName, newName, options = {}
|
|
|
60
150
|
});
|
|
61
151
|
if (options.json) {
|
|
62
152
|
console.log(JSON.stringify(result, null, 2));
|
|
63
|
-
|
|
153
|
+
if (result.status === 'failed') {
|
|
154
|
+
process.exitCode = 1;
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
64
157
|
}
|
|
65
158
|
if (result.status === 'success') {
|
|
66
159
|
const modeStr = options.dryRun ? '[Dry-Run] ' : '';
|
|
@@ -73,6 +166,7 @@ export async function refactorCommand(entityType, oldName, newName, options = {}
|
|
|
73
166
|
}
|
|
74
167
|
else {
|
|
75
168
|
console.error(`\x1b[31m✗ Refactoring failed: ${result.error}\x1b[0m`);
|
|
76
|
-
process.
|
|
169
|
+
process.exitCode = 1;
|
|
77
170
|
}
|
|
171
|
+
return result;
|
|
78
172
|
}
|