@testspectra/cli 1.0.70 ā 1.1.0
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/LICENSE.md +48 -0
- package/README.md +36 -270
- package/bin/.testspectra-runner.hash +1 -0
- package/bin/spectra.js +1 -1
- package/bin/testspectra-runner +0 -0
- package/demo-app/assets/index-DPfv5_EF.js +116 -0
- package/demo-app/assets/index-gIwu2EMV.css +1 -0
- package/demo-app/index.html +2 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/commands/__tests__/cloud-run.test.d.ts +1 -0
- package/dist/commands/__tests__/cloud-run.test.js +93 -0
- package/dist/commands/__tests__/doctor.test.d.ts +1 -0
- package/dist/commands/__tests__/doctor.test.js +39 -0
- package/dist/commands/__tests__/init.test.d.ts +1 -0
- package/dist/commands/__tests__/init.test.js +148 -0
- package/dist/commands/__tests__/license.test.d.ts +1 -0
- package/dist/commands/__tests__/license.test.js +13 -0
- package/dist/commands/__tests__/refactor.test.d.ts +1 -0
- package/dist/commands/__tests__/refactor.test.js +64 -0
- package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
- package/dist/commands/__tests__/run-e2e.test.js +181 -0
- package/dist/commands/__tests__/run.test.d.ts +1 -0
- package/dist/commands/__tests__/run.test.js +197 -0
- package/dist/commands/__tests__/watch.test.d.ts +1 -0
- package/dist/commands/__tests__/watch.test.js +155 -0
- package/dist/commands/add.js +86 -85
- package/dist/commands/cloud-run.d.ts +11 -0
- package/dist/commands/cloud-run.js +118 -0
- package/dist/commands/demo.js +7 -7
- package/dist/commands/devices.d.ts +14 -2
- package/dist/commands/devices.js +44 -34
- package/dist/commands/doctor.d.ts +3 -1
- package/dist/commands/doctor.js +43 -264
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +206 -196
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.js +59 -0
- package/dist/commands/license.d.ts +4 -0
- package/dist/commands/license.js +30 -0
- package/dist/commands/refactor.d.ts +8 -0
- package/dist/commands/refactor.js +78 -0
- package/dist/commands/run.d.ts +22 -1
- package/dist/commands/run.js +252 -109
- package/dist/commands/sync-types.d.ts +4 -0
- package/dist/commands/sync-types.js +14 -0
- package/dist/commands/update.js +39 -43
- package/dist/commands/watch.d.ts +6 -0
- package/dist/commands/watch.js +47 -43
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +40 -25
- package/dist/config/schema.d.ts +117 -49
- package/dist/config/schema.js +41 -34
- package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
- package/dist/generator/__tests__/tsconfig-isolation.test.js +278 -0
- package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
- package/dist/generator/__tests__/type-generator.test.js +386 -0
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +2 -0
- package/dist/generator/tsconfig-generator.d.ts +27 -0
- package/dist/generator/tsconfig-generator.js +391 -0
- package/dist/generator/type-generator.d.ts +36 -0
- package/dist/generator/type-generator.js +644 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +124 -51
- package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
- package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
- package/dist/lifecycle/hook-discovery.d.ts +24 -0
- package/dist/lifecycle/hook-discovery.js +60 -0
- package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
- package/dist/lifecycle/hook-dispatcher.js +190 -0
- package/dist/lifecycle/index.d.ts +3 -0
- package/dist/lifecycle/index.js +3 -0
- package/dist/lifecycle/parallel-grouping.d.ts +27 -0
- package/dist/lifecycle/parallel-grouping.js +151 -0
- package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
- package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
- package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
- package/dist/mobile/__tests__/device-pool.test.js +97 -0
- package/dist/mobile/device-pool.d.ts +23 -0
- package/dist/mobile/device-pool.js +1 -0
- package/dist/mobile/index.d.ts +1 -0
- package/dist/mobile/index.js +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.js +175 -0
- package/dist/refactor/index.d.ts +1 -0
- package/dist/refactor/index.js +1 -0
- package/dist/refactor/refactor-engine.d.ts +25 -0
- package/dist/refactor/refactor-engine.js +255 -0
- package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
- package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
- package/dist/reporter/semantic-formatter.d.ts +6 -0
- package/dist/reporter/semantic-formatter.js +191 -0
- package/dist/reporter/summary-view.d.ts +12 -0
- package/dist/reporter/summary-view.js +19 -0
- package/dist/reporter/terminal-stream.d.ts +32 -0
- package/dist/reporter/terminal-stream.js +82 -0
- package/dist/reporter/types.d.ts +2 -0
- package/dist/reporter/types.js +1 -0
- package/dist/runner/bridge.d.ts +10 -5
- package/dist/runner/bridge.js +69 -44
- package/dist/runner/reporter.d.ts +30 -2
- package/dist/runner/reporter.js +419 -13
- package/dist/server/demo.js +43 -44
- package/dist/types/generator.d.ts +1 -17
- package/dist/types/generator.js +1 -443
- package/dist/utils/demo-server.js +38 -35
- package/package.json +20 -12
- package/templates/default/CLAUDE.md +7 -5
- package/templates/default/fixtures/apiData.json +24 -0
- package/templates/default/package.json +13 -4
- package/templates/default/page-objects/MatchersPage/mobile.ts +41 -16
- package/templates/default/page-objects/MatchersPage/web.ts +14 -0
- package/templates/default/page-objects/NavigationPage/mobile.ts +10 -1
- package/templates/default/page-objects/PlaygroundPage/mobile.ts +27 -17
- package/templates/default/page-objects/PlaygroundPage/web.ts +2 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +11 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +16 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
- package/templates/default/specs/ApiInterception/suite.md +9 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +15 -1
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
- package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
- package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
- package/templates/default/spectra.config.ts +21 -14
- package/templates/default/support/actions/fillCredentials/mobile.action.ts +2 -2
- package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +2 -0
- package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +2 -2
- package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +3 -1
- package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +3 -3
- package/templates/default/tsconfig.json +3 -1
- package/templates/nx/.nx/workspace-data/d/daemon.log +168 -0
- package/templates/nx/CLAUDE.md +2 -2
- package/templates/nx/package.json +7 -5
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +3 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +3 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
- package/templates/nx/pnpm-lock.yaml +6663 -0
- package/templates/nx/pnpm-workspace.yaml +2 -1
- package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
- package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +12 -0
- package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +14 -0
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +2 -0
- package/templates/nx/shared/testing/project.json +19 -0
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +2 -2
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +1 -1
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +3 -3
- package/templates/nx/spectra.config.ts +19 -12
- package/templates/nx/tsconfig.json +3 -1
- package/demo-app/assets/index-DAUlrHTr.css +0 -1
- package/demo-app/assets/index-DN2Kb0A8.js +0 -100
- package/dist/index.cjs +0 -101
- package/dist/plugin.cjs +0 -575
- package/dist/plugin.d.ts +0 -16
- package/dist/plugin.js +0 -184
- package/dist/step/index.d.ts +0 -6
- package/dist/step/index.js +0 -6
- package/dist/step/matchers.d.ts +0 -14
- package/dist/step/matchers.js +0 -114
- package/dist/step/proto.d.ts +0 -46
- package/dist/step/proto.js +0 -114
- package/dist/step/runner/collection.d.ts +0 -13
- package/dist/step/runner/collection.js +0 -51
- package/dist/step/runner/single.d.ts +0 -21
- package/dist/step/runner/single.js +0 -138
- package/dist/step/spectra.d.ts +0 -34
- package/dist/step/spectra.js +0 -156
- package/dist/step/types.d.ts +0 -32
- package/dist/step/types.js +0 -5
- package/templates/default/tsconfig.spectra.android.json +0 -46
- package/templates/default/tsconfig.spectra.ios.json +0 -46
- package/templates/default/tsconfig.spectra.json +0 -22
- package/templates/default/tsconfig.spectra.web.json +0 -53
- package/templates/nx/fixtures/playgroundData.json +0 -7
- package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
- package/templates/nx/global-hooks/after/web.hook.ts +0 -3
- package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
- package/templates/nx/global-hooks/before/web.hook.ts +0 -3
- package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
- package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
- package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
- package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
- package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
- package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
- package/templates/nx/specs/BrowserContext/suite.md +0 -9
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
- package/templates/nx/specs/ContentMatchers/suite.md +0 -9
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
- package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/suite.md +0 -9
- package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
- package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
- package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
- package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/suite.md +0 -9
- package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
- package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
- package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
- package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
- package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
- package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
- package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
- package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
- package/templates/nx/tsconfig.spectra.android.json +0 -39
- package/templates/nx/tsconfig.spectra.ios.json +0 -39
- package/templates/nx/tsconfig.spectra.json +0 -25
- package/templates/nx/tsconfig.spectra.shared.json +0 -25
- package/templates/nx/tsconfig.spectra.web.json +0 -45
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
package/dist/commands/init.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import { fileURLToPath } from
|
|
4
|
-
import * as p from
|
|
5
|
-
import chalk from
|
|
6
|
-
import { ConfigLoader } from
|
|
7
|
-
import { TypeGenerator } from
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import * as p from '@clack/prompts';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { ConfigLoader } from '../config/loader.js';
|
|
7
|
+
import { TypeGenerator } from '../types/generator.js';
|
|
8
|
+
import { TsConfigGenerator } from '../generator/tsconfig-generator.js';
|
|
8
9
|
function parsePnpmWorkspaceGlobs(content) {
|
|
9
|
-
const lines = content.split(
|
|
10
|
+
const lines = content.split('\n');
|
|
10
11
|
const globs = [];
|
|
11
12
|
let inPackages = false;
|
|
12
13
|
for (const line of lines) {
|
|
13
14
|
const trimmed = line.trim();
|
|
14
|
-
if (trimmed.startsWith(
|
|
15
|
+
if (trimmed.startsWith('packages:')) {
|
|
15
16
|
inPackages = true;
|
|
16
17
|
continue;
|
|
17
18
|
}
|
|
18
19
|
if (inPackages) {
|
|
19
|
-
if (trimmed.startsWith(
|
|
20
|
-
const item = trimmed.replace(/^-\s*['"]?/,
|
|
20
|
+
if (trimmed.startsWith('-')) {
|
|
21
|
+
const item = trimmed.replace(/^-\s*['"]?/, '').replace(/['"]?\s*$/, '');
|
|
21
22
|
if (item)
|
|
22
23
|
globs.push(item);
|
|
23
24
|
}
|
|
24
|
-
else if (trimmed && !trimmed.startsWith(
|
|
25
|
+
else if (trimmed && !trimmed.startsWith('#')) {
|
|
25
26
|
break;
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -29,27 +30,28 @@ function parsePnpmWorkspaceGlobs(content) {
|
|
|
29
30
|
return globs;
|
|
30
31
|
}
|
|
31
32
|
function ensurePnpmWorkspaceIncludes(pnpmWorkspacePath, targetPath) {
|
|
32
|
-
const normalizedTarget = targetPath.replace(/\\/g,
|
|
33
|
+
const normalizedTarget = targetPath.replace(/\\/g, '/');
|
|
33
34
|
if (!fs.existsSync(pnpmWorkspacePath)) {
|
|
34
35
|
const newContent = `packages:\n - '${normalizedTarget}'\n`;
|
|
35
|
-
fs.writeFileSync(pnpmWorkspacePath, newContent,
|
|
36
|
+
fs.writeFileSync(pnpmWorkspacePath, newContent, 'utf-8');
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
|
-
const content = fs.readFileSync(pnpmWorkspacePath,
|
|
39
|
+
const content = fs.readFileSync(pnpmWorkspacePath, 'utf-8');
|
|
39
40
|
const currentGlobs = parsePnpmWorkspaceGlobs(content);
|
|
40
41
|
// Check if target is already covered by an existing glob or exact path
|
|
41
42
|
const isAlreadyCovered = currentGlobs.some((glob) => {
|
|
42
|
-
const normGlob = glob.replace(/\\/g,
|
|
43
|
+
const normGlob = glob.replace(/\\/g, '/');
|
|
43
44
|
if (normGlob === normalizedTarget)
|
|
44
45
|
return true;
|
|
45
46
|
if (normGlob === `${normalizedTarget}/*` || normGlob === `${normalizedTarget}/**`)
|
|
46
47
|
return true;
|
|
47
|
-
if (normGlob.endsWith(
|
|
48
|
+
if (normGlob.endsWith('/*')) {
|
|
48
49
|
const parent = normGlob.slice(0, -2);
|
|
49
|
-
if (normalizedTarget.startsWith(`${parent}/`) &&
|
|
50
|
+
if (normalizedTarget.startsWith(`${parent}/`) &&
|
|
51
|
+
normalizedTarget.split('/').length === parent.split('/').length + 1)
|
|
50
52
|
return true;
|
|
51
53
|
}
|
|
52
|
-
if (normGlob.endsWith(
|
|
54
|
+
if (normGlob.endsWith('/**')) {
|
|
53
55
|
const parent = normGlob.slice(0, -3);
|
|
54
56
|
if (normalizedTarget.startsWith(`${parent}/`))
|
|
55
57
|
return true;
|
|
@@ -58,42 +60,42 @@ function ensurePnpmWorkspaceIncludes(pnpmWorkspacePath, targetPath) {
|
|
|
58
60
|
});
|
|
59
61
|
if (!isAlreadyCovered) {
|
|
60
62
|
// Insert new package pattern right under packages:
|
|
61
|
-
const lines = content.split(
|
|
63
|
+
const lines = content.split('\n');
|
|
62
64
|
let insertIndex = -1;
|
|
63
65
|
for (let i = 0; i < lines.length; i++) {
|
|
64
|
-
if (lines[i].trim().startsWith(
|
|
66
|
+
if (lines[i].trim().startsWith('packages:')) {
|
|
65
67
|
insertIndex = i + 1;
|
|
66
68
|
break;
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
if (insertIndex !== -1) {
|
|
70
72
|
lines.splice(insertIndex, 0, ` - '${normalizedTarget}'`);
|
|
71
|
-
fs.writeFileSync(pnpmWorkspacePath, lines.join(
|
|
73
|
+
fs.writeFileSync(pnpmWorkspacePath, lines.join('\n'), 'utf-8');
|
|
72
74
|
}
|
|
73
75
|
else {
|
|
74
76
|
const newContent = `packages:\n - '${normalizedTarget}'\n` + content;
|
|
75
|
-
fs.writeFileSync(pnpmWorkspacePath, newContent,
|
|
77
|
+
fs.writeFileSync(pnpmWorkspacePath, newContent, 'utf-8');
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
function ensureVsCodeExtensions(cwd) {
|
|
80
|
-
const vscodeDir = path.join(cwd,
|
|
81
|
-
const extFile = path.join(vscodeDir,
|
|
82
|
+
const vscodeDir = path.join(cwd, '.vscode');
|
|
83
|
+
const extFile = path.join(vscodeDir, 'extensions.json');
|
|
82
84
|
if (!fs.existsSync(vscodeDir))
|
|
83
85
|
fs.mkdirSync(vscodeDir, { recursive: true });
|
|
84
86
|
let config = { recommendations: [] };
|
|
85
87
|
if (fs.existsSync(extFile)) {
|
|
86
88
|
try {
|
|
87
|
-
config = JSON.parse(fs.readFileSync(extFile,
|
|
89
|
+
config = JSON.parse(fs.readFileSync(extFile, 'utf-8'));
|
|
88
90
|
}
|
|
89
91
|
catch { }
|
|
90
92
|
}
|
|
91
93
|
if (!Array.isArray(config.recommendations))
|
|
92
94
|
config.recommendations = [];
|
|
93
|
-
if (!config.recommendations.includes(
|
|
94
|
-
config.recommendations.push(
|
|
95
|
+
if (!config.recommendations.includes('testspectra.testspectra-vscode')) {
|
|
96
|
+
config.recommendations.push('testspectra.testspectra-vscode');
|
|
95
97
|
}
|
|
96
|
-
fs.writeFileSync(extFile, JSON.stringify(config, null, 2) +
|
|
98
|
+
fs.writeFileSync(extFile, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
97
99
|
}
|
|
98
100
|
function scanDirectoriesForProjects(cwd, patterns) {
|
|
99
101
|
const foundProjects = [];
|
|
@@ -102,11 +104,11 @@ function scanDirectoriesForProjects(cwd, patterns) {
|
|
|
102
104
|
if (currentDepth > maxDepth || !fs.existsSync(dir))
|
|
103
105
|
return;
|
|
104
106
|
const base = path.basename(dir);
|
|
105
|
-
if (base ===
|
|
107
|
+
if (base === 'node_modules' || base === 'dist' || base === '.testspectra' || base === '.git' || base === 'shared') {
|
|
106
108
|
return;
|
|
107
109
|
}
|
|
108
|
-
const hasPkg = fs.existsSync(path.join(dir,
|
|
109
|
-
const hasNx = fs.existsSync(path.join(dir,
|
|
110
|
+
const hasPkg = fs.existsSync(path.join(dir, 'package.json'));
|
|
111
|
+
const hasNx = fs.existsSync(path.join(dir, 'project.json'));
|
|
110
112
|
if ((hasPkg || hasNx) && dir !== cwd) {
|
|
111
113
|
const rel = path.relative(cwd, dir);
|
|
112
114
|
if (!visited.has(rel)) {
|
|
@@ -114,7 +116,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
|
|
|
114
116
|
let name = path.basename(dir);
|
|
115
117
|
if (hasPkg) {
|
|
116
118
|
try {
|
|
117
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(dir,
|
|
119
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
|
|
118
120
|
if (pkg.name)
|
|
119
121
|
name = pkg.name;
|
|
120
122
|
}
|
|
@@ -122,7 +124,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
|
|
|
122
124
|
}
|
|
123
125
|
else if (hasNx) {
|
|
124
126
|
try {
|
|
125
|
-
const nxJson = JSON.parse(fs.readFileSync(path.join(dir,
|
|
127
|
+
const nxJson = JSON.parse(fs.readFileSync(path.join(dir, 'project.json'), 'utf-8'));
|
|
126
128
|
if (nxJson.name)
|
|
127
129
|
name = nxJson.name;
|
|
128
130
|
}
|
|
@@ -150,7 +152,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
|
|
|
150
152
|
// If patterns exist from pnpm-workspace.yaml, search those roots
|
|
151
153
|
if (patterns.length > 0) {
|
|
152
154
|
for (const pat of patterns) {
|
|
153
|
-
const cleanPat = pat.replace(/\/\*\*?$/,
|
|
155
|
+
const cleanPat = pat.replace(/\/\*\*?$/, '').replace(/\/\*$/, '');
|
|
154
156
|
const searchRoot = path.join(cwd, cleanPat);
|
|
155
157
|
if (fs.existsSync(searchRoot)) {
|
|
156
158
|
const stat = fs.statSync(searchRoot);
|
|
@@ -167,7 +169,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
|
|
|
167
169
|
}
|
|
168
170
|
// Fallback search in standard directories if nothing found
|
|
169
171
|
if (foundProjects.length === 0) {
|
|
170
|
-
for (const fallback of [
|
|
172
|
+
for (const fallback of ['modules', 'packages', 'apps', 'libs', 'features']) {
|
|
171
173
|
const fbPath = path.join(cwd, fallback);
|
|
172
174
|
if (fs.existsSync(fbPath)) {
|
|
173
175
|
search(fbPath, 1, 3);
|
|
@@ -183,16 +185,16 @@ export async function initCommand(options = {}) {
|
|
|
183
185
|
p.log.warn(chalk.yellow(`Config already exists at ${path.basename(existingConfig)}. Use --force to overwrite.`));
|
|
184
186
|
return;
|
|
185
187
|
}
|
|
186
|
-
p.intro(chalk.bold.cyan(
|
|
188
|
+
p.intro(chalk.bold.cyan('⨠TestSpectra Enterprise Scaffolder'));
|
|
187
189
|
// Detect monorepo environment
|
|
188
190
|
let isMonorepo = false;
|
|
189
191
|
let pnpmPatterns = [];
|
|
190
|
-
const pnpmWorkspacePath = path.join(cwd,
|
|
191
|
-
const nxJsonPath = path.join(cwd,
|
|
192
|
+
const pnpmWorkspacePath = path.join(cwd, 'pnpm-workspace.yaml');
|
|
193
|
+
const nxJsonPath = path.join(cwd, 'nx.json');
|
|
192
194
|
if (fs.existsSync(pnpmWorkspacePath)) {
|
|
193
195
|
isMonorepo = true;
|
|
194
196
|
try {
|
|
195
|
-
const content = fs.readFileSync(pnpmWorkspacePath,
|
|
197
|
+
const content = fs.readFileSync(pnpmWorkspacePath, 'utf-8');
|
|
196
198
|
pnpmPatterns = parsePnpmWorkspaceGlobs(content);
|
|
197
199
|
}
|
|
198
200
|
catch { }
|
|
@@ -204,28 +206,28 @@ export async function initCommand(options = {}) {
|
|
|
204
206
|
let selectedTemplate = options.template;
|
|
205
207
|
if (!selectedTemplate && !options.force) {
|
|
206
208
|
const templateChoice = await p.select({
|
|
207
|
-
message:
|
|
208
|
-
initialValue: isMonorepo ?
|
|
209
|
+
message: 'Select TestSpectra setup mode:',
|
|
210
|
+
initialValue: isMonorepo ? 'nx' : 'default',
|
|
209
211
|
options: [
|
|
210
212
|
{
|
|
211
|
-
label:
|
|
212
|
-
value:
|
|
213
|
-
hint:
|
|
213
|
+
label: 'Nx Monorepo (Recommended)',
|
|
214
|
+
value: 'nx',
|
|
215
|
+
hint: 'Interactive workspace feature discovery & centralized config',
|
|
214
216
|
},
|
|
215
217
|
{
|
|
216
|
-
label:
|
|
217
|
-
value:
|
|
218
|
-
hint:
|
|
218
|
+
label: 'Standard Project',
|
|
219
|
+
value: 'default',
|
|
220
|
+
hint: 'Standalone single-suite E2E testing project',
|
|
219
221
|
},
|
|
220
222
|
],
|
|
221
223
|
});
|
|
222
224
|
if (p.isCancel(templateChoice)) {
|
|
223
|
-
p.cancel(
|
|
225
|
+
p.cancel('Setup cancelled.');
|
|
224
226
|
return;
|
|
225
227
|
}
|
|
226
228
|
selectedTemplate = templateChoice;
|
|
227
229
|
}
|
|
228
|
-
const templateName = selectedTemplate || (isMonorepo ?
|
|
230
|
+
const templateName = selectedTemplate || (isMonorepo ? 'nx' : 'default');
|
|
229
231
|
// 1. Resolve template directory
|
|
230
232
|
const __filename = fileURLToPath(import.meta.url);
|
|
231
233
|
const __dirname = path.dirname(__filename);
|
|
@@ -241,14 +243,14 @@ export async function initCommand(options = {}) {
|
|
|
241
243
|
return;
|
|
242
244
|
}
|
|
243
245
|
// 2. Resolve CLI dependency version
|
|
244
|
-
let cliVersion =
|
|
246
|
+
let cliVersion = '^1.0.65';
|
|
245
247
|
try {
|
|
246
248
|
let pDir = __dirname;
|
|
247
249
|
while (pDir !== path.dirname(pDir)) {
|
|
248
|
-
const cand = path.join(pDir,
|
|
250
|
+
const cand = path.join(pDir, 'package.json');
|
|
249
251
|
if (fs.existsSync(cand)) {
|
|
250
|
-
const cliPkg = JSON.parse(fs.readFileSync(cand,
|
|
251
|
-
if (cliPkg.name ===
|
|
252
|
+
const cliPkg = JSON.parse(fs.readFileSync(cand, 'utf-8'));
|
|
253
|
+
if (cliPkg.name === '@testspectra/cli' && cliPkg.version) {
|
|
252
254
|
cliVersion = `^${cliPkg.version}`;
|
|
253
255
|
break;
|
|
254
256
|
}
|
|
@@ -258,11 +260,11 @@ export async function initCommand(options = {}) {
|
|
|
258
260
|
}
|
|
259
261
|
catch { }
|
|
260
262
|
const projectName = path.basename(cwd);
|
|
261
|
-
const cliDepVersion = cliVersion.startsWith(
|
|
263
|
+
const cliDepVersion = cliVersion.startsWith('^') ? cliVersion : `^${cliVersion}`;
|
|
262
264
|
// Helper: Copy directory contents recursively
|
|
263
265
|
function copyRecursive(src, dest, replacements = {}) {
|
|
264
266
|
const base = path.basename(src);
|
|
265
|
-
if (base ===
|
|
267
|
+
if (base === 'node_modules' || base === 'dist' || base === '.testspectra' || base === '.git') {
|
|
266
268
|
return;
|
|
267
269
|
}
|
|
268
270
|
const stat = fs.statSync(src);
|
|
@@ -279,26 +281,30 @@ export async function initCommand(options = {}) {
|
|
|
279
281
|
if (fs.existsSync(dest) && !options.force) {
|
|
280
282
|
return;
|
|
281
283
|
}
|
|
282
|
-
let content = fs.readFileSync(src,
|
|
284
|
+
let content = fs.readFileSync(src, 'utf-8');
|
|
283
285
|
for (const [k, v] of Object.entries(replacements)) {
|
|
284
286
|
content = content.replaceAll(k, v);
|
|
285
287
|
}
|
|
286
|
-
if (dest.endsWith(
|
|
288
|
+
if (dest.endsWith('package.json')) {
|
|
287
289
|
try {
|
|
288
290
|
const pkg = JSON.parse(content);
|
|
289
|
-
if (!pkg.name || pkg.name.startsWith(
|
|
291
|
+
if (!pkg.name || pkg.name.startsWith('@testspectra/template-') || pkg.name === 'spectra-monorepo') {
|
|
290
292
|
pkg.name = projectName;
|
|
291
293
|
}
|
|
292
294
|
if (pkg.devDependencies) {
|
|
293
295
|
for (const key of Object.keys(pkg.devDependencies)) {
|
|
294
|
-
if (key.startsWith(
|
|
296
|
+
if (key.startsWith('@testspectra/') ||
|
|
297
|
+
pkg.devDependencies[key] === 'workspace:*' ||
|
|
298
|
+
(typeof pkg.devDependencies[key] === 'string' && pkg.devDependencies[key].startsWith('link:'))) {
|
|
295
299
|
pkg.devDependencies[key] = cliDepVersion;
|
|
296
300
|
}
|
|
297
301
|
}
|
|
298
302
|
}
|
|
299
303
|
if (pkg.dependencies) {
|
|
300
304
|
for (const key of Object.keys(pkg.dependencies)) {
|
|
301
|
-
if (key.startsWith(
|
|
305
|
+
if (key.startsWith('@testspectra/') ||
|
|
306
|
+
pkg.dependencies[key] === 'workspace:*' ||
|
|
307
|
+
(typeof pkg.dependencies[key] === 'string' && pkg.dependencies[key].startsWith('link:'))) {
|
|
302
308
|
pkg.dependencies[key] = cliDepVersion;
|
|
303
309
|
}
|
|
304
310
|
}
|
|
@@ -307,11 +313,11 @@ export async function initCommand(options = {}) {
|
|
|
307
313
|
}
|
|
308
314
|
catch { }
|
|
309
315
|
}
|
|
310
|
-
fs.writeFileSync(dest, content,
|
|
316
|
+
fs.writeFileSync(dest, content, 'utf-8');
|
|
311
317
|
}
|
|
312
318
|
}
|
|
313
319
|
// --- MONOREPO INTERACTIVE FLOW ---
|
|
314
|
-
if (templateName ===
|
|
320
|
+
if (templateName === 'nx') {
|
|
315
321
|
let chosenProjects = [];
|
|
316
322
|
if (options.force) {
|
|
317
323
|
chosenProjects = detectedProjects.map((p) => p.relPath);
|
|
@@ -319,86 +325,81 @@ export async function initCommand(options = {}) {
|
|
|
319
325
|
else if (detectedProjects.length > 0) {
|
|
320
326
|
p.log.step(chalk.cyan(`Monorepo Auto-Discovery: Found ${detectedProjects.length} workspace feature package(s)`));
|
|
321
327
|
const projectChoices = await p.multiselect({
|
|
322
|
-
message:
|
|
328
|
+
message: 'Select workspace features to initialize with TestSpectra:',
|
|
323
329
|
options: detectedProjects.map((proj) => ({
|
|
324
330
|
value: proj.relPath,
|
|
325
331
|
label: `${proj.relPath} (${proj.name})`,
|
|
326
|
-
hint: proj.hasNxProject ?
|
|
332
|
+
hint: proj.hasNxProject ? 'Nx project.json' : 'package.json',
|
|
327
333
|
})),
|
|
328
334
|
initialValues: detectedProjects.map((p) => p.relPath),
|
|
329
335
|
required: true,
|
|
330
336
|
});
|
|
331
337
|
if (p.isCancel(projectChoices)) {
|
|
332
|
-
p.cancel(
|
|
338
|
+
p.cancel('Setup cancelled.');
|
|
333
339
|
return;
|
|
334
340
|
}
|
|
335
341
|
chosenProjects = projectChoices;
|
|
336
342
|
}
|
|
337
343
|
else {
|
|
338
344
|
// No packages auto-detected ā ask user to manually enter feature names
|
|
339
|
-
p.log.warn(chalk.yellow(
|
|
345
|
+
p.log.warn(chalk.yellow('No feature packages found in workspace. You can enter feature names to scaffold.'));
|
|
340
346
|
const featureNamesInput = await p.text({
|
|
341
|
-
message:
|
|
342
|
-
placeholder:
|
|
343
|
-
defaultValue:
|
|
347
|
+
message: 'Enter feature module names (comma-separated), or leave blank to skip:',
|
|
348
|
+
placeholder: 'playground, matchers, navigation',
|
|
349
|
+
defaultValue: '',
|
|
344
350
|
});
|
|
345
351
|
if (p.isCancel(featureNamesInput)) {
|
|
346
|
-
p.cancel(
|
|
352
|
+
p.cancel('Setup cancelled.');
|
|
347
353
|
return;
|
|
348
354
|
}
|
|
349
355
|
const raw = featureNamesInput.trim();
|
|
350
356
|
if (raw) {
|
|
351
357
|
// Build relPaths from feature names using a sensible default location
|
|
352
358
|
const featureBaseInput = await p.text({
|
|
353
|
-
message:
|
|
354
|
-
placeholder:
|
|
355
|
-
defaultValue:
|
|
359
|
+
message: 'Enter base directory for feature modules:',
|
|
360
|
+
placeholder: 'packages',
|
|
361
|
+
defaultValue: 'packages',
|
|
356
362
|
});
|
|
357
363
|
if (p.isCancel(featureBaseInput)) {
|
|
358
|
-
p.cancel(
|
|
364
|
+
p.cancel('Setup cancelled.');
|
|
359
365
|
return;
|
|
360
366
|
}
|
|
361
|
-
const featureBase = featureBaseInput.trim() ||
|
|
362
|
-
chosenProjects = raw
|
|
367
|
+
const featureBase = featureBaseInput.trim() || 'packages';
|
|
368
|
+
chosenProjects = raw
|
|
369
|
+
.split(',')
|
|
370
|
+
.map((n) => `${featureBase}/${n.trim()}`)
|
|
371
|
+
.filter(Boolean);
|
|
363
372
|
}
|
|
364
373
|
}
|
|
365
|
-
let e2eFolderName =
|
|
366
|
-
let sharedTestingRelPath =
|
|
374
|
+
let e2eFolderName = 'e2e';
|
|
375
|
+
let sharedTestingRelPath = 'shared/testing';
|
|
367
376
|
if (!options.force) {
|
|
368
377
|
const e2eFolderNameInput = await p.text({
|
|
369
|
-
message:
|
|
370
|
-
placeholder:
|
|
371
|
-
defaultValue:
|
|
378
|
+
message: 'Enter E2E test folder name for each selected feature:',
|
|
379
|
+
placeholder: 'e2e',
|
|
380
|
+
defaultValue: 'e2e',
|
|
372
381
|
});
|
|
373
382
|
if (p.isCancel(e2eFolderNameInput)) {
|
|
374
|
-
p.cancel(
|
|
383
|
+
p.cancel('Setup cancelled.');
|
|
375
384
|
return;
|
|
376
385
|
}
|
|
377
386
|
const sharedTestingPathInput = await p.text({
|
|
378
|
-
message:
|
|
379
|
-
placeholder:
|
|
380
|
-
defaultValue:
|
|
387
|
+
message: 'Enter path for Shared Testing library (POMs, Steps, Actions, Fixtures):',
|
|
388
|
+
placeholder: 'shared/testing',
|
|
389
|
+
defaultValue: 'shared/testing',
|
|
381
390
|
});
|
|
382
391
|
if (p.isCancel(sharedTestingPathInput)) {
|
|
383
|
-
p.cancel(
|
|
392
|
+
p.cancel('Setup cancelled.');
|
|
384
393
|
return;
|
|
385
394
|
}
|
|
386
|
-
e2eFolderName = e2eFolderNameInput.trim() ||
|
|
387
|
-
sharedTestingRelPath = sharedTestingPathInput.trim() ||
|
|
395
|
+
e2eFolderName = e2eFolderNameInput.trim() || 'e2e';
|
|
396
|
+
sharedTestingRelPath = sharedTestingPathInput.trim() || 'shared/testing';
|
|
388
397
|
}
|
|
389
398
|
const sharedTestingAbsPath = path.join(cwd, sharedTestingRelPath);
|
|
390
399
|
const s = p.spinner();
|
|
391
|
-
s.start(
|
|
400
|
+
s.start('Scaffolding distributed monorepo structure and ambient types...');
|
|
392
401
|
// 1. Centralized Root Configs & Orchestration
|
|
393
|
-
const rootConfigFiles = [
|
|
394
|
-
"spectra.config.ts",
|
|
395
|
-
"tsconfig.spectra.json",
|
|
396
|
-
"tsconfig.spectra.shared.json",
|
|
397
|
-
"tsconfig.spectra.web.json",
|
|
398
|
-
"tsconfig.spectra.android.json",
|
|
399
|
-
"tsconfig.spectra.ios.json",
|
|
400
|
-
"nx.json",
|
|
401
|
-
];
|
|
402
|
+
const rootConfigFiles = ['spectra.config.ts', 'nx.json'];
|
|
402
403
|
for (const cf of rootConfigFiles) {
|
|
403
404
|
const src = path.join(templateDir, cf);
|
|
404
405
|
const dest = path.join(cwd, cf);
|
|
@@ -406,31 +407,33 @@ export async function initCommand(options = {}) {
|
|
|
406
407
|
fs.copyFileSync(src, dest);
|
|
407
408
|
}
|
|
408
409
|
}
|
|
410
|
+
// Write tsconfig.json and auto-generate .testspectra/tsconfig.json and sub-configs
|
|
411
|
+
TsConfigGenerator.scaffoldConfigs(cwd, options.force);
|
|
409
412
|
// Ensure root package.json has TestSpectra devDependencies
|
|
410
|
-
const rootPkgPath = path.join(cwd,
|
|
413
|
+
const rootPkgPath = path.join(cwd, 'package.json');
|
|
411
414
|
if (!fs.existsSync(rootPkgPath)) {
|
|
412
|
-
const templatePkgSrc = path.join(templateDir,
|
|
415
|
+
const templatePkgSrc = path.join(templateDir, 'package.json');
|
|
413
416
|
if (fs.existsSync(templatePkgSrc)) {
|
|
414
417
|
try {
|
|
415
|
-
const tPkg = JSON.parse(fs.readFileSync(templatePkgSrc,
|
|
416
|
-
if (!tPkg.name || tPkg.name.startsWith(
|
|
418
|
+
const tPkg = JSON.parse(fs.readFileSync(templatePkgSrc, 'utf-8'));
|
|
419
|
+
if (!tPkg.name || tPkg.name.startsWith('@testspectra/template-') || tPkg.name === 'spectra-monorepo') {
|
|
417
420
|
tPkg.name = projectName;
|
|
418
421
|
}
|
|
419
422
|
if (tPkg.devDependencies) {
|
|
420
423
|
for (const key of Object.keys(tPkg.devDependencies)) {
|
|
421
|
-
if (key.startsWith(
|
|
424
|
+
if (key.startsWith('@testspectra/') || tPkg.devDependencies[key] === 'workspace:*') {
|
|
422
425
|
tPkg.devDependencies[key] = cliDepVersion;
|
|
423
426
|
}
|
|
424
427
|
}
|
|
425
428
|
}
|
|
426
429
|
if (tPkg.dependencies) {
|
|
427
430
|
for (const key of Object.keys(tPkg.dependencies)) {
|
|
428
|
-
if (key.startsWith(
|
|
431
|
+
if (key.startsWith('@testspectra/') || tPkg.dependencies[key] === 'workspace:*') {
|
|
429
432
|
tPkg.dependencies[key] = cliDepVersion;
|
|
430
433
|
}
|
|
431
434
|
}
|
|
432
435
|
}
|
|
433
|
-
fs.writeFileSync(rootPkgPath, JSON.stringify(tPkg, null, 2),
|
|
436
|
+
fs.writeFileSync(rootPkgPath, JSON.stringify(tPkg, null, 2), 'utf-8');
|
|
434
437
|
}
|
|
435
438
|
catch {
|
|
436
439
|
fs.copyFileSync(templatePkgSrc, rootPkgPath);
|
|
@@ -439,107 +442,116 @@ export async function initCommand(options = {}) {
|
|
|
439
442
|
}
|
|
440
443
|
else {
|
|
441
444
|
try {
|
|
442
|
-
const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath,
|
|
445
|
+
const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, 'utf-8'));
|
|
443
446
|
if (!rootPkg.devDependencies)
|
|
444
447
|
rootPkg.devDependencies = {};
|
|
445
|
-
if (!rootPkg.devDependencies[
|
|
446
|
-
rootPkg.devDependencies[
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
rootPkg.devDependencies[
|
|
453
|
-
|
|
454
|
-
rootPkg.devDependencies[
|
|
455
|
-
if (!rootPkg.devDependencies[
|
|
456
|
-
rootPkg.devDependencies[
|
|
457
|
-
if (!rootPkg.devDependencies[
|
|
458
|
-
rootPkg.devDependencies[
|
|
459
|
-
if (!rootPkg.devDependencies[
|
|
460
|
-
rootPkg.devDependencies[
|
|
461
|
-
if (!rootPkg.devDependencies[
|
|
462
|
-
rootPkg.devDependencies[
|
|
463
|
-
if (!rootPkg.devDependencies[
|
|
464
|
-
rootPkg.devDependencies[
|
|
465
|
-
if (!rootPkg.devDependencies[
|
|
466
|
-
rootPkg.devDependencies[
|
|
467
|
-
|
|
448
|
+
if (!rootPkg.devDependencies['@testspectra/cli'] ||
|
|
449
|
+
rootPkg.devDependencies['@testspectra/cli'] === 'workspace:*' ||
|
|
450
|
+
(typeof rootPkg.devDependencies['@testspectra/cli'] === 'string' &&
|
|
451
|
+
rootPkg.devDependencies['@testspectra/cli'].startsWith('link:')))
|
|
452
|
+
rootPkg.devDependencies['@testspectra/cli'] = cliDepVersion;
|
|
453
|
+
if (!rootPkg.devDependencies['@testspectra/matchers'] ||
|
|
454
|
+
rootPkg.devDependencies['@testspectra/matchers'] === 'workspace:*' ||
|
|
455
|
+
(typeof rootPkg.devDependencies['@testspectra/matchers'] === 'string' &&
|
|
456
|
+
rootPkg.devDependencies['@testspectra/matchers'].startsWith('link:')))
|
|
457
|
+
rootPkg.devDependencies['@testspectra/matchers'] = cliDepVersion;
|
|
458
|
+
if (!rootPkg.devDependencies['typescript'])
|
|
459
|
+
rootPkg.devDependencies['typescript'] = '^5.4.5';
|
|
460
|
+
if (!rootPkg.devDependencies['@wdio/cli'])
|
|
461
|
+
rootPkg.devDependencies['@wdio/cli'] = '^9.2.8';
|
|
462
|
+
if (!rootPkg.devDependencies['@wdio/local-runner'])
|
|
463
|
+
rootPkg.devDependencies['@wdio/local-runner'] = '^9.2.8';
|
|
464
|
+
if (!rootPkg.devDependencies['@wdio/spec-reporter'])
|
|
465
|
+
rootPkg.devDependencies['@wdio/spec-reporter'] = '^9.2.8';
|
|
466
|
+
if (!rootPkg.devDependencies['@wdio/devtools-service'])
|
|
467
|
+
rootPkg.devDependencies['@wdio/devtools-service'] = '^8.46.0';
|
|
468
|
+
if (!rootPkg.devDependencies['@wdio/globals'])
|
|
469
|
+
rootPkg.devDependencies['@wdio/globals'] = '^9.2.8';
|
|
470
|
+
if (!rootPkg.devDependencies['@wdio/mocha-framework'])
|
|
471
|
+
rootPkg.devDependencies['@wdio/mocha-framework'] = '^9.2.8';
|
|
472
|
+
if (!rootPkg.devDependencies['@types/node'])
|
|
473
|
+
rootPkg.devDependencies['@types/node'] = '^20.14.0';
|
|
474
|
+
if (!rootPkg.devDependencies['webdriverio'])
|
|
475
|
+
rootPkg.devDependencies['webdriverio'] = '^9.2.8';
|
|
476
|
+
if (!rootPkg.scripts)
|
|
477
|
+
rootPkg.scripts = {};
|
|
478
|
+
if (!rootPkg.scripts['postinstall'])
|
|
479
|
+
rootPkg.scripts['postinstall'] = 'spectra sync-types';
|
|
480
|
+
if (!rootPkg.scripts['type-check'])
|
|
481
|
+
rootPkg.scripts['type-check'] = 'tsc -b';
|
|
482
|
+
fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2), 'utf-8');
|
|
468
483
|
}
|
|
469
484
|
catch { }
|
|
470
485
|
}
|
|
471
486
|
// 2. Centralized Root .testspectra folder for cache/logs
|
|
472
|
-
const rootTestDataDir = path.join(cwd,
|
|
487
|
+
const rootTestDataDir = path.join(cwd, '.testspectra');
|
|
473
488
|
if (!fs.existsSync(rootTestDataDir)) {
|
|
474
489
|
fs.mkdirSync(rootTestDataDir, { recursive: true });
|
|
475
490
|
}
|
|
476
491
|
// 3. Centralized Shared Testing Library
|
|
477
|
-
const sharedTestingSrc = path.join(templateDir,
|
|
492
|
+
const sharedTestingSrc = path.join(templateDir, 'shared/testing');
|
|
478
493
|
if (fs.existsSync(sharedTestingSrc)) {
|
|
479
494
|
copyRecursive(sharedTestingSrc, sharedTestingAbsPath);
|
|
480
495
|
}
|
|
481
496
|
// 4. Distribute E2E Folders to Selected Feature Modules
|
|
482
|
-
const sampleE2eSrc = path.join(templateDir,
|
|
497
|
+
const sampleE2eSrc = path.join(templateDir, 'packages/playground/e2e');
|
|
483
498
|
const featureE2eDirs = [];
|
|
484
499
|
for (const projRel of chosenProjects) {
|
|
485
500
|
const targetE2eDir = path.join(cwd, projRel, e2eFolderName);
|
|
486
501
|
const featureName = path.basename(projRel);
|
|
487
502
|
const e2eProjectName = `${featureName}-${e2eFolderName}`;
|
|
488
503
|
copyRecursive(sampleE2eSrc, targetE2eDir, {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
504
|
+
'playground-e2e': e2eProjectName,
|
|
505
|
+
'packages/playground/e2e': path.relative(cwd, targetE2eDir).replace(/\\/g, '/'),
|
|
506
|
+
'scope:playground': `scope:${featureName}`,
|
|
492
507
|
});
|
|
493
508
|
// Adjust project.json
|
|
494
|
-
const projJsonPath = path.join(targetE2eDir,
|
|
509
|
+
const projJsonPath = path.join(targetE2eDir, 'project.json');
|
|
495
510
|
if (fs.existsSync(projJsonPath)) {
|
|
496
511
|
try {
|
|
497
|
-
const pObj = JSON.parse(fs.readFileSync(projJsonPath,
|
|
512
|
+
const pObj = JSON.parse(fs.readFileSync(projJsonPath, 'utf-8'));
|
|
498
513
|
pObj.name = e2eProjectName;
|
|
499
|
-
pObj.sourceRoot = path.relative(cwd, targetE2eDir).replace(/\\/g,
|
|
500
|
-
pObj.targets.e2e.options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g,
|
|
501
|
-
pObj.targets[
|
|
502
|
-
pObj.tags = [`scope:${featureName}`,
|
|
503
|
-
fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2),
|
|
514
|
+
pObj.sourceRoot = path.relative(cwd, targetE2eDir).replace(/\\/g, '/');
|
|
515
|
+
pObj.targets.e2e.options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g, '/');
|
|
516
|
+
pObj.targets['type-check'].options.cwd = '.';
|
|
517
|
+
pObj.tags = [`scope:${featureName}`, 'type:e2e'];
|
|
518
|
+
fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2), 'utf-8');
|
|
504
519
|
}
|
|
505
520
|
catch { }
|
|
506
521
|
}
|
|
507
522
|
// Ensure no local spectra.config.ts (centralized in root)
|
|
508
|
-
const localConfig = path.join(targetE2eDir,
|
|
523
|
+
const localConfig = path.join(targetE2eDir, 'spectra.config.ts');
|
|
509
524
|
if (fs.existsSync(localConfig))
|
|
510
525
|
fs.unlinkSync(localConfig);
|
|
511
526
|
featureE2eDirs.push(targetE2eDir);
|
|
512
527
|
}
|
|
513
528
|
// 5. Ensure pnpm-workspace.yaml includes shared library (feature modules are already part of existing workspace globs)
|
|
514
|
-
const pnpmWorkspaceFilePath = path.join(cwd,
|
|
529
|
+
const pnpmWorkspaceFilePath = path.join(cwd, 'pnpm-workspace.yaml');
|
|
515
530
|
ensurePnpmWorkspaceIncludes(pnpmWorkspaceFilePath, sharedTestingRelPath);
|
|
516
|
-
// 6. Update Root Solution tsconfig.json to reference tsconfig.
|
|
517
|
-
const rootTsConfigPath = path.join(cwd,
|
|
531
|
+
// 6. Update Root Solution tsconfig.json to reference .testspectra/tsconfig.json
|
|
532
|
+
const rootTsConfigPath = path.join(cwd, 'tsconfig.json');
|
|
518
533
|
let rootTsConfig = { files: [], references: [] };
|
|
519
534
|
if (fs.existsSync(rootTsConfigPath)) {
|
|
520
535
|
try {
|
|
521
|
-
rootTsConfig = JSON.parse(fs.readFileSync(rootTsConfigPath,
|
|
536
|
+
rootTsConfig = JSON.parse(fs.readFileSync(rootTsConfigPath, 'utf-8'));
|
|
522
537
|
if (!rootTsConfig.references)
|
|
523
538
|
rootTsConfig.references = [];
|
|
524
|
-
|
|
525
|
-
|
|
539
|
+
rootTsConfig.references = rootTsConfig.references.filter((r) => r.path !== './tsconfig.spectra.json');
|
|
540
|
+
if (!rootTsConfig.references.some((r) => r.path === './.testspectra/tsconfig.json')) {
|
|
541
|
+
rootTsConfig.references.push({ path: './.testspectra/tsconfig.json' });
|
|
526
542
|
}
|
|
527
543
|
}
|
|
528
544
|
catch {
|
|
529
|
-
rootTsConfig = { files: [], references: [{ path:
|
|
545
|
+
rootTsConfig = { files: [], references: [{ path: './.testspectra/tsconfig.json' }] };
|
|
530
546
|
}
|
|
531
547
|
}
|
|
532
548
|
else {
|
|
533
|
-
rootTsConfig = { files: [], references: [{ path:
|
|
549
|
+
rootTsConfig = { files: [], references: [{ path: './.testspectra/tsconfig.json' }] };
|
|
534
550
|
}
|
|
535
|
-
fs.writeFileSync(rootTsConfigPath, JSON.stringify(rootTsConfig, null, 2),
|
|
551
|
+
fs.writeFileSync(rootTsConfigPath, JSON.stringify(rootTsConfig, null, 2), 'utf-8');
|
|
536
552
|
// 7. Generate Ambient Types in ROOT only
|
|
537
553
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
538
554
|
ensureVsCodeExtensions(cwd);
|
|
539
|
-
const sharedPoLine = `${sharedTestingRelPath}/page-objects`.padEnd(28, " ");
|
|
540
|
-
const sharedStepsLine = `${sharedTestingRelPath}/steps`.padEnd(28, " ");
|
|
541
|
-
const sharedActLine = `${sharedTestingRelPath}/actions`.padEnd(28, " ");
|
|
542
|
-
const sharedFixLine = `${sharedTestingRelPath}/fixtures`.padEnd(28, " ");
|
|
543
555
|
const archDocContent = `# TestSpectra Enterprise Monorepo Architecture
|
|
544
556
|
|
|
545
557
|
This workspace uses TestSpectra's **"Centralized Configuration, Distributed Implementation"** model for automated cross-platform testing.
|
|
@@ -550,14 +562,10 @@ This workspace uses TestSpectra's **"Centralized Configuration, Distributed Impl
|
|
|
550
562
|
|
|
551
563
|
### Centralized Root Elements
|
|
552
564
|
- \`./spectra.config.ts\`: Single source of truth for runtime configurations (Base URL, Appium devices, browser targets, timeouts).
|
|
553
|
-
- \`./.testspectra/\`: Local test project cache
|
|
554
|
-
- \`./tsconfig.spectra.json\`: Solution TypeScript entry point equipped with TestSpectra language service plugin.
|
|
555
|
-
- \`./tsconfig.spectra.web.json\`: Strongly-typed composite configuration for Web platform tests.
|
|
556
|
-
- \`./tsconfig.spectra.android.json\`: Strongly-typed composite configuration for Android platform tests.
|
|
557
|
-
- \`./tsconfig.spectra.ios.json\`: Strongly-typed composite configuration for iOS platform tests.
|
|
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/\`.
|
|
558
566
|
- \`./nx.json\`: Nx target defaults with execution caching and inputs.
|
|
559
567
|
- \`./pnpm-workspace.yaml\`: Monorepo package glob definitions.
|
|
560
|
-
- \`./tsconfig.json\`: Solution-style TypeScript orchestrator
|
|
568
|
+
- \`./tsconfig.json\`: Solution-style TypeScript orchestrator referencing \`./.testspectra/tsconfig.json\`.
|
|
561
569
|
|
|
562
570
|
### Centralized Shared Testing Library (\`./${sharedTestingRelPath}\`)
|
|
563
571
|
Provides cross-feature reusable test entities with **100% zero-import global resolution**:
|
|
@@ -567,7 +575,8 @@ Provides cross-feature reusable test entities with **100% zero-import global res
|
|
|
567
575
|
- \`fixtures/\`: Common test data and environment configurations (e.g. \`Fixture.appConfig\`).
|
|
568
576
|
|
|
569
577
|
### Distributed Feature E2E Modules (${featureE2eDirs.length} Features)
|
|
570
|
-
${featureE2eDirs
|
|
578
|
+
${featureE2eDirs
|
|
579
|
+
.map((d) => {
|
|
571
580
|
const rel = path.relative(cwd, d);
|
|
572
581
|
const name = path.basename(path.dirname(d));
|
|
573
582
|
const e2eName = `${name}-${e2eFolderName}`;
|
|
@@ -575,7 +584,8 @@ ${featureE2eDirs.map((d) => {
|
|
|
575
584
|
- \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
|
|
576
585
|
- \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
|
|
577
586
|
- \`project.json\`: Nx targets (\`e2e\`, \`type-check\`) supporting configurations (\`--configuration=android\`, \`--configuration=ios\`, \`--configuration=headless\`).`;
|
|
578
|
-
})
|
|
587
|
+
})
|
|
588
|
+
.join('\n')}
|
|
579
589
|
|
|
580
590
|
---
|
|
581
591
|
|
|
@@ -644,7 +654,7 @@ pnpm nx affected -t e2e
|
|
|
644
654
|
|
|
645
655
|
\`\`\`bash
|
|
646
656
|
# Navigate to the feature e2e folder
|
|
647
|
-
cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g,
|
|
657
|
+
cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/') : `modules/auth/${e2eFolderName}`}
|
|
648
658
|
|
|
649
659
|
# Run all specs in this feature
|
|
650
660
|
pnpm spectra run
|
|
@@ -672,12 +682,12 @@ pnpm type-check
|
|
|
672
682
|
# or: npx tsc -b
|
|
673
683
|
\`\`\`
|
|
674
684
|
`;
|
|
675
|
-
fs.writeFileSync(path.join(cwd,
|
|
685
|
+
fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), archDocContent, 'utf-8');
|
|
676
686
|
// 8. Ensure root .gitignore ignores .testspectra/ and *.tsbuildinfo
|
|
677
|
-
const rootGitignorePath = path.join(cwd,
|
|
678
|
-
const ignoreEntries = [
|
|
687
|
+
const rootGitignorePath = path.join(cwd, '.gitignore');
|
|
688
|
+
const ignoreEntries = ['.testspectra/', '*.tsbuildinfo', '.nx/cache'];
|
|
679
689
|
if (fs.existsSync(rootGitignorePath)) {
|
|
680
|
-
let giContent = fs.readFileSync(rootGitignorePath,
|
|
690
|
+
let giContent = fs.readFileSync(rootGitignorePath, 'utf-8');
|
|
681
691
|
let added = false;
|
|
682
692
|
for (const entry of ignoreEntries) {
|
|
683
693
|
if (!giContent.includes(entry)) {
|
|
@@ -686,42 +696,42 @@ pnpm type-check
|
|
|
686
696
|
}
|
|
687
697
|
}
|
|
688
698
|
if (added) {
|
|
689
|
-
fs.writeFileSync(rootGitignorePath, giContent.trim() +
|
|
699
|
+
fs.writeFileSync(rootGitignorePath, giContent.trim() + '\n', 'utf-8');
|
|
690
700
|
}
|
|
691
701
|
}
|
|
692
702
|
else {
|
|
693
|
-
fs.writeFileSync(rootGitignorePath, `node_modules/\ndist/\n${ignoreEntries.join(
|
|
703
|
+
fs.writeFileSync(rootGitignorePath, `node_modules/\ndist/\n${ignoreEntries.join('\n')}\n`, 'utf-8');
|
|
694
704
|
}
|
|
695
705
|
// 9. Safely append to root README.md (do not overwrite existing content)
|
|
696
|
-
const rootReadmePath = path.join(cwd,
|
|
706
|
+
const rootReadmePath = path.join(cwd, 'README.md');
|
|
697
707
|
const archSection = `\n## E2E Testing Architecture (TestSpectra)\n\nThis workspace uses TestSpectra's **Centralized Configuration, Distributed Implementation** architecture.\nDetailed architecture, folder map, and zero-import mechanics are documented in: \nš **[ARCHITECTURE.md](./ARCHITECTURE.md)**\n\n- **Run all E2E tests**: \`pnpm nx run-many -t e2e\`\n- **Run affected E2E tests**: \`pnpm nx affected -t e2e\`\n- **Type-check E2E suites**: \`pnpm type-check\`\n`;
|
|
698
708
|
if (fs.existsSync(rootReadmePath)) {
|
|
699
|
-
const existing = fs.readFileSync(rootReadmePath,
|
|
700
|
-
if (!existing.includes(
|
|
701
|
-
fs.appendFileSync(rootReadmePath, archSection,
|
|
709
|
+
const existing = fs.readFileSync(rootReadmePath, 'utf-8');
|
|
710
|
+
if (!existing.includes('ARCHITECTURE.md')) {
|
|
711
|
+
fs.appendFileSync(rootReadmePath, archSection, 'utf-8');
|
|
702
712
|
}
|
|
703
713
|
}
|
|
704
714
|
else {
|
|
705
715
|
const readmeContent = `# ${projectName}\n\nAutomated cross-platform E2E testing powered by [TestSpectra](https://github.com/testspectra).\n${archSection}`;
|
|
706
|
-
fs.writeFileSync(rootReadmePath, readmeContent,
|
|
716
|
+
fs.writeFileSync(rootReadmePath, readmeContent, 'utf-8');
|
|
707
717
|
}
|
|
708
|
-
s.stop(
|
|
709
|
-
p.log.success(chalk.green(
|
|
710
|
-
p.log.success(chalk.green(
|
|
718
|
+
s.stop('Monorepo scaffolding complete!');
|
|
719
|
+
p.log.success(chalk.green('Centralized Configuration: ./spectra.config.ts'));
|
|
720
|
+
p.log.success(chalk.green('Centralized App Data/Cache: ./.testspectra/'));
|
|
711
721
|
p.log.success(chalk.green(`Shared Testing Library: ./${sharedTestingRelPath}`));
|
|
712
|
-
p.log.success(chalk.green(
|
|
713
|
-
p.log.message(chalk.cyan(`Initialized Features (${featureE2eDirs.length}):\n${featureE2eDirs.map((d) => ` - ./${path.relative(cwd, d)}`).join(
|
|
714
|
-
p.outro(chalk.bold.green(
|
|
722
|
+
p.log.success(chalk.green('Architecture Documentation: ./ARCHITECTURE.md'));
|
|
723
|
+
p.log.message(chalk.cyan(`Initialized Features (${featureE2eDirs.length}):\n${featureE2eDirs.map((d) => ` - ./${path.relative(cwd, d)}`).join('\n')}`));
|
|
724
|
+
p.outro(chalk.bold.green('š Enterprise Nx Monorepo ready for testing!'));
|
|
715
725
|
return;
|
|
716
726
|
}
|
|
717
727
|
// --- STANDALONE / DEFAULT PROJECT FLOW ---
|
|
718
728
|
const s = p.spinner();
|
|
719
|
-
s.start(
|
|
729
|
+
s.start('Scaffolding standalone TestSpectra project...');
|
|
720
730
|
copyRecursive(templateDir, cwd);
|
|
721
|
-
const standaloneWsPath = path.join(cwd,
|
|
731
|
+
const standaloneWsPath = path.join(cwd, 'pnpm-workspace.yaml');
|
|
722
732
|
if (!fs.existsSync(standaloneWsPath) || options.force) {
|
|
723
733
|
const pnpmWsContent = `allowBuilds:\n chromedriver: true\n edgedriver: true\n geckodriver: true\n`;
|
|
724
|
-
fs.writeFileSync(standaloneWsPath, pnpmWsContent,
|
|
734
|
+
fs.writeFileSync(standaloneWsPath, pnpmWsContent, 'utf-8');
|
|
725
735
|
}
|
|
726
736
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
727
737
|
ensureVsCodeExtensions(cwd);
|
|
@@ -753,22 +763,22 @@ pnpm spectra run --target ios
|
|
|
753
763
|
pnpm type-check
|
|
754
764
|
\`\`\`
|
|
755
765
|
`;
|
|
756
|
-
fs.writeFileSync(path.join(cwd,
|
|
766
|
+
fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), standaloneArchDoc, 'utf-8');
|
|
757
767
|
// Safely update root README.md (do not overwrite existing content)
|
|
758
|
-
const standaloneReadmePath = path.join(cwd,
|
|
768
|
+
const standaloneReadmePath = path.join(cwd, 'README.md');
|
|
759
769
|
const standaloneArchSection = `\n## E2E Testing Architecture (TestSpectra)\n\nDetailed test architecture and folder map are documented in: \nš **[ARCHITECTURE.md](./ARCHITECTURE.md)**\n\n- **Run tests**: \`pnpm test\`\n- **Type-check**: \`pnpm type-check\`\n`;
|
|
760
770
|
if (fs.existsSync(standaloneReadmePath)) {
|
|
761
|
-
const existing = fs.readFileSync(standaloneReadmePath,
|
|
762
|
-
if (!existing.includes(
|
|
763
|
-
fs.appendFileSync(standaloneReadmePath, standaloneArchSection,
|
|
771
|
+
const existing = fs.readFileSync(standaloneReadmePath, 'utf-8');
|
|
772
|
+
if (!existing.includes('ARCHITECTURE.md')) {
|
|
773
|
+
fs.appendFileSync(standaloneReadmePath, standaloneArchSection, 'utf-8');
|
|
764
774
|
}
|
|
765
775
|
}
|
|
766
776
|
else {
|
|
767
777
|
const standaloneReadme = `# ${projectName}\n\nTestSpectra automated testing project.\n${standaloneArchSection}`;
|
|
768
|
-
fs.writeFileSync(standaloneReadmePath, standaloneReadme,
|
|
778
|
+
fs.writeFileSync(standaloneReadmePath, standaloneReadme, 'utf-8');
|
|
769
779
|
}
|
|
770
|
-
s.stop(
|
|
771
|
-
p.log.success(chalk.green(
|
|
772
|
-
p.log.success(chalk.green(
|
|
773
|
-
p.outro(chalk.bold.green(
|
|
780
|
+
s.stop('Standalone project ready!');
|
|
781
|
+
p.log.success(chalk.green('Generated ambient multi-platform types in .testspectra/types/'));
|
|
782
|
+
p.log.success(chalk.green('Architecture documentation created at ./ARCHITECTURE.md'));
|
|
783
|
+
p.outro(chalk.bold.green('š Project initialized successfully!'));
|
|
774
784
|
}
|