greenrun-cli 0.2.9 → 0.2.10
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/api-client.d.ts +2 -4
- package/dist/api-client.js +23 -36
- package/dist/commands/init.js +80 -11
- package/package.json +1 -1
package/dist/api-client.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export declare class ApiClient {
|
|
|
50
50
|
name?: string;
|
|
51
51
|
}): Promise<unknown>;
|
|
52
52
|
deletePage(id: string): Promise<unknown>;
|
|
53
|
-
listTests(projectId: string): Promise<unknown>;
|
|
53
|
+
listTests(projectId: string, compact?: boolean): Promise<unknown>;
|
|
54
54
|
createTest(projectId: string, data: {
|
|
55
55
|
name: string;
|
|
56
56
|
instructions: string;
|
|
@@ -98,12 +98,10 @@ export declare class ApiClient {
|
|
|
98
98
|
test_id: any;
|
|
99
99
|
test_name: any;
|
|
100
100
|
run_id: any;
|
|
101
|
-
instructions: any;
|
|
102
101
|
credential_name: any;
|
|
103
102
|
pages: any;
|
|
104
103
|
tags: any;
|
|
105
|
-
|
|
106
|
-
script_generated_at: any;
|
|
104
|
+
has_script: any;
|
|
107
105
|
}[];
|
|
108
106
|
}>;
|
|
109
107
|
}
|
package/dist/api-client.js
CHANGED
|
@@ -61,8 +61,9 @@ export class ApiClient {
|
|
|
61
61
|
return this.request('DELETE', `/pages/${id}`);
|
|
62
62
|
}
|
|
63
63
|
// Tests
|
|
64
|
-
async listTests(projectId) {
|
|
65
|
-
|
|
64
|
+
async listTests(projectId, compact) {
|
|
65
|
+
const query = compact ? '?compact=1' : '';
|
|
66
|
+
return this.request('GET', `/projects/${projectId}/tests${query}`);
|
|
66
67
|
}
|
|
67
68
|
async createTest(projectId, data) {
|
|
68
69
|
return this.request('POST', `/projects/${projectId}/tests`, data);
|
|
@@ -106,7 +107,7 @@ export class ApiClient {
|
|
|
106
107
|
async prepareTestBatch(projectId, filter, testIds) {
|
|
107
108
|
const [projectResult, testsResult] = await Promise.all([
|
|
108
109
|
this.getProject(projectId),
|
|
109
|
-
this.listTests(projectId),
|
|
110
|
+
this.listTests(projectId, true),
|
|
110
111
|
]);
|
|
111
112
|
const project = projectResult.project;
|
|
112
113
|
let tests = (testsResult.tests || []).filter((t) => t.status === 'active');
|
|
@@ -127,44 +128,30 @@ export class ApiClient {
|
|
|
127
128
|
tests = tests.filter((t) => (t.name || '').toLowerCase().includes(term));
|
|
128
129
|
}
|
|
129
130
|
}
|
|
131
|
+
const projectSummary = {
|
|
132
|
+
id: project.id, name: project.name, base_url: project.base_url,
|
|
133
|
+
auth_mode: project.auth_mode ?? 'none',
|
|
134
|
+
login_url: project.login_url ?? null,
|
|
135
|
+
register_url: project.register_url ?? null,
|
|
136
|
+
login_instructions: project.login_instructions ?? null,
|
|
137
|
+
register_instructions: project.register_instructions ?? null,
|
|
138
|
+
credentials: project.credentials ?? null,
|
|
139
|
+
};
|
|
130
140
|
if (tests.length === 0) {
|
|
131
|
-
return {
|
|
132
|
-
project: {
|
|
133
|
-
id: project.id, name: project.name, base_url: project.base_url,
|
|
134
|
-
auth_mode: project.auth_mode ?? 'none',
|
|
135
|
-
login_url: project.login_url ?? null,
|
|
136
|
-
register_url: project.register_url ?? null,
|
|
137
|
-
login_instructions: project.login_instructions ?? null,
|
|
138
|
-
register_instructions: project.register_instructions ?? null,
|
|
139
|
-
credentials: project.credentials ?? null,
|
|
140
|
-
},
|
|
141
|
-
tests: [],
|
|
142
|
-
};
|
|
141
|
+
return { project: projectSummary, tests: [] };
|
|
143
142
|
}
|
|
144
|
-
//
|
|
145
|
-
const fullTests = await Promise.all(tests.map((t) => this.getTest(t.id)));
|
|
146
|
-
// Start runs in parallel
|
|
143
|
+
// Start runs in parallel (listTests already has full details, no need for getTest)
|
|
147
144
|
const runs = await Promise.all(tests.map((t) => this.startRun(t.id)));
|
|
148
145
|
return {
|
|
149
|
-
project:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
register_url: project.register_url ?? null,
|
|
154
|
-
login_instructions: project.login_instructions ?? null,
|
|
155
|
-
register_instructions: project.register_instructions ?? null,
|
|
156
|
-
credentials: project.credentials ?? null,
|
|
157
|
-
},
|
|
158
|
-
tests: fullTests.map((ft, i) => ({
|
|
159
|
-
test_id: ft.test.id,
|
|
160
|
-
test_name: ft.test.name,
|
|
146
|
+
project: projectSummary,
|
|
147
|
+
tests: tests.map((t, i) => ({
|
|
148
|
+
test_id: t.id,
|
|
149
|
+
test_name: t.name,
|
|
161
150
|
run_id: runs[i].run.id,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
script: ft.test.script ?? null,
|
|
167
|
-
script_generated_at: ft.test.script_generated_at ?? null,
|
|
151
|
+
credential_name: t.credential_name ?? null,
|
|
152
|
+
pages: (t.pages || []).map((p) => ({ id: p.id, url: p.url })),
|
|
153
|
+
tags: (t.tags || []).map((tg) => tg.name || tg),
|
|
154
|
+
has_script: t.has_script ?? !!t.script,
|
|
168
155
|
})),
|
|
169
156
|
};
|
|
170
157
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -36,6 +36,43 @@ function prompt(rl, question) {
|
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
|
+
function detectSystemChrome() {
|
|
40
|
+
const platform = process.platform;
|
|
41
|
+
if (platform === 'darwin') {
|
|
42
|
+
return existsSync('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome');
|
|
43
|
+
}
|
|
44
|
+
if (platform === 'win32') {
|
|
45
|
+
const dirs = [process.env['PROGRAMFILES'], process.env['PROGRAMFILES(X86)'], process.env['LOCALAPPDATA']];
|
|
46
|
+
return dirs.some(dir => dir && existsSync(join(dir, 'Google', 'Chrome', 'Application', 'chrome.exe')));
|
|
47
|
+
}
|
|
48
|
+
// Linux
|
|
49
|
+
try {
|
|
50
|
+
execSync('which google-chrome-stable || which google-chrome || which chromium-browser || which chromium', { stdio: 'pipe' });
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function installPlaywrightChromium() {
|
|
58
|
+
try {
|
|
59
|
+
console.log(' Installing @playwright/test (this may take a minute)...');
|
|
60
|
+
execSync('npm install -g @playwright/test@latest', { stdio: 'inherit' });
|
|
61
|
+
console.log(' Installing Chromium browser...');
|
|
62
|
+
execSync('npx playwright install --with-deps chromium', { stdio: 'inherit' });
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
console.error(' Failed to install Playwright. You can install manually:');
|
|
67
|
+
console.error(' npm install -g @playwright/test@latest');
|
|
68
|
+
console.error(' npx playwright install --with-deps chromium\n');
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function checkNodeVersion() {
|
|
73
|
+
const match = process.version.match(/^v(\d+)\./);
|
|
74
|
+
return match ? parseInt(match[1], 10) >= 18 : false;
|
|
75
|
+
}
|
|
39
76
|
function checkPrerequisites() {
|
|
40
77
|
let claude = false;
|
|
41
78
|
try {
|
|
@@ -55,14 +92,15 @@ async function validateToken(token) {
|
|
|
55
92
|
'Accept': 'application/json',
|
|
56
93
|
},
|
|
57
94
|
});
|
|
58
|
-
if (!response.ok)
|
|
59
|
-
return { valid: false };
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
return { valid: false, error: `API returned HTTP ${response.status}` };
|
|
97
|
+
}
|
|
60
98
|
const data = await response.json();
|
|
61
99
|
const projects = Array.isArray(data) ? data : (data.data ?? []);
|
|
62
100
|
return { valid: true, projectCount: projects.length };
|
|
63
101
|
}
|
|
64
|
-
catch {
|
|
65
|
-
return { valid: false };
|
|
102
|
+
catch (err) {
|
|
103
|
+
return { valid: false, error: err?.message || String(err) };
|
|
66
104
|
}
|
|
67
105
|
}
|
|
68
106
|
function getClaudeConfigPath() {
|
|
@@ -107,24 +145,24 @@ function configureMcpLocal(token) {
|
|
|
107
145
|
console.error(` claude mcp add greenrun --transport stdio -e GREENRUN_API_TOKEN=${token} -- npx -y greenrun-cli@latest\n`);
|
|
108
146
|
}
|
|
109
147
|
}
|
|
110
|
-
function configurePlaywrightMcp() {
|
|
148
|
+
function configurePlaywrightMcp(browser = 'chrome') {
|
|
111
149
|
try {
|
|
112
150
|
setLocalMcpServer('playwright', {
|
|
113
151
|
type: 'stdio',
|
|
114
152
|
command: 'npx',
|
|
115
153
|
args: [
|
|
116
154
|
'@playwright/mcp@latest',
|
|
117
|
-
'--browser',
|
|
155
|
+
'--browser', browser,
|
|
118
156
|
'--user-data-dir', join(homedir(), '.greenrun', 'browser-profile'),
|
|
119
157
|
],
|
|
120
158
|
env: {},
|
|
121
159
|
});
|
|
122
|
-
console.log(
|
|
160
|
+
console.log(` Configured playwright MCP server (${browser})`);
|
|
123
161
|
}
|
|
124
162
|
catch {
|
|
125
163
|
console.error('\nFailed to write Playwright MCP config to ~/.claude.json');
|
|
126
164
|
console.error('You can add it manually:\n');
|
|
127
|
-
console.error(
|
|
165
|
+
console.error(` claude mcp add playwright -- npx @playwright/mcp@latest --browser ${browser} --user-data-dir ~/.greenrun/browser-profile\n`);
|
|
128
166
|
}
|
|
129
167
|
}
|
|
130
168
|
function configureMcpProject(token) {
|
|
@@ -287,6 +325,12 @@ export async function runInit(args) {
|
|
|
287
325
|
const opts = parseFlags(args);
|
|
288
326
|
const interactive = !opts.token;
|
|
289
327
|
console.log('\nGreenrun - Browser Test Management for Claude Code\n');
|
|
328
|
+
// Node version gate
|
|
329
|
+
if (!checkNodeVersion()) {
|
|
330
|
+
console.error(`Error: Node.js 18 or later is required (detected ${process.version}).`);
|
|
331
|
+
console.error('Install a newer version: https://nodejs.org/\n');
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
290
334
|
// Prerequisites
|
|
291
335
|
console.log('Prerequisites:');
|
|
292
336
|
const prereqs = checkPrerequisites();
|
|
@@ -318,7 +362,7 @@ export async function runInit(args) {
|
|
|
318
362
|
process.stdout.write(' Validating... ');
|
|
319
363
|
const validation = await validateToken(token);
|
|
320
364
|
if (!validation.valid) {
|
|
321
|
-
console.log(
|
|
365
|
+
console.log(`Failed! ${validation.error || 'Invalid token or cannot reach the API.'}`);
|
|
322
366
|
rl.close();
|
|
323
367
|
process.exit(1);
|
|
324
368
|
}
|
|
@@ -348,12 +392,37 @@ export async function runInit(args) {
|
|
|
348
392
|
process.stdout.write('Validating token... ');
|
|
349
393
|
const validation = await validateToken(token);
|
|
350
394
|
if (!validation.valid) {
|
|
351
|
-
console.log(
|
|
395
|
+
console.log(`Failed! ${validation.error || 'Invalid token or cannot reach the API.'}`);
|
|
352
396
|
process.exit(1);
|
|
353
397
|
}
|
|
354
398
|
console.log(`Connected! (${validation.projectCount} project${validation.projectCount === 1 ? '' : 's'} found)`);
|
|
355
399
|
scope = scope || 'local';
|
|
356
400
|
}
|
|
401
|
+
// Detect browser
|
|
402
|
+
let browser = 'chrome';
|
|
403
|
+
if (!detectSystemChrome()) {
|
|
404
|
+
if (interactive) {
|
|
405
|
+
const rl2 = createInterface({ input: process.stdin, output: process.stdout });
|
|
406
|
+
console.log('Chrome not detected on this system.');
|
|
407
|
+
const installChoice = await prompt(rl2, ' Install Playwright Chromium? [Y/n]: ');
|
|
408
|
+
rl2.close();
|
|
409
|
+
if (installChoice.toLowerCase() !== 'n') {
|
|
410
|
+
if (installPlaywrightChromium()) {
|
|
411
|
+
browser = 'chromium';
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
console.log(' Continuing with chrome config. You can install Chrome manually later.\n');
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
console.log('Chrome not detected. Installing Playwright Chromium...');
|
|
420
|
+
if (installPlaywrightChromium()) {
|
|
421
|
+
browser = 'chromium';
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
console.log();
|
|
425
|
+
}
|
|
357
426
|
// Configure MCP
|
|
358
427
|
console.log('Configuring MCP servers...');
|
|
359
428
|
if (scope === 'project') {
|
|
@@ -362,7 +431,7 @@ export async function runInit(args) {
|
|
|
362
431
|
else {
|
|
363
432
|
configureMcpLocal(token);
|
|
364
433
|
}
|
|
365
|
-
configurePlaywrightMcp();
|
|
434
|
+
configurePlaywrightMcp(browser);
|
|
366
435
|
console.log(' MCP servers configured.\n');
|
|
367
436
|
// Install extras
|
|
368
437
|
if (opts.claudeMd) {
|