@testspectra/cli 1.0.65 → 1.0.67

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.
@@ -1,3 +1,19 @@
1
- export declare function doctorCommand(options: {
1
+ export interface DependencyStatus {
2
+ name: string;
3
+ category: "core" | "web" | "mobile";
4
+ installed: boolean;
5
+ version: string | null;
6
+ required: boolean;
7
+ required_version: string | null;
8
+ depends_on: string[];
9
+ }
10
+ export interface SystemCheckResult {
11
+ all_ready: boolean;
12
+ missing_count: number;
13
+ dependencies: DependencyStatus[];
14
+ }
15
+ export declare function runSystemChecks(cwd?: string): Promise<SystemCheckResult>;
16
+ export declare function doctorCommand(options?: {
2
17
  fix?: boolean;
18
+ json?: boolean;
3
19
  }): Promise<void>;
@@ -1,54 +1,278 @@
1
+ import fs from "fs";
2
+ import path from "path";
1
3
  import { execSync } from "child_process";
2
- export async function doctorCommand(options) {
3
- console.log("\x1b[36m[TestSpectra Doctor]\x1b[0m Checking development and runtime dependencies...\n");
4
- const checks = [
5
- {
6
- name: "Node.js",
7
- command: "node -v",
8
- required: true,
9
- },
10
- {
11
- name: "Bun",
12
- command: "bun -v",
13
- required: false,
14
- },
15
- {
16
- name: "ADB (Android Debug Bridge)",
17
- command: "adb version",
18
- required: false,
19
- },
20
- {
21
- name: "Java JDK",
22
- command: "java -version",
23
- required: false,
24
- },
25
- {
26
- name: "Google Chrome",
27
- command: process.platform === "darwin"
28
- ? '"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --version'
29
- : "google-chrome --version",
30
- required: false,
31
- },
32
- ];
33
- let missingCount = 0;
34
- for (const check of checks) {
35
- try {
36
- const out = execSync(check.command, { stdio: "pipe" }).toString().trim();
37
- const firstLine = out.split("\n")[0];
38
- console.log(` \x1b[32m✓\x1b[0m ${check.name.padEnd(30)} \x1b[90m(${firstLine})\x1b[0m`);
4
+ function runCommandSafe(cmd) {
5
+ try {
6
+ const out = execSync(cmd, { stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).toString().trim();
7
+ if (out)
8
+ return out;
9
+ }
10
+ catch (e) {
11
+ const combined = `${e.stdout?.toString() || ""} ${e.stderr?.toString() || ""}`.trim();
12
+ if (combined) {
13
+ const lower = combined.toLowerCase();
14
+ if (!lower.includes("not found") &&
15
+ !lower.includes("no such file") &&
16
+ !lower.includes("cannot find") &&
17
+ !lower.includes("no java runtime present") &&
18
+ !lower.includes("command not found")) {
19
+ return combined;
20
+ }
39
21
  }
40
- catch {
41
- console.log(` \x1b[31m✗\x1b[0m ${check.name.padEnd(30)} \x1b[31m(Not found)\x1b[0m`);
42
- if (check.required) {
43
- missingCount++;
22
+ }
23
+ return null;
24
+ }
25
+ function checkChrome() {
26
+ const home = process.env.HOME || process.env.USERPROFILE || "";
27
+ const driversDir = path.join(home, ".testspectra", "drivers");
28
+ if (!fs.existsSync(driversDir)) {
29
+ return { installed: false, version: null };
30
+ }
31
+ try {
32
+ const entries = fs.readdirSync(driversDir, { recursive: true });
33
+ for (const entry of entries) {
34
+ const entryStr = String(entry);
35
+ if (entryStr.endsWith("Google Chrome for Testing") ||
36
+ entryStr.endsWith("chrome") ||
37
+ entryStr.endsWith("chrome.exe")) {
38
+ const fullPath = path.join(driversDir, entryStr);
39
+ if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
40
+ const out = runCommandSafe(`"${fullPath}" --version`);
41
+ if (out) {
42
+ return { installed: true, version: `${out.split("\n")[0].trim()} (Chrome for Testing)` };
43
+ }
44
+ // Binary exists but couldn't get version — still count as installed
45
+ return { installed: true, version: "Chrome for Testing (cached)" };
46
+ }
44
47
  }
45
48
  }
46
49
  }
47
- console.log("\n------------------------------------------------------------");
48
- if (missingCount === 0) {
49
- console.log("\x1b[32m[Doctor]\x1b[0m System ready for local test execution!");
50
+ catch { }
51
+ return { installed: false, version: null };
52
+ }
53
+ function checkWorkspacePackage(pkgName, startDir) {
54
+ let cur = path.resolve(startDir);
55
+ while (cur && cur !== path.dirname(cur)) {
56
+ const pkgJsonPath = path.join(cur, "node_modules", pkgName, "package.json");
57
+ if (fs.existsSync(pkgJsonPath)) {
58
+ try {
59
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
60
+ return { installed: true, version: `v${pkg.version || "installed"}` };
61
+ }
62
+ catch { }
63
+ }
64
+ // Also check root package.json devDependencies/dependencies
65
+ const rootPkgPath = path.join(cur, "package.json");
66
+ if (fs.existsSync(rootPkgPath)) {
67
+ try {
68
+ const rPkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
69
+ if (rPkg.name === pkgName) {
70
+ return { installed: true, version: `v${rPkg.version || "workspace"}` };
71
+ }
72
+ const depVer = rPkg.devDependencies?.[pkgName] || rPkg.dependencies?.[pkgName];
73
+ if (depVer) {
74
+ return { installed: true, version: depVer };
75
+ }
76
+ }
77
+ catch { }
78
+ }
79
+ // Check subpackages if monorepo
80
+ const cand1 = path.join(cur, "tools", pkgName.replace("@testspectra/", ""), "package.json");
81
+ const cand2 = path.join(cur, "packages", pkgName.replace("@testspectra/", ""), "package.json");
82
+ const cand3 = path.join(cur, pkgName.replace("@testspectra/", ""), "package.json");
83
+ for (const cand of [cand1, cand2, cand3]) {
84
+ if (fs.existsSync(cand)) {
85
+ try {
86
+ const pkg = JSON.parse(fs.readFileSync(cand, "utf-8"));
87
+ if (pkg.name === pkgName) {
88
+ return { installed: true, version: `v${pkg.version || "workspace"}` };
89
+ }
90
+ }
91
+ catch { }
92
+ }
93
+ }
94
+ cur = path.dirname(cur);
95
+ }
96
+ return { installed: false, version: null };
97
+ }
98
+ export async function runSystemChecks(cwd = process.cwd()) {
99
+ const dependencies = [];
100
+ // --- 1. Essential Core ---
101
+ // Node.js
102
+ const nodeOut = runCommandSafe("node -v");
103
+ dependencies.push({
104
+ name: "Node.js",
105
+ category: "core",
106
+ installed: !!nodeOut,
107
+ version: nodeOut ? nodeOut.split("\n")[0].trim() : null,
108
+ required: true,
109
+ required_version: ">=18.0.0",
110
+ depends_on: [],
111
+ });
112
+ // Bun
113
+ const bunOut = runCommandSafe("bun -v");
114
+ dependencies.push({
115
+ name: "Bun",
116
+ category: "core",
117
+ installed: !!bunOut,
118
+ version: bunOut ? `v${bunOut.split("\n")[0].trim()}` : null,
119
+ required: false,
120
+ required_version: null,
121
+ depends_on: [],
122
+ });
123
+ // Git
124
+ const gitOut = runCommandSafe("git --version");
125
+ dependencies.push({
126
+ name: "Git",
127
+ category: "core",
128
+ installed: !!gitOut,
129
+ version: gitOut ? gitOut.replace("git version", "").trim() : null,
130
+ required: false,
131
+ required_version: null,
132
+ depends_on: [],
133
+ });
134
+ // --- 2. Web Automation & Browsers ---
135
+ // Google Chrome / Chromium
136
+ const chromeCheck = checkChrome();
137
+ dependencies.push({
138
+ name: "Google Chrome",
139
+ category: "web",
140
+ installed: chromeCheck.installed,
141
+ version: chromeCheck.version,
142
+ required: true,
143
+ required_version: "Stable / CfT",
144
+ depends_on: [],
145
+ });
146
+ // WebDriverIO Core packages
147
+ const webPackages = [
148
+ { name: "WDIO CLI", pkg: "@wdio/cli", required: true },
149
+ { name: "WebDriverIO Core", pkg: "webdriverio", required: true },
150
+ { name: "WDIO Local Runner", pkg: "@wdio/local-runner", required: true },
151
+ { name: "WDIO Globals", pkg: "@wdio/globals", required: true },
152
+ { name: "WDIO Spec Reporter", pkg: "@wdio/spec-reporter", required: true },
153
+ { name: "WDIO Mocha Framework", pkg: "@wdio/mocha-framework", required: true },
154
+ { name: "WDIO DevTools Service", pkg: "@wdio/devtools-service", required: false },
155
+ { name: "TestSpectra CLI", pkg: "@testspectra/cli", required: true },
156
+ { name: "TestSpectra Matchers", pkg: "@testspectra/matchers", required: true },
157
+ ];
158
+ for (const wp of webPackages) {
159
+ const pkgCheck = checkWorkspacePackage(wp.pkg, cwd);
160
+ dependencies.push({
161
+ name: wp.name,
162
+ category: "web",
163
+ installed: pkgCheck.installed,
164
+ version: pkgCheck.version,
165
+ required: wp.required,
166
+ required_version: null,
167
+ depends_on: ["Node.js"],
168
+ });
169
+ }
170
+ // --- 3. Mobile Testing (Android) ---
171
+ const adbOut = runCommandSafe("adb version");
172
+ let adbVer = null;
173
+ if (adbOut) {
174
+ const m = adbOut.match(/Version\s+([\d.]+)/i) || adbOut.match(/Android Debug Bridge version\s+([\d.]+)/i);
175
+ adbVer = m ? `v${m[1]}` : "Installed";
176
+ }
177
+ dependencies.push({
178
+ name: "ADB (Android Debug Bridge)",
179
+ category: "mobile",
180
+ installed: !!adbOut,
181
+ version: adbVer,
182
+ required: false,
183
+ required_version: null,
184
+ depends_on: [],
185
+ });
186
+ const javaOut = runCommandSafe("java -version 2>&1");
187
+ let javaVer = null;
188
+ if (javaOut && !javaOut.toLowerCase().includes("no java runtime present") && !javaOut.toLowerCase().includes("not found")) {
189
+ const m = javaOut.match(/(?:java|openjdk)\s+version\s+"([^"]+)"/i) || javaOut.match(/(?:java|openjdk)\s+([\d.]+)/i);
190
+ javaVer = m ? `v${m[1]}` : "Installed";
191
+ }
192
+ dependencies.push({
193
+ name: "Java JDK",
194
+ category: "mobile",
195
+ installed: !!javaVer,
196
+ version: javaVer,
197
+ required: false,
198
+ required_version: ">=21.0.0",
199
+ depends_on: [],
200
+ });
201
+ const appiumOut = runCommandSafe("appium -v");
202
+ dependencies.push({
203
+ name: "Appium",
204
+ category: "mobile",
205
+ installed: !!appiumOut,
206
+ version: appiumOut ? `v${appiumOut.split("\n")[0].trim()}` : null,
207
+ required: false,
208
+ required_version: ">=2.0.0",
209
+ depends_on: ["Node.js"],
210
+ });
211
+ const missingRequired = dependencies.filter((d) => d.required && !d.installed);
212
+ const missingCount = missingRequired.length;
213
+ return {
214
+ all_ready: missingCount === 0,
215
+ missing_count: missingCount,
216
+ dependencies,
217
+ };
218
+ }
219
+ export async function doctorCommand(options = {}) {
220
+ const cwd = process.cwd();
221
+ const result = await runSystemChecks(cwd);
222
+ if (options.json) {
223
+ console.log(JSON.stringify(result, null, 2));
224
+ return;
225
+ }
226
+ console.log("\x1b[36m┌ 🩺 TestSpectra Doctor: System & Dependency Diagnostics\x1b[0m");
227
+ console.log("\x1b[36m│\x1b[0m");
228
+ const categories = [
229
+ { key: "core", title: "Essential Core Runtimes" },
230
+ { key: "web", title: "Web Automation (WebDriverIO & Browsers)" },
231
+ { key: "mobile", title: "Mobile Testing (Android & Appium)" },
232
+ ];
233
+ for (const cat of categories) {
234
+ const items = result.dependencies.filter((d) => d.category === cat.key);
235
+ if (items.length === 0)
236
+ continue;
237
+ console.log(`\x1b[36m│\x1b[0m \x1b[1m\x1b[34m${cat.title}\x1b[0m`);
238
+ for (const item of items) {
239
+ if (item.installed) {
240
+ const verStr = item.version ? `\x1b[90m(${item.version})\x1b[0m` : "";
241
+ console.log(`\x1b[36m│\x1b[0m \x1b[32m✓\x1b[0m ${item.name.padEnd(28)} ${verStr}`);
242
+ }
243
+ else if (item.required) {
244
+ console.log(`\x1b[36m│\x1b[0m \x1b[31m✗\x1b[0m ${item.name.padEnd(28)} \x1b[31m[Missing - Required]\x1b[0m`);
245
+ }
246
+ else {
247
+ console.log(`\x1b[36m│\x1b[0m \x1b[33m!\x1b[0m ${item.name.padEnd(28)} \x1b[90m(Not found - Optional)\x1b[0m`);
248
+ }
249
+ }
250
+ console.log("\x1b[36m│\x1b[0m");
251
+ }
252
+ console.log("\x1b[36m└──────────────────────────────────────────────────────────\x1b[0m");
253
+ if (result.all_ready) {
254
+ console.log("\x1b[32m🎉 [TestSpectra] System is 100% ready for Web & Local Test Execution!\x1b[0m\n");
50
255
  }
51
256
  else {
52
- console.log(`\x1b[33m[Doctor]\x1b[0m Found ${missingCount} missing required dependencies.`);
257
+ console.log(`\x1b[33m⚠️ [TestSpectra] Found ${result.missing_count} missing required dependencies.\x1b[0m`);
258
+ console.log(" Run \x1b[36mspectra doctor --fix\x1b[0m or install missing tools above.\n");
259
+ if (options.fix) {
260
+ console.log("\x1b[36m[Doctor Fix] Attempting automated resolution...\x1b[0m");
261
+ const missingPkgs = result.dependencies
262
+ .filter((d) => !d.installed && d.category === "web" && d.name.startsWith("WDIO"))
263
+ .map((d) => d.name);
264
+ if (missingPkgs.length > 0) {
265
+ console.log(`Installing workspace packages: ${missingPkgs.join(", ")}`);
266
+ try {
267
+ execSync("pnpm add -D @wdio/cli webdriverio @wdio/local-runner @wdio/globals @wdio/spec-reporter @wdio/mocha-framework @wdio/devtools-service", {
268
+ stdio: "inherit",
269
+ });
270
+ console.log("\x1b[32m✓ Installed WebDriverIO packages successfully.\x1b[0m");
271
+ }
272
+ catch (err) {
273
+ console.error("\x1b[31m✗ Failed to install packages:\x1b[0m", err.message);
274
+ }
275
+ }
276
+ }
53
277
  }
54
278
  }
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ export function createCliProgram() {
42
42
  .command("doctor")
43
43
  .description("Verify local environment prerequisites (ADB, Java, Chrome, Bun, Node)")
44
44
  .option("--fix", "Attempt automatic fix / download of missing tools")
45
+ .option("--json", "Output diagnostic results as JSON")
45
46
  .action(doctorCommand);
46
47
  program
47
48
  .command("devices")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testspectra/cli",
3
- "version": "1.0.65",
3
+ "version": "1.0.67",
4
4
  "description": "TestSpectra Zero-Config Cross-Platform Test Runner CLI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -30,7 +30,7 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@clack/prompts": "^1.7.0",
33
- "@testspectra/matchers": "^1.0.65",
33
+ "@testspectra/matchers": "^1.0.67",
34
34
  "chalk": "^5.3.0",
35
35
  "commander": "^12.1.0",
36
36
  "dotenv": "^16.4.5",
@@ -8,8 +8,8 @@
8
8
  "type-check": "tsc -b"
9
9
  },
10
10
  "devDependencies": {
11
- "@testspectra/cli": "^1.0.65",
12
- "@testspectra/matchers": "^1.0.65",
11
+ "@testspectra/cli": "^1.0.67",
12
+ "@testspectra/matchers": "^1.0.67",
13
13
  "@types/node": "^20.14.0",
14
14
  "@wdio/cli": "^9.2.8",
15
15
  "@wdio/local-runner": "^9.2.8",
@@ -10,8 +10,8 @@
10
10
  "build": "nx run-many -t build"
11
11
  },
12
12
  "devDependencies": {
13
- "@testspectra/cli": "^1.0.65",
14
- "@testspectra/matchers": "^1.0.65",
13
+ "@testspectra/cli": "^1.0.67",
14
+ "@testspectra/matchers": "^1.0.67",
15
15
  "@types/node": "^20.14.0",
16
16
  "@wdio/cli": "^9.2.8",
17
17
  "@wdio/local-runner": "^9.2.8",