@testspectra/cli 1.0.16 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/init.js +273 -58
- package/dist/commands/run.js +11 -1
- package/dist/config/loader.js +11 -4
- package/dist/types/generator.d.ts +2 -1
- package/dist/types/generator.js +108 -27
- package/package.json +1 -1
- package/templates/nx/modules/auth/e2e/.testspectra/types/android.d.ts +3 -0
- package/templates/nx/modules/auth/e2e/.testspectra/types/common.d.ts +3 -0
- package/templates/nx/modules/auth/e2e/.testspectra/types/fixtures.d.ts +1 -0
- package/templates/nx/modules/auth/e2e/.testspectra/types/ios.d.ts +3 -0
- package/templates/nx/modules/auth/e2e/.testspectra/types/mobile.d.ts +3 -0
- package/templates/nx/modules/auth/e2e/.testspectra/types/web.d.ts +3 -0
- package/templates/nx/modules/auth/e2e/package.json +1 -0
- package/templates/nx/modules/auth/e2e/specs/Auth/TC-AUTH-01/web.test.ts +10 -4
- package/templates/nx/modules/auth/e2e/tsconfig.android.tsbuildinfo +1 -1
- package/templates/nx/modules/auth/e2e/tsconfig.ios.tsbuildinfo +1 -1
- package/templates/nx/modules/auth/e2e/tsconfig.web.tsbuildinfo +1 -1
- package/templates/nx/modules/catalog/e2e/.testspectra/types/android.d.ts +3 -0
- package/templates/nx/modules/catalog/e2e/.testspectra/types/common.d.ts +3 -0
- package/templates/nx/modules/catalog/e2e/.testspectra/types/fixtures.d.ts +1 -0
- package/templates/nx/modules/catalog/e2e/.testspectra/types/ios.d.ts +3 -0
- package/templates/nx/modules/catalog/e2e/.testspectra/types/mobile.d.ts +3 -0
- package/templates/nx/modules/catalog/e2e/.testspectra/types/web.d.ts +3 -0
- package/templates/nx/modules/catalog/e2e/tsconfig.android.tsbuildinfo +1 -1
- package/templates/nx/modules/catalog/e2e/tsconfig.ios.tsbuildinfo +1 -1
- package/templates/nx/modules/catalog/e2e/tsconfig.web.tsbuildinfo +1 -1
- package/templates/nx/node_modules/.svelte2tsx-language-server-files/svelte-native-jsx.d.ts +32 -0
- package/templates/nx/node_modules/.svelte2tsx-language-server-files/svelte-shims-v4.d.ts +290 -0
- package/templates/nx/shared/testing/actions/dismissBanner/common.action.ts +6 -0
- package/templates/nx/shared/testing/fixtures/appConfig.json +4 -0
- package/templates/nx/shared/testing/page-objects/NavigationBar/common.ts +13 -0
- package/templates/nx/shared/testing/steps/loginAsAdmin/common.step.ts +3 -0
- package/templates/nx/shared/testing/tsconfig.json +9 -5
- package/templates/nx/shared/testing/tsconfig.tsbuildinfo +1 -1
- package/templates/nx/modules/catalog/e2e/spectra.config.ts +0 -61
- package/templates/nx/shared/testing/src/index.ts +0 -5
- /package/templates/nx/{modules/auth/e2e/spectra.config.ts → spectra.config.ts} +0 -0
package/dist/commands/init.js
CHANGED
|
@@ -4,41 +4,152 @@ import { fileURLToPath } from "url";
|
|
|
4
4
|
import { ConfigLoader } from "../config/loader.js";
|
|
5
5
|
import { TypeGenerator } from "../types/generator.js";
|
|
6
6
|
import inquirer from "inquirer";
|
|
7
|
+
function parsePnpmWorkspaceGlobs(content) {
|
|
8
|
+
const lines = content.split("\n");
|
|
9
|
+
const globs = [];
|
|
10
|
+
let inPackages = false;
|
|
11
|
+
for (const line of lines) {
|
|
12
|
+
const trimmed = line.trim();
|
|
13
|
+
if (trimmed.startsWith("packages:")) {
|
|
14
|
+
inPackages = true;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (inPackages) {
|
|
18
|
+
if (trimmed.startsWith("-")) {
|
|
19
|
+
const item = trimmed.replace(/^-\s*['"]?/, "").replace(/['"]?\s*$/, "");
|
|
20
|
+
if (item)
|
|
21
|
+
globs.push(item);
|
|
22
|
+
}
|
|
23
|
+
else if (trimmed && !trimmed.startsWith("#")) {
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return globs;
|
|
29
|
+
}
|
|
30
|
+
function scanDirectoriesForProjects(cwd, patterns) {
|
|
31
|
+
const foundProjects = [];
|
|
32
|
+
const visited = new Set();
|
|
33
|
+
function search(dir, currentDepth, maxDepth) {
|
|
34
|
+
if (currentDepth > maxDepth || !fs.existsSync(dir))
|
|
35
|
+
return;
|
|
36
|
+
const base = path.basename(dir);
|
|
37
|
+
if (base === "node_modules" || base === "dist" || base === ".testspectra" || base === ".git" || base === "shared") {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const hasPkg = fs.existsSync(path.join(dir, "package.json"));
|
|
41
|
+
const hasNx = fs.existsSync(path.join(dir, "project.json"));
|
|
42
|
+
if ((hasPkg || hasNx) && dir !== cwd) {
|
|
43
|
+
const rel = path.relative(cwd, dir);
|
|
44
|
+
if (!visited.has(rel)) {
|
|
45
|
+
visited.add(rel);
|
|
46
|
+
let name = path.basename(dir);
|
|
47
|
+
if (hasPkg) {
|
|
48
|
+
try {
|
|
49
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf-8"));
|
|
50
|
+
if (pkg.name)
|
|
51
|
+
name = pkg.name;
|
|
52
|
+
}
|
|
53
|
+
catch { }
|
|
54
|
+
}
|
|
55
|
+
else if (hasNx) {
|
|
56
|
+
try {
|
|
57
|
+
const nxJson = JSON.parse(fs.readFileSync(path.join(dir, "project.json"), "utf-8"));
|
|
58
|
+
if (nxJson.name)
|
|
59
|
+
name = nxJson.name;
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
}
|
|
63
|
+
foundProjects.push({
|
|
64
|
+
name,
|
|
65
|
+
relPath: rel,
|
|
66
|
+
absPath: dir,
|
|
67
|
+
hasNxProject: hasNx,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return; // don't recurse into nested sub-packages unless needed
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
search(path.join(dir, entry.name), currentDepth + 1, maxDepth);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
}
|
|
82
|
+
// If patterns exist from pnpm-workspace.yaml, search those roots
|
|
83
|
+
if (patterns.length > 0) {
|
|
84
|
+
for (const pat of patterns) {
|
|
85
|
+
const cleanPat = pat.replace(/\/\*\*?$/, "").replace(/\/\*$/, "");
|
|
86
|
+
const searchRoot = path.join(cwd, cleanPat);
|
|
87
|
+
if (fs.existsSync(searchRoot)) {
|
|
88
|
+
const stat = fs.statSync(searchRoot);
|
|
89
|
+
if (stat.isDirectory()) {
|
|
90
|
+
const entries = fs.readdirSync(searchRoot, { withFileTypes: true });
|
|
91
|
+
for (const e of entries) {
|
|
92
|
+
if (e.isDirectory()) {
|
|
93
|
+
search(path.join(searchRoot, e.name), 1, 3);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Fallback search in standard directories if nothing found
|
|
101
|
+
if (foundProjects.length === 0) {
|
|
102
|
+
for (const fallback of ["modules", "packages", "apps", "libs", "features"]) {
|
|
103
|
+
const fbPath = path.join(cwd, fallback);
|
|
104
|
+
if (fs.existsSync(fbPath)) {
|
|
105
|
+
search(fbPath, 1, 3);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return foundProjects;
|
|
110
|
+
}
|
|
7
111
|
export async function initCommand(options = {}) {
|
|
8
112
|
const cwd = process.cwd();
|
|
9
113
|
const existingConfig = ConfigLoader.findConfigFile(cwd);
|
|
10
|
-
if (existingConfig && !options.force) {
|
|
114
|
+
if (existingConfig && existingConfig === path.join(cwd, path.basename(existingConfig)) && !options.force) {
|
|
11
115
|
console.log(`\x1b[33m[TestSpectra]\x1b[0m Config already exists at ${path.basename(existingConfig)}. Use --force to overwrite.`);
|
|
12
116
|
return;
|
|
13
117
|
}
|
|
14
|
-
//
|
|
15
|
-
let
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
checkNx = path.dirname(checkNx);
|
|
118
|
+
// Detect monorepo environment
|
|
119
|
+
let isMonorepo = false;
|
|
120
|
+
let pnpmPatterns = [];
|
|
121
|
+
const pnpmWorkspacePath = path.join(cwd, "pnpm-workspace.yaml");
|
|
122
|
+
const nxJsonPath = path.join(cwd, "nx.json");
|
|
123
|
+
if (fs.existsSync(pnpmWorkspacePath)) {
|
|
124
|
+
isMonorepo = true;
|
|
125
|
+
try {
|
|
126
|
+
const content = fs.readFileSync(pnpmWorkspacePath, "utf-8");
|
|
127
|
+
pnpmPatterns = parsePnpmWorkspaceGlobs(content);
|
|
26
128
|
}
|
|
129
|
+
catch { }
|
|
130
|
+
}
|
|
131
|
+
else if (fs.existsSync(nxJsonPath)) {
|
|
132
|
+
isMonorepo = true;
|
|
133
|
+
}
|
|
134
|
+
const detectedProjects = isMonorepo ? scanDirectoriesForProjects(cwd, pnpmPatterns) : [];
|
|
135
|
+
let selectedTemplate = options.template;
|
|
136
|
+
if (!selectedTemplate && !options.force) {
|
|
137
|
+
const choices = [
|
|
138
|
+
{ name: "Standard (Standalone or single project)", value: "default" },
|
|
139
|
+
{ name: "Nx Monorepo (Interactive multi-module feature discovery & centralized config)", value: "nx" },
|
|
140
|
+
];
|
|
27
141
|
const answers = await inquirer.prompt([
|
|
28
142
|
{
|
|
29
143
|
type: "list",
|
|
30
144
|
name: "template",
|
|
31
|
-
message: "Select TestSpectra
|
|
32
|
-
choices
|
|
33
|
-
|
|
34
|
-
{ name: "Nx Monorepo (Includes project.json with Nx targets & affected support)", value: "nx" },
|
|
35
|
-
],
|
|
36
|
-
default: isNx ? "nx" : "default",
|
|
145
|
+
message: "Select TestSpectra setup mode:",
|
|
146
|
+
choices,
|
|
147
|
+
default: isMonorepo ? "nx" : "default",
|
|
37
148
|
},
|
|
38
149
|
]);
|
|
39
150
|
selectedTemplate = answers.template;
|
|
40
151
|
}
|
|
41
|
-
const templateName = selectedTemplate || "default";
|
|
152
|
+
const templateName = selectedTemplate || (isMonorepo ? "nx" : "default");
|
|
42
153
|
// 1. Resolve template directory
|
|
43
154
|
const __filename = fileURLToPath(import.meta.url);
|
|
44
155
|
const __dirname = path.dirname(__filename);
|
|
@@ -52,8 +163,8 @@ export async function initCommand(options = {}) {
|
|
|
52
163
|
if (!fs.existsSync(templateDir)) {
|
|
53
164
|
throw new Error(`[TestSpectra] Scaffold template directory not found at: ${templateDir}`);
|
|
54
165
|
}
|
|
55
|
-
// 2.
|
|
56
|
-
let cliVersion = "^1.0.
|
|
166
|
+
// 2. Resolve CLI dependency version
|
|
167
|
+
let cliVersion = "^1.0.17";
|
|
57
168
|
try {
|
|
58
169
|
const cliPackageJsonPath = path.resolve(__dirname, "../../package.json");
|
|
59
170
|
if (fs.existsSync(cliPackageJsonPath)) {
|
|
@@ -64,7 +175,6 @@ export async function initCommand(options = {}) {
|
|
|
64
175
|
}
|
|
65
176
|
}
|
|
66
177
|
catch { }
|
|
67
|
-
// Check if cwd is inside the testspectra monorepo with a pnpm-workspace.yaml
|
|
68
178
|
let isInternalWorkspace = false;
|
|
69
179
|
let cur = cwd;
|
|
70
180
|
while (cur !== path.dirname(cur)) {
|
|
@@ -81,9 +191,8 @@ export async function initCommand(options = {}) {
|
|
|
81
191
|
cur = path.dirname(cur);
|
|
82
192
|
}
|
|
83
193
|
const cliDepVersion = isInternalWorkspace ? "workspace:*" : cliVersion;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
function copyRecursive(src, dest) {
|
|
194
|
+
// Helper: Copy directory contents recursively
|
|
195
|
+
function copyRecursive(src, dest, replacements = {}) {
|
|
87
196
|
const base = path.basename(src);
|
|
88
197
|
if (base === "node_modules" || base === "dist" || base === ".testspectra" || base === ".git") {
|
|
89
198
|
return;
|
|
@@ -95,41 +204,161 @@ export async function initCommand(options = {}) {
|
|
|
95
204
|
}
|
|
96
205
|
const entries = fs.readdirSync(src);
|
|
97
206
|
for (const entry of entries) {
|
|
98
|
-
copyRecursive(path.join(src, entry), path.join(dest, entry));
|
|
207
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry), replacements);
|
|
99
208
|
}
|
|
100
209
|
}
|
|
101
210
|
else {
|
|
102
|
-
// If destination exists and force is not set, skip
|
|
103
211
|
if (fs.existsSync(dest) && !options.force) {
|
|
104
212
|
return;
|
|
105
213
|
}
|
|
106
214
|
let content = fs.readFileSync(src, "utf-8");
|
|
107
|
-
|
|
215
|
+
for (const [k, v] of Object.entries(replacements)) {
|
|
216
|
+
content = content.replaceAll(k, v);
|
|
217
|
+
}
|
|
218
|
+
if (dest.endsWith("package.json")) {
|
|
108
219
|
try {
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
if (pkgObj.devDependencies && pkgObj.devDependencies["@testspectra/cli"]) {
|
|
114
|
-
pkgObj.devDependencies["@testspectra/cli"] = cliDepVersion;
|
|
220
|
+
const pkg = JSON.parse(content);
|
|
221
|
+
if (pkg.devDependencies && pkg.devDependencies["@testspectra/cli"]) {
|
|
222
|
+
pkg.devDependencies["@testspectra/cli"] = cliDepVersion;
|
|
115
223
|
}
|
|
116
|
-
if (
|
|
117
|
-
|
|
224
|
+
if (pkg.dependencies && pkg.dependencies["@testspectra/cli"]) {
|
|
225
|
+
pkg.dependencies["@testspectra/cli"] = cliDepVersion;
|
|
118
226
|
}
|
|
119
|
-
content = JSON.stringify(
|
|
227
|
+
content = JSON.stringify(pkg, null, 2);
|
|
120
228
|
}
|
|
121
229
|
catch { }
|
|
122
230
|
}
|
|
123
|
-
else if (src.endsWith("project.json")) {
|
|
124
|
-
content = content
|
|
125
|
-
.replace(/\{\{PROJECT_NAME\}\}/g, projectName)
|
|
126
|
-
.replace(/\{\{PROJECT_DIR\}\}/g, path.relative(cur, cwd) || ".");
|
|
127
|
-
}
|
|
128
231
|
fs.writeFileSync(dest, content, "utf-8");
|
|
129
232
|
}
|
|
130
233
|
}
|
|
234
|
+
// --- MONOREPO INTERACTIVE FLOW ---
|
|
235
|
+
if (templateName === "nx" && (detectedProjects.length > 0 || isMonorepo)) {
|
|
236
|
+
console.log(`\n\x1b[36m[TestSpectra Monorepo Discovery]\x1b[0m`);
|
|
237
|
+
console.log(`Found ${detectedProjects.length} workspace feature project(s).`);
|
|
238
|
+
let chosenProjects = [];
|
|
239
|
+
if (detectedProjects.length > 0) {
|
|
240
|
+
const projectAnswers = await inquirer.prompt([
|
|
241
|
+
{
|
|
242
|
+
type: "checkbox",
|
|
243
|
+
name: "selectedProjects",
|
|
244
|
+
message: "Select workspace features where TestSpectra should be initialized:",
|
|
245
|
+
choices: detectedProjects.map((p) => ({
|
|
246
|
+
name: `${p.relPath} (${p.name})${p.hasNxProject ? " [Nx project.json]" : ""}`,
|
|
247
|
+
value: p.relPath,
|
|
248
|
+
checked: true,
|
|
249
|
+
})),
|
|
250
|
+
},
|
|
251
|
+
]);
|
|
252
|
+
chosenProjects = projectAnswers.selectedProjects;
|
|
253
|
+
}
|
|
254
|
+
const folderAnswers = await inquirer.prompt([
|
|
255
|
+
{
|
|
256
|
+
type: "input",
|
|
257
|
+
name: "e2eFolderName",
|
|
258
|
+
message: "Enter E2E test folder name for each selected feature:",
|
|
259
|
+
default: "e2e",
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
type: "input",
|
|
263
|
+
name: "sharedTestingPath",
|
|
264
|
+
message: "Enter path for Shared Testing library (POMs, Steps, Actions, Fixtures):",
|
|
265
|
+
default: "shared/testing",
|
|
266
|
+
},
|
|
267
|
+
]);
|
|
268
|
+
const e2eFolderName = folderAnswers.e2eFolderName.trim() || "e2e";
|
|
269
|
+
const sharedTestingRelPath = folderAnswers.sharedTestingPath.trim() || "shared/testing";
|
|
270
|
+
const sharedTestingAbsPath = path.join(cwd, sharedTestingRelPath);
|
|
271
|
+
// 1. Centralized Root Config
|
|
272
|
+
const rootConfigSrc = path.join(templateDir, "spectra.config.ts");
|
|
273
|
+
const rootConfigDest = path.join(cwd, "spectra.config.ts");
|
|
274
|
+
if (fs.existsSync(rootConfigSrc) && (!fs.existsSync(rootConfigDest) || options.force)) {
|
|
275
|
+
fs.copyFileSync(rootConfigSrc, rootConfigDest);
|
|
276
|
+
console.log(`\x1b[32m[TestSpectra]\x1b[0m Created centralized configuration at ./spectra.config.ts`);
|
|
277
|
+
}
|
|
278
|
+
// 2. Centralized Root .testspectra folder for cache/logs
|
|
279
|
+
const rootTestDataDir = path.join(cwd, ".testspectra");
|
|
280
|
+
if (!fs.existsSync(rootTestDataDir)) {
|
|
281
|
+
fs.mkdirSync(rootTestDataDir, { recursive: true });
|
|
282
|
+
}
|
|
283
|
+
// 3. Centralized Shared Testing Library
|
|
284
|
+
const sharedTestingSrc = path.join(templateDir, "shared/testing");
|
|
285
|
+
if (fs.existsSync(sharedTestingSrc)) {
|
|
286
|
+
copyRecursive(sharedTestingSrc, sharedTestingAbsPath);
|
|
287
|
+
console.log(`\x1b[32m[TestSpectra]\x1b[0m Initialized shared testing library at ./${sharedTestingRelPath}`);
|
|
288
|
+
}
|
|
289
|
+
// 4. Distribute E2E Folders to Selected Feature Modules
|
|
290
|
+
const sampleE2eSrc = path.join(templateDir, "modules/auth/e2e");
|
|
291
|
+
const featureE2eDirs = [];
|
|
292
|
+
for (const projRel of chosenProjects) {
|
|
293
|
+
const targetE2eDir = path.join(cwd, projRel, e2eFolderName);
|
|
294
|
+
const featureName = path.basename(projRel);
|
|
295
|
+
const e2eProjectName = `${featureName}-${e2eFolderName}`;
|
|
296
|
+
copyRecursive(sampleE2eSrc, targetE2eDir, {
|
|
297
|
+
"auth-e2e": e2eProjectName,
|
|
298
|
+
"modules/auth/e2e": path.relative(cwd, targetE2eDir).replace(/\\/g, "/"),
|
|
299
|
+
"scope:auth": `scope:${featureName}`,
|
|
300
|
+
});
|
|
301
|
+
// Adjust project.json
|
|
302
|
+
const projJsonPath = path.join(targetE2eDir, "project.json");
|
|
303
|
+
if (fs.existsSync(projJsonPath)) {
|
|
304
|
+
try {
|
|
305
|
+
const pObj = JSON.parse(fs.readFileSync(projJsonPath, "utf-8"));
|
|
306
|
+
pObj.name = e2eProjectName;
|
|
307
|
+
pObj.sourceRoot = path.relative(cwd, targetE2eDir).replace(/\\/g, "/");
|
|
308
|
+
pObj.targets.e2e.options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g, "/");
|
|
309
|
+
pObj.targets["type-check"].options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g, "/");
|
|
310
|
+
pObj.tags = [`scope:${featureName}`, "type:e2e"];
|
|
311
|
+
fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2), "utf-8");
|
|
312
|
+
}
|
|
313
|
+
catch { }
|
|
314
|
+
}
|
|
315
|
+
// Ensure no local spectra.config.ts (centralized in root)
|
|
316
|
+
const localConfig = path.join(targetE2eDir, "spectra.config.ts");
|
|
317
|
+
if (fs.existsSync(localConfig))
|
|
318
|
+
fs.unlinkSync(localConfig);
|
|
319
|
+
featureE2eDirs.push(targetE2eDir);
|
|
320
|
+
console.log(`\x1b[32m[TestSpectra]\x1b[0m Initialized ${e2eProjectName} in ./${path.relative(cwd, targetE2eDir)}`);
|
|
321
|
+
}
|
|
322
|
+
// 5. Update Root Solution tsconfig.json references
|
|
323
|
+
const rootTsConfigPath = path.join(cwd, "tsconfig.json");
|
|
324
|
+
const references = [];
|
|
325
|
+
for (const e2eDir of featureE2eDirs) {
|
|
326
|
+
references.push({ path: `./${path.relative(cwd, e2eDir).replace(/\\/g, "/")}` });
|
|
327
|
+
}
|
|
328
|
+
references.push({ path: `./${sharedTestingRelPath.replace(/\\/g, "/")}` });
|
|
329
|
+
let rootTsConfig = { files: [], references: [] };
|
|
330
|
+
if (fs.existsSync(rootTsConfigPath)) {
|
|
331
|
+
try {
|
|
332
|
+
rootTsConfig = JSON.parse(fs.readFileSync(rootTsConfigPath, "utf-8"));
|
|
333
|
+
if (!rootTsConfig.references)
|
|
334
|
+
rootTsConfig.references = [];
|
|
335
|
+
for (const ref of references) {
|
|
336
|
+
if (!rootTsConfig.references.some((r) => r.path === ref.path)) {
|
|
337
|
+
rootTsConfig.references.push(ref);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch { }
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
rootTsConfig = { files: [], references };
|
|
345
|
+
}
|
|
346
|
+
fs.writeFileSync(rootTsConfigPath, JSON.stringify(rootTsConfig, null, 2), "utf-8");
|
|
347
|
+
// 6. Generate Ambient Types for Shared + All E2E Feature Directories
|
|
348
|
+
TypeGenerator.writeDeclarationFiles(cwd);
|
|
349
|
+
for (const e2eDir of featureE2eDirs) {
|
|
350
|
+
TypeGenerator.writeDeclarationFiles(e2eDir);
|
|
351
|
+
}
|
|
352
|
+
console.log(`\n\x1b[32m[TestSpectra]\x1b[0m Enterprise Nx Monorepo initialized successfully!`);
|
|
353
|
+
console.log(`\x1b[36m- Centralized Configuration:\x1b[0m ./spectra.config.ts`);
|
|
354
|
+
console.log(`\x1b[36m- Centralized App Data/Cache:\x1b[0m ./.testspectra/`);
|
|
355
|
+
console.log(`\x1b[36m- Shared Testing Library:\x1b[0m ./${sharedTestingRelPath}`);
|
|
356
|
+
console.log(`\x1b[36m- Initialized Features (${featureE2eDirs.length}):\x1b[0m\n ${featureE2eDirs.map((d) => path.relative(cwd, d)).join("\n ")}`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
// --- STANDALONE / DEFAULT PROJECT FLOW ---
|
|
360
|
+
const projectName = path.basename(cwd);
|
|
131
361
|
copyRecursive(templateDir, cwd);
|
|
132
|
-
// 4. Pre-configure pnpm allowBuilds for standalone projects
|
|
133
362
|
if (!isInternalWorkspace) {
|
|
134
363
|
const pnpmWorkspacePath = path.join(cwd, "pnpm-workspace.yaml");
|
|
135
364
|
if (!fs.existsSync(pnpmWorkspacePath) || options.force) {
|
|
@@ -137,21 +366,7 @@ export async function initCommand(options = {}) {
|
|
|
137
366
|
fs.writeFileSync(pnpmWorkspacePath, pnpmWsContent, "utf-8");
|
|
138
367
|
}
|
|
139
368
|
}
|
|
140
|
-
// 5. Generate ambient declaration files
|
|
141
369
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
142
|
-
// If subprojects exist (e.g. in Nx monorepo template), generate types for them as well
|
|
143
|
-
const modulesDir = path.join(cwd, "modules");
|
|
144
|
-
if (fs.existsSync(modulesDir)) {
|
|
145
|
-
const mods = fs.readdirSync(modulesDir, { withFileTypes: true });
|
|
146
|
-
for (const m of mods) {
|
|
147
|
-
if (m.isDirectory()) {
|
|
148
|
-
const e2eDir = path.join(modulesDir, m.name, "e2e");
|
|
149
|
-
if (fs.existsSync(e2eDir)) {
|
|
150
|
-
TypeGenerator.writeDeclarationFiles(e2eDir);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
370
|
console.log(`\x1b[32m[TestSpectra]\x1b[0m Initialized project from template successfully!`);
|
|
156
371
|
console.log(`\x1b[32m[TestSpectra]\x1b[0m Generated ambient multi-platform types in .testspectra/types/`);
|
|
157
372
|
}
|
package/dist/commands/run.js
CHANGED
|
@@ -13,7 +13,17 @@ export async function runCommand(specPath, options = {}) {
|
|
|
13
13
|
config.webConfig.headlessMode = options.headless;
|
|
14
14
|
}
|
|
15
15
|
const platform = options.target || "web";
|
|
16
|
-
|
|
16
|
+
// Find workspace root for centralized .testspectra
|
|
17
|
+
let workspaceRoot = cwd;
|
|
18
|
+
let cur = cwd;
|
|
19
|
+
while (cur !== path.dirname(cur)) {
|
|
20
|
+
if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml")) || fs.existsSync(path.join(cur, "nx.json")) || fs.existsSync(path.join(cur, "spectra.config.ts")) || fs.existsSync(path.join(cur, "testspectra.config.ts"))) {
|
|
21
|
+
workspaceRoot = cur;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
cur = path.dirname(cur);
|
|
25
|
+
}
|
|
26
|
+
const appDataPath = options.workdir ? path.resolve(options.workdir) : path.join(workspaceRoot, ".testspectra");
|
|
17
27
|
if (!fs.existsSync(appDataPath)) {
|
|
18
28
|
fs.mkdirSync(appDataPath, { recursive: true });
|
|
19
29
|
}
|
package/dist/config/loader.js
CHANGED
|
@@ -11,11 +11,18 @@ export class ConfigLoader {
|
|
|
11
11
|
".testspectrarc.json",
|
|
12
12
|
];
|
|
13
13
|
static findConfigFile(cwd = process.cwd()) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
let cur = path.resolve(cwd);
|
|
15
|
+
while (true) {
|
|
16
|
+
for (const filename of this.CONFIG_FILE_NAMES) {
|
|
17
|
+
const fullPath = path.join(cur, filename);
|
|
18
|
+
if (fs.existsSync(fullPath)) {
|
|
19
|
+
return fullPath;
|
|
20
|
+
}
|
|
18
21
|
}
|
|
22
|
+
const parent = path.dirname(cur);
|
|
23
|
+
if (parent === cur)
|
|
24
|
+
break;
|
|
25
|
+
cur = parent;
|
|
19
26
|
}
|
|
20
27
|
return null;
|
|
21
28
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type PlatformTarget = "web" | "android" | "ios" | "mobile" | "common";
|
|
2
2
|
export declare class TypeGenerator {
|
|
3
|
-
static
|
|
3
|
+
static getPageObjectsDirs(cwd: string): string[];
|
|
4
|
+
static getEntityDirs(cwd: string, entityType: "actions" | "steps" | "fixtures"): string[];
|
|
4
5
|
static generateAmbientDeclarations(cwd: string): Record<string, string>;
|
|
5
6
|
static generateFixturesDeclaration(cwd: string): string;
|
|
6
7
|
static writeDeclarationFiles(cwd: string): void;
|