@testspectra/cli 1.0.44 → 1.0.45

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/plugin.js CHANGED
@@ -1,4 +1,23 @@
1
+ import fs from "fs";
2
+ import path from "path";
1
3
  import { TypeGenerator } from "./types/generator.js";
4
+ /**
5
+ * Traverses upwards from `startDir` to locate the TestSpectra workspace root.
6
+ * Looks for indicators: `spectra.config.ts`, `.testspectra`, `pnpm-workspace.yaml`, or `nx.json`.
7
+ */
8
+ function findWorkspaceRoot(startDir) {
9
+ let cur = path.resolve(startDir);
10
+ while (cur !== path.dirname(cur)) {
11
+ if (fs.existsSync(path.join(cur, "spectra.config.ts")) ||
12
+ fs.existsSync(path.join(cur, ".testspectra")) ||
13
+ fs.existsSync(path.join(cur, "pnpm-workspace.yaml")) ||
14
+ fs.existsSync(path.join(cur, "nx.json"))) {
15
+ return cur;
16
+ }
17
+ cur = path.dirname(cur);
18
+ }
19
+ return startDir;
20
+ }
2
21
  /**
3
22
  * TypeScript Language Service Plugin Factory for TestSpectra.
4
23
  * Runs silently inside TSServer (VS Code, Cursor, WebStorm, Neovim, etc.).
@@ -11,31 +30,74 @@ function init(modules) {
11
30
  function create(info) {
12
31
  const project = info.project;
13
32
  const projectDir = project.getCurrentDirectory();
33
+ const workspaceRoot = findWorkspaceRoot(projectDir);
34
+ const log = (msg) => {
35
+ try {
36
+ info.project.projectService.logger.info(`[TestSpectra TS Plugin] ${msg}`);
37
+ }
38
+ catch { }
39
+ };
14
40
  // 1. Initial Generation on project load
15
41
  try {
16
- TypeGenerator.writeDeclarationFiles(projectDir);
17
- info.project.projectService.logger.info(`[TestSpectra TS Plugin] Initialized ambient declarations in ${projectDir}`);
42
+ TypeGenerator.writeDeclarationFiles(workspaceRoot);
43
+ log(`Initialized ambient declarations in workspace root: ${workspaceRoot}`);
18
44
  }
19
45
  catch (err) {
20
- info.project.projectService.logger.info(`[TestSpectra TS Plugin] Failed initial type generation: ${err?.message}`);
46
+ log(`Failed initial type generation: ${err?.message}`);
21
47
  }
22
- // 2. Watch for entity file changes inside TSServer
23
- const watchedFolders = ["page-objects", "pageobjects", "actions", "steps", "fixtures", "hooks"];
24
- // Debounce type regeneration
48
+ // Debounced regeneration handler
25
49
  let debounceTimer = null;
26
- const triggerRegeneration = (pathChanged) => {
27
- if (debounceTimer)
28
- clearTimeout(debounceTimer);
29
- debounceTimer = setTimeout(() => {
50
+ const triggerRegeneration = (reason, sync = false) => {
51
+ const doRegen = () => {
30
52
  try {
31
- TypeGenerator.writeDeclarationFiles(projectDir);
32
- info.project.projectService.logger.info(`[TestSpectra TS Plugin] Regenerated ambient declarations after change in ${pathChanged}`);
53
+ TypeGenerator.writeDeclarationFiles(workspaceRoot);
54
+ log(`Regenerated ambient declarations (${reason})`);
33
55
  }
34
56
  catch (err) {
35
- info.project.projectService.logger.info(`[TestSpectra TS Plugin] Error during regeneration: ${err?.message}`);
57
+ log(`Error during regeneration: ${err?.message}`);
36
58
  }
37
- }, 200);
59
+ };
60
+ if (sync) {
61
+ doRegen();
62
+ }
63
+ else {
64
+ if (debounceTimer)
65
+ clearTimeout(debounceTimer);
66
+ debounceTimer = setTimeout(doRegen, 80);
67
+ }
38
68
  };
69
+ // 2. Active File System Watcher on the entire workspace
70
+ try {
71
+ if (fs.existsSync(workspaceRoot)) {
72
+ const watcher = fs.watch(workspaceRoot, { recursive: true }, (eventType, filename) => {
73
+ if (!filename)
74
+ return;
75
+ const fn = filename.toString();
76
+ // Skip internal and build directories
77
+ if (fn.includes(".testspectra") ||
78
+ fn.includes("node_modules") ||
79
+ fn.includes(".git") ||
80
+ fn.includes("dist") ||
81
+ fn.includes(".nx")) {
82
+ return;
83
+ }
84
+ // Match relevant entity files
85
+ if (fn.includes("fixtures") ||
86
+ fn.includes("page-objects") ||
87
+ fn.includes("pageobjects") ||
88
+ fn.includes("actions") ||
89
+ fn.includes("steps") ||
90
+ fn.includes("hooks") ||
91
+ fn.includes("spectra.config")) {
92
+ triggerRegeneration(`fs.watch '${eventType}' on ${fn}`);
93
+ }
94
+ });
95
+ watcher.on("error", () => { });
96
+ }
97
+ }
98
+ catch (err) {
99
+ log(`Note: Recursive fs.watch not supported or failed: ${err?.message}`);
100
+ }
39
101
  // 3. Proxy language service methods to detect file modifications / completions
40
102
  const proxy = Object.create(null);
41
103
  for (const k of Object.keys(info.languageService)) {
@@ -46,9 +108,9 @@ function init(modules) {
46
108
  // Intercept getCompletionsAtPosition to ensure fresh types
47
109
  const originalGetCompletions = info.languageService.getCompletionsAtPosition;
48
110
  proxy.getCompletionsAtPosition = (fileName, position, options, formattingSettings) => {
49
- const isEntity = watchedFolders.some((f) => fileName.includes(f));
50
- if (isEntity) {
51
- triggerRegeneration(fileName);
111
+ const ext = path.extname(fileName);
112
+ if (ext === ".ts" || ext === ".tsx" || ext === ".js" || ext === ".mjs") {
113
+ triggerRegeneration(`completions at ${path.basename(fileName)}`, false);
52
114
  }
53
115
  return originalGetCompletions.apply(info.languageService, [
54
116
  fileName,
@@ -57,6 +119,11 @@ function init(modules) {
57
119
  formattingSettings,
58
120
  ]);
59
121
  };
122
+ // Intercept getQuickInfoAtPosition for fresh hover info
123
+ const originalGetQuickInfo = info.languageService.getQuickInfoAtPosition;
124
+ proxy.getQuickInfoAtPosition = (fileName, position) => {
125
+ return originalGetQuickInfo.apply(info.languageService, [fileName, position]);
126
+ };
60
127
  return proxy;
61
128
  }
62
129
  return { create };
@@ -307,20 +307,31 @@ export class TypeGenerator {
307
307
  content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
308
308
  content += `interface TestSpectraFixtures {\n`;
309
309
  const declaredFixtures = new Set();
310
- for (const fixturesDir of fixturesDirs) {
311
- if (!fs.existsSync(fixturesDir))
312
- continue;
313
- const fixtureFiles = fs.readdirSync(fixturesDir);
314
- for (const file of fixtureFiles) {
315
- if (file.startsWith("."))
316
- continue;
317
- const parsed = path.parse(file);
318
- const varName = parsed.name.replace(/[^a-zA-Z0-9_$]/g, "_");
319
- if (declaredFixtures.has(varName))
320
- continue;
321
- content += ` readonly ${varName}: string;\n`;
322
- declaredFixtures.add(varName);
310
+ function scanFixtureDir(dir) {
311
+ if (!fs.existsSync(dir))
312
+ return;
313
+ try {
314
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
315
+ for (const entry of entries) {
316
+ if (entry.name.startsWith("."))
317
+ continue;
318
+ if (entry.isDirectory()) {
319
+ scanFixtureDir(path.join(dir, entry.name));
320
+ }
321
+ else if (entry.isFile()) {
322
+ const parsed = path.parse(entry.name);
323
+ const varName = parsed.name.replace(/[^a-zA-Z0-9_$]/g, "_");
324
+ if (declaredFixtures.has(varName))
325
+ continue;
326
+ content += ` readonly ${varName}: string;\n`;
327
+ declaredFixtures.add(varName);
328
+ }
329
+ }
323
330
  }
331
+ catch { }
332
+ }
333
+ for (const fixturesDir of fixturesDirs) {
334
+ scanFixtureDir(fixturesDir);
324
335
  }
325
336
  content += `}\n`;
326
337
  content += `declare const Fixture: TestSpectraFixtures;\n`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testspectra/cli",
3
- "version": "1.0.44",
3
+ "version": "1.0.45",
4
4
  "description": "TestSpectra Zero-Config Cross-Platform Test Runner CLI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "@clack/prompts": "^1.7.0",
24
- "@testspectra/matchers": "^1.0.44",
24
+ "@testspectra/matchers": "^1.0.45",
25
25
  "chalk": "^5.3.0",
26
26
  "commander": "^12.1.0",
27
27
  "dotenv": "^16.4.5",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "devDependencies": {
11
11
  "@testspectra/cli": "workspace:*",
12
- "@testspectra/matchers": "^1.0.44",
12
+ "@testspectra/matchers": "^1.0.45",
13
13
  "@types/node": "^20.14.0",
14
14
  "@wdio/globals": "^9.2.8",
15
15
  "@wdio/mocha-framework": "^9.2.8",
@@ -7,7 +7,10 @@
7
7
  "noEmit": true,
8
8
  "skipLibCheck": true,
9
9
  "strict": true,
10
- "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"]
10
+ "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
11
+ "plugins": [
12
+ { "name": "@testspectra/cli" }
13
+ ]
11
14
  },
12
15
  "include": [
13
16
  ".testspectra/types/android.d.ts",
@@ -7,7 +7,10 @@
7
7
  "noEmit": true,
8
8
  "skipLibCheck": true,
9
9
  "strict": true,
10
- "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"]
10
+ "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
11
+ "plugins": [
12
+ { "name": "@testspectra/cli" }
13
+ ]
11
14
  },
12
15
  "include": [
13
16
  ".testspectra/types/ios.d.ts",
@@ -7,7 +7,10 @@
7
7
  "noEmit": true,
8
8
  "skipLibCheck": true,
9
9
  "strict": true,
10
- "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"]
10
+ "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
11
+ "plugins": [
12
+ { "name": "@testspectra/cli" }
13
+ ]
11
14
  },
12
15
  "include": [
13
16
  ".testspectra/types/web.d.ts",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "devDependencies": {
13
13
  "@testspectra/cli": "workspace:*",
14
- "@testspectra/matchers": "^1.0.44",
14
+ "@testspectra/matchers": "^1.0.45",
15
15
  "@types/node": "^20.14.0",
16
16
  "@wdio/globals": "^9.2.8",
17
17
  "@wdio/mocha-framework": "^9.2.8",
@@ -7,7 +7,10 @@
7
7
  "noEmit": true,
8
8
  "skipLibCheck": true,
9
9
  "strict": true,
10
- "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"]
10
+ "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
11
+ "plugins": [
12
+ { "name": "@testspectra/cli" }
13
+ ]
11
14
  },
12
15
  "include": [
13
16
  ".testspectra/types/android.d.ts",
@@ -7,7 +7,10 @@
7
7
  "noEmit": true,
8
8
  "skipLibCheck": true,
9
9
  "strict": true,
10
- "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"]
10
+ "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
11
+ "plugins": [
12
+ { "name": "@testspectra/cli" }
13
+ ]
11
14
  },
12
15
  "include": [
13
16
  ".testspectra/types/ios.d.ts",
@@ -7,7 +7,10 @@
7
7
  "noEmit": true,
8
8
  "skipLibCheck": true,
9
9
  "strict": true,
10
- "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"]
10
+ "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
11
+ "plugins": [
12
+ { "name": "@testspectra/cli" }
13
+ ]
11
14
  },
12
15
  "include": [
13
16
  ".testspectra/types/shared/common.d.ts",
@@ -7,7 +7,10 @@
7
7
  "noEmit": true,
8
8
  "skipLibCheck": true,
9
9
  "strict": true,
10
- "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"]
10
+ "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
11
+ "plugins": [
12
+ { "name": "@testspectra/cli" }
13
+ ]
11
14
  },
12
15
  "include": [
13
16
  ".testspectra/types/web.d.ts",