@regressionproof/cli 0.7.3 → 0.8.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/build/cli.js +44 -0
- package/build/commands/invite/CreateInvite.js +3 -3
- package/build/components/Doctor.d.ts +7 -0
- package/build/components/Doctor.js +49 -0
- package/build/components/Doctor.tsx +75 -0
- package/build/config/ConfigManager.d.ts +9 -0
- package/build/config/ConfigManager.js +15 -0
- package/build/doctor/Doctor.d.ts +7 -0
- package/build/doctor/Doctor.js +23 -0
- package/build/doctor/DoctorContext.d.ts +12 -0
- package/build/doctor/DoctorContext.js +44 -0
- package/build/doctor/DoctorOutput.d.ts +6 -0
- package/build/doctor/DoctorOutput.js +32 -0
- package/build/doctor/DoctorResult.d.ts +8 -0
- package/build/doctor/DoctorResult.js +1 -0
- package/build/doctor/DoctorRunner.d.ts +7 -0
- package/build/doctor/DoctorRunner.js +9 -0
- package/build/doctor/checks/CredentialsCheck.d.ts +6 -0
- package/build/doctor/checks/CredentialsCheck.js +55 -0
- package/build/doctor/checks/JestReporterCheck.d.ts +5 -0
- package/build/doctor/checks/JestReporterCheck.js +35 -0
- package/build/doctor/checks/LocalConfigCheck.d.ts +5 -0
- package/build/doctor/checks/LocalConfigCheck.js +17 -0
- package/build/doctor/checks/MirrorAccessCheck.d.ts +9 -0
- package/build/doctor/checks/MirrorAccessCheck.js +129 -0
- package/build/esm/cli.js +44 -0
- package/build/esm/commands/invite/CreateInvite.js +3 -3
- package/build/esm/components/Doctor.d.ts +7 -0
- package/build/esm/components/Doctor.js +49 -0
- package/build/esm/config/ConfigManager.d.ts +9 -0
- package/build/esm/config/ConfigManager.js +16 -0
- package/build/esm/doctor/Doctor.d.ts +7 -0
- package/build/esm/doctor/Doctor.js +33 -0
- package/build/esm/doctor/DoctorContext.d.ts +12 -0
- package/build/esm/doctor/DoctorContext.js +41 -0
- package/build/esm/doctor/DoctorOutput.d.ts +6 -0
- package/build/esm/doctor/DoctorOutput.js +32 -0
- package/build/esm/doctor/DoctorResult.d.ts +8 -0
- package/build/esm/doctor/DoctorResult.js +1 -0
- package/build/esm/doctor/DoctorRunner.d.ts +7 -0
- package/build/esm/doctor/DoctorRunner.js +21 -0
- package/build/esm/doctor/checks/CredentialsCheck.d.ts +6 -0
- package/build/esm/doctor/checks/CredentialsCheck.js +66 -0
- package/build/esm/doctor/checks/JestReporterCheck.d.ts +5 -0
- package/build/esm/doctor/checks/JestReporterCheck.js +46 -0
- package/build/esm/doctor/checks/LocalConfigCheck.d.ts +5 -0
- package/build/esm/doctor/checks/LocalConfigCheck.js +28 -0
- package/build/esm/doctor/checks/MirrorAccessCheck.d.ts +9 -0
- package/build/esm/doctor/checks/MirrorAccessCheck.js +141 -0
- package/build/esm/jest/JestConfigurator.js +9 -0
- package/build/esm/jest/JestReporterConfigInspector.d.ts +13 -0
- package/build/esm/jest/JestReporterConfigInspector.js +60 -0
- package/build/jest/JestConfigurator.js +9 -0
- package/build/jest/JestReporterConfigInspector.d.ts +13 -0
- package/build/jest/JestReporterConfigInspector.js +56 -0
- package/package.json +3 -3
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import ConfigManager from '../../config/ConfigManager.js';
|
|
4
|
+
export default class MirrorAccessCheck {
|
|
5
|
+
async run(context) {
|
|
6
|
+
if (!context.projectName) {
|
|
7
|
+
return {
|
|
8
|
+
name: 'Mirror directory',
|
|
9
|
+
status: 'warn',
|
|
10
|
+
details: [
|
|
11
|
+
'Unable to resolve project name from .regressionproof.json.',
|
|
12
|
+
],
|
|
13
|
+
fix: 'Run `npx regressionproof init` from the project root.',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const configManager = new ConfigManager();
|
|
17
|
+
const mirrorPath = context.mirrorPath;
|
|
18
|
+
const credentials = configManager.loadCredentials(context.projectName);
|
|
19
|
+
if (!mirrorPath || !fs.existsSync(mirrorPath)) {
|
|
20
|
+
return {
|
|
21
|
+
name: 'Mirror directory',
|
|
22
|
+
status: 'warn',
|
|
23
|
+
details: [
|
|
24
|
+
`Mirror directory not found at ${mirrorPath ??
|
|
25
|
+
configManager.getConfigDir(context.projectName)}.`,
|
|
26
|
+
'This is normal before the first snapshot is created.',
|
|
27
|
+
],
|
|
28
|
+
fix: 'Run your tests to create the first snapshot.',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (!credentials) {
|
|
32
|
+
return {
|
|
33
|
+
name: 'Mirror directory',
|
|
34
|
+
status: 'warn',
|
|
35
|
+
details: ['Credentials not found; remote access not checked.'],
|
|
36
|
+
fix: 'Run `npx regressionproof invite accept <token>` (teammate) or `npx regressionproof init` (owner).',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const authedUrl = credentials.url.replace('://', `://${credentials.token}@`);
|
|
40
|
+
const pullResult = this.checkPullAccess(authedUrl);
|
|
41
|
+
if (!pullResult.ok) {
|
|
42
|
+
return {
|
|
43
|
+
name: 'Mirror directory',
|
|
44
|
+
status: 'fail',
|
|
45
|
+
details: [
|
|
46
|
+
'Unable to access remote (pull).',
|
|
47
|
+
pullResult.message ?? 'Unknown error.',
|
|
48
|
+
],
|
|
49
|
+
fix: 'Run `npx regressionproof invite accept <token>` to refresh credentials.',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const pushResult = this.checkPushAccess(mirrorPath, authedUrl);
|
|
53
|
+
if (pushResult.status === 'fail') {
|
|
54
|
+
return {
|
|
55
|
+
name: 'Mirror directory',
|
|
56
|
+
status: 'fail',
|
|
57
|
+
details: [
|
|
58
|
+
'Unable to access remote (push).',
|
|
59
|
+
pushResult.message ?? 'Unknown error.',
|
|
60
|
+
],
|
|
61
|
+
fix: 'Run `npx regressionproof invite accept <token>` to refresh credentials.',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (pushResult.status === 'warn') {
|
|
65
|
+
return {
|
|
66
|
+
name: 'Mirror directory',
|
|
67
|
+
status: 'warn',
|
|
68
|
+
details: [
|
|
69
|
+
'Remote access confirmed (pull).',
|
|
70
|
+
pushResult.message ?? 'Unknown error.',
|
|
71
|
+
],
|
|
72
|
+
fix: 'Run your tests to create the first snapshot.',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
name: 'Mirror directory',
|
|
77
|
+
status: 'ok',
|
|
78
|
+
details: [
|
|
79
|
+
`Mirror directory exists at ${mirrorPath}.`,
|
|
80
|
+
'Remote access confirmed (pull/push).',
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
checkPullAccess(url) {
|
|
85
|
+
try {
|
|
86
|
+
execSync(`git ls-remote "${url}"`, { stdio: 'pipe' });
|
|
87
|
+
return { ok: true };
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
message: this.getErrorMessage(err),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
checkPushAccess(mirrorPath, url) {
|
|
97
|
+
try {
|
|
98
|
+
execSync(`git -C "${mirrorPath}" push --dry-run "${url}" HEAD`, {
|
|
99
|
+
stdio: 'pipe',
|
|
100
|
+
});
|
|
101
|
+
return { status: 'ok' };
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
const message = this.getErrorMessage(err);
|
|
105
|
+
if (this.isNoCommitsError(message)) {
|
|
106
|
+
return {
|
|
107
|
+
status: 'warn',
|
|
108
|
+
message: 'No commits in mirror; push check skipped.',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return { status: 'fail', message };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
isNoCommitsError(message) {
|
|
115
|
+
const normalized = message.toLowerCase();
|
|
116
|
+
return (normalized.includes('src refspec') ||
|
|
117
|
+
normalized.includes('does not match any') ||
|
|
118
|
+
normalized.includes('no commits'));
|
|
119
|
+
}
|
|
120
|
+
getErrorMessage(err) {
|
|
121
|
+
if (err && typeof err === 'object') {
|
|
122
|
+
const error = err;
|
|
123
|
+
return (error.stderr?.toString().trim() ||
|
|
124
|
+
error.stdout?.toString().trim() ||
|
|
125
|
+
error.message);
|
|
126
|
+
}
|
|
127
|
+
return String(err);
|
|
128
|
+
}
|
|
129
|
+
}
|
package/build/esm/cli.js
CHANGED
|
@@ -6,7 +6,10 @@ import acceptInvite from './commands/invite/AcceptInvite.js.js';
|
|
|
6
6
|
import createInvite from './commands/invite/CreateInvite.js.js';
|
|
7
7
|
import listInvites from './commands/invite/ListInvites.js.js';
|
|
8
8
|
import revokeInvite from './commands/invite/RevokeInvite.js.js';
|
|
9
|
+
import Doctor from './components/Doctor.js.js';
|
|
9
10
|
import Init from './components/Init.js.js';
|
|
11
|
+
import DoctorOutput from './doctor/DoctorOutput.js.js';
|
|
12
|
+
import DoctorRunner from './doctor/DoctorRunner.js.js';
|
|
10
13
|
const command = process.argv[2];
|
|
11
14
|
const projectNameArg = process.argv[3];
|
|
12
15
|
if (command === 'init') {
|
|
@@ -42,11 +45,52 @@ else if (command === 'invite') {
|
|
|
42
45
|
process.exit(1);
|
|
43
46
|
}
|
|
44
47
|
}
|
|
48
|
+
else if (command === 'doctor') {
|
|
49
|
+
const options = parseDoctorArgs(process.argv.slice(3));
|
|
50
|
+
void DoctorRunner.run({ cwd: options.cwd })
|
|
51
|
+
.then((results) => {
|
|
52
|
+
if (options.json) {
|
|
53
|
+
console.log(JSON.stringify(results, null, 2));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const { waitUntilExit } = render(React.createElement(Doctor, { results }));
|
|
57
|
+
void waitUntilExit().then(() => {
|
|
58
|
+
process.exit(DoctorOutput.exitCode(results));
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
process.exit(DoctorOutput.exitCode(results));
|
|
63
|
+
})
|
|
64
|
+
.catch((err) => {
|
|
65
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
66
|
+
console.error(message);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
45
70
|
else {
|
|
46
71
|
console.log('Usage: regressionproof <command>');
|
|
47
72
|
console.log('');
|
|
48
73
|
console.log('Commands:');
|
|
49
74
|
console.log(' init [projectName] Initialize a new project');
|
|
50
75
|
console.log(' invite ... Manage project invites');
|
|
76
|
+
console.log(' doctor Check project configuration');
|
|
51
77
|
process.exit(1);
|
|
52
78
|
}
|
|
79
|
+
function parseDoctorArgs(args) {
|
|
80
|
+
const options = { json: false };
|
|
81
|
+
for (let i = 0; i < args.length; i++) {
|
|
82
|
+
const arg = args[i];
|
|
83
|
+
if (arg === '--json') {
|
|
84
|
+
options.json = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (arg === '--cwd') {
|
|
88
|
+
const value = args[i + 1];
|
|
89
|
+
if (value) {
|
|
90
|
+
options.cwd = value;
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return options;
|
|
96
|
+
}
|
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
var _a;
|
|
11
11
|
import ConfigManager from '../../config/ConfigManager.js.js';
|
|
12
|
-
import {
|
|
12
|
+
import { toSlug } from '../../utilities/slug.js.js';
|
|
13
13
|
const API_URL = (_a = process.env.REGRESSIONPROOF_API_URL) !== null && _a !== void 0 ? _a : 'https://api.regressionproof.ai';
|
|
14
14
|
class InviteCreator {
|
|
15
15
|
constructor(configManager = new ConfigManager()) {
|
|
@@ -40,9 +40,9 @@ class InviteCreator {
|
|
|
40
40
|
}
|
|
41
41
|
resolveProjectName(projectNameArg) {
|
|
42
42
|
const provided = projectNameArg ? toSlug(projectNameArg) : '';
|
|
43
|
-
const name = provided ||
|
|
43
|
+
const name = provided || this.configManager.getLocalProjectName();
|
|
44
44
|
if (!name) {
|
|
45
|
-
throw new Error('Project name is required. Provide it explicitly or ensure
|
|
45
|
+
throw new Error('Project name is required. Provide it explicitly or ensure .regressionproof.json exists.');
|
|
46
46
|
}
|
|
47
47
|
return name;
|
|
48
48
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Box, Text } from 'ink';
|
|
2
|
+
import BigText from 'ink-big-text';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
class DoctorComponent extends React.Component {
|
|
5
|
+
render() {
|
|
6
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
7
|
+
React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
8
|
+
React.createElement(BigText, { text: "regressionproof.ai", font: "tiny", colors: ['magenta', 'cyan'] }),
|
|
9
|
+
React.createElement(Text, { color: "gray" }, "Teaching LLMs to write better code.")),
|
|
10
|
+
React.createElement(Box, { flexDirection: "column", paddingX: 1, paddingBottom: 1 },
|
|
11
|
+
React.createElement(Text, { bold: true }, "RegressionProof Doctor"),
|
|
12
|
+
this.props.results.map((result) => (React.createElement(Box, { key: result.name, flexDirection: "column" },
|
|
13
|
+
React.createElement(Box, null,
|
|
14
|
+
React.createElement(Text, { color: this.colorFor(result.status) }, this.labelFor(result.status)),
|
|
15
|
+
React.createElement(Text, null,
|
|
16
|
+
" ",
|
|
17
|
+
result.name)),
|
|
18
|
+
result.details.map((detail) => (React.createElement(Text, { key: detail },
|
|
19
|
+
" ",
|
|
20
|
+
detail))),
|
|
21
|
+
result.fix ? (React.createElement(Text, null,
|
|
22
|
+
" Fix: ",
|
|
23
|
+
result.fix)) : null,
|
|
24
|
+
React.createElement(Text, null, " ")))))));
|
|
25
|
+
}
|
|
26
|
+
labelFor(status) {
|
|
27
|
+
switch (status) {
|
|
28
|
+
case 'ok':
|
|
29
|
+
return 'OK';
|
|
30
|
+
case 'warn':
|
|
31
|
+
return 'WARN';
|
|
32
|
+
case 'fail':
|
|
33
|
+
return 'FAIL';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
colorFor(status) {
|
|
37
|
+
switch (status) {
|
|
38
|
+
case 'ok':
|
|
39
|
+
return 'green';
|
|
40
|
+
case 'warn':
|
|
41
|
+
return 'yellow';
|
|
42
|
+
case 'fail':
|
|
43
|
+
return 'red';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export default function Doctor(props) {
|
|
48
|
+
return React.createElement(DoctorComponent, { results: props.results });
|
|
49
|
+
}
|
|
@@ -5,6 +5,8 @@ export default class ConfigManager {
|
|
|
5
5
|
saveCredentials(projectName: string, credentials: Credentials): void;
|
|
6
6
|
loadCredentials(projectName: string): Credentials | null;
|
|
7
7
|
writeLocalConfig(projectName: string, url: string): void;
|
|
8
|
+
readLocalConfig(cwd?: string): LocalConfig | null;
|
|
9
|
+
getLocalProjectName(cwd?: string): string | null;
|
|
8
10
|
}
|
|
9
11
|
export interface Credentials {
|
|
10
12
|
url: string;
|
|
@@ -16,3 +18,10 @@ export interface ProjectConfig {
|
|
|
16
18
|
token: string;
|
|
17
19
|
};
|
|
18
20
|
}
|
|
21
|
+
export interface LocalConfig {
|
|
22
|
+
version?: string;
|
|
23
|
+
projectName?: string;
|
|
24
|
+
remote?: {
|
|
25
|
+
url?: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -43,4 +43,20 @@ export default class ConfigManager {
|
|
|
43
43
|
};
|
|
44
44
|
fs.writeFileSync(configPath, JSON.stringify(payload, null, 2));
|
|
45
45
|
}
|
|
46
|
+
readLocalConfig(cwd = process.cwd()) {
|
|
47
|
+
const configPath = path.join(cwd, '.regressionproof.json');
|
|
48
|
+
if (!fs.existsSync(configPath)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
53
|
+
}
|
|
54
|
+
catch (_a) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
getLocalProjectName(cwd = process.cwd()) {
|
|
59
|
+
var _a, _b;
|
|
60
|
+
return (_b = (_a = this.readLocalConfig(cwd)) === null || _a === void 0 ? void 0 : _a.projectName) !== null && _b !== void 0 ? _b : null;
|
|
61
|
+
}
|
|
46
62
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import CredentialsCheck from './checks/CredentialsCheck.js.js';
|
|
11
|
+
import JestReporterCheck from './checks/JestReporterCheck.js.js';
|
|
12
|
+
import LocalConfigCheck from './checks/LocalConfigCheck.js.js';
|
|
13
|
+
import MirrorAccessCheck from './checks/MirrorAccessCheck.js.js';
|
|
14
|
+
export default class Doctor {
|
|
15
|
+
constructor(context) {
|
|
16
|
+
this.context = context;
|
|
17
|
+
}
|
|
18
|
+
run() {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
const checks = [
|
|
21
|
+
new LocalConfigCheck(),
|
|
22
|
+
new JestReporterCheck(),
|
|
23
|
+
new CredentialsCheck(),
|
|
24
|
+
new MirrorAccessCheck(),
|
|
25
|
+
];
|
|
26
|
+
const results = [];
|
|
27
|
+
for (const check of checks) {
|
|
28
|
+
results.push(yield check.run(this.context));
|
|
29
|
+
}
|
|
30
|
+
return results;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default class DoctorContext {
|
|
2
|
+
cwd: string;
|
|
3
|
+
projectName: string | null;
|
|
4
|
+
localConfigPath: string | null;
|
|
5
|
+
homeConfigDir: string;
|
|
6
|
+
constructor(cwd: string, projectName: string | null, localConfigPath: string | null, homeConfigDir: string);
|
|
7
|
+
static fromCwd(cwd: string): DoctorContext;
|
|
8
|
+
get configDir(): string | null;
|
|
9
|
+
get credentialsPath(): string | null;
|
|
10
|
+
get mirrorPath(): string | null;
|
|
11
|
+
private static readProjectName;
|
|
12
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
export default class DoctorContext {
|
|
5
|
+
constructor(cwd, projectName, localConfigPath, homeConfigDir) {
|
|
6
|
+
this.cwd = cwd;
|
|
7
|
+
this.projectName = projectName;
|
|
8
|
+
this.localConfigPath = localConfigPath;
|
|
9
|
+
this.homeConfigDir = homeConfigDir;
|
|
10
|
+
}
|
|
11
|
+
static fromCwd(cwd) {
|
|
12
|
+
const localConfigPath = path.join(cwd, '.regressionproof.json');
|
|
13
|
+
const localExists = fs.existsSync(localConfigPath);
|
|
14
|
+
const projectName = localExists
|
|
15
|
+
? DoctorContext.readProjectName(localConfigPath)
|
|
16
|
+
: null;
|
|
17
|
+
const homeConfigDir = path.join(os.homedir(), '.regressionproof');
|
|
18
|
+
return new DoctorContext(cwd, projectName, localExists ? localConfigPath : null, homeConfigDir);
|
|
19
|
+
}
|
|
20
|
+
get configDir() {
|
|
21
|
+
return this.projectName
|
|
22
|
+
? path.join(this.homeConfigDir, this.projectName)
|
|
23
|
+
: null;
|
|
24
|
+
}
|
|
25
|
+
get credentialsPath() {
|
|
26
|
+
return this.configDir ? path.join(this.configDir, 'config.json') : null;
|
|
27
|
+
}
|
|
28
|
+
get mirrorPath() {
|
|
29
|
+
return this.configDir ? path.join(this.configDir, 'mirror') : null;
|
|
30
|
+
}
|
|
31
|
+
static readProjectName(localConfigPath) {
|
|
32
|
+
var _a;
|
|
33
|
+
try {
|
|
34
|
+
const raw = JSON.parse(fs.readFileSync(localConfigPath, 'utf-8'));
|
|
35
|
+
return (_a = raw.projectName) !== null && _a !== void 0 ? _a : null;
|
|
36
|
+
}
|
|
37
|
+
catch (_b) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export default class DoctorOutput {
|
|
2
|
+
static print(results) {
|
|
3
|
+
console.log('===================================');
|
|
4
|
+
console.log(' RegressionProof Doctor');
|
|
5
|
+
console.log('===================================');
|
|
6
|
+
console.log('');
|
|
7
|
+
for (const result of results) {
|
|
8
|
+
const label = DoctorOutput.formatStatus(result.status);
|
|
9
|
+
console.log(`${label} ${result.name}`);
|
|
10
|
+
for (const detail of result.details) {
|
|
11
|
+
console.log(` ${detail}`);
|
|
12
|
+
}
|
|
13
|
+
if (result.fix) {
|
|
14
|
+
console.log(` Fix: ${result.fix}`);
|
|
15
|
+
}
|
|
16
|
+
console.log('');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
static exitCode(results) {
|
|
20
|
+
return results.some((result) => result.status === 'fail') ? 1 : 0;
|
|
21
|
+
}
|
|
22
|
+
static formatStatus(status) {
|
|
23
|
+
switch (status) {
|
|
24
|
+
case 'ok':
|
|
25
|
+
return 'OK ';
|
|
26
|
+
case 'warn':
|
|
27
|
+
return 'WARN';
|
|
28
|
+
case 'fail':
|
|
29
|
+
return 'FAIL';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import Doctor from './Doctor.js.js';
|
|
11
|
+
import DoctorContext from './DoctorContext.js.js';
|
|
12
|
+
export default class DoctorRunner {
|
|
13
|
+
static run(options) {
|
|
14
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
var _a;
|
|
16
|
+
const cwd = (_a = options === null || options === void 0 ? void 0 : options.cwd) !== null && _a !== void 0 ? _a : process.cwd();
|
|
17
|
+
const context = DoctorContext.fromCwd(cwd);
|
|
18
|
+
return new Doctor(context).run();
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import ConfigManager from '../../config/ConfigManager.js.js';
|
|
12
|
+
export default class CredentialsCheck {
|
|
13
|
+
run(context) {
|
|
14
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
var _a, _b;
|
|
16
|
+
if (!context.projectName) {
|
|
17
|
+
return {
|
|
18
|
+
name: 'Credentials for project',
|
|
19
|
+
status: 'warn',
|
|
20
|
+
details: [
|
|
21
|
+
'Unable to resolve project name from .regressionproof.json.',
|
|
22
|
+
],
|
|
23
|
+
fix: 'Run `npx regressionproof init` from the project root.',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const configManager = new ConfigManager();
|
|
27
|
+
const configPath = context.credentialsPath;
|
|
28
|
+
if (!configPath || !fs.existsSync(configPath)) {
|
|
29
|
+
return {
|
|
30
|
+
name: `Credentials for project "${context.projectName}"`,
|
|
31
|
+
status: 'fail',
|
|
32
|
+
details: [
|
|
33
|
+
`Missing credentials at ${configPath !== null && configPath !== void 0 ? configPath : configManager.getConfigDir(context.projectName)}.`,
|
|
34
|
+
],
|
|
35
|
+
fix: 'Run `npx regressionproof invite accept <token>` (teammate) or `npx regressionproof init` (owner).',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const parsed = this.safeReadJson(configPath);
|
|
39
|
+
const url = (_a = parsed === null || parsed === void 0 ? void 0 : parsed.remote) === null || _a === void 0 ? void 0 : _a.url;
|
|
40
|
+
const token = (_b = parsed === null || parsed === void 0 ? void 0 : parsed.remote) === null || _b === void 0 ? void 0 : _b.token;
|
|
41
|
+
if (!url || !token) {
|
|
42
|
+
return {
|
|
43
|
+
name: `Credentials for project "${context.projectName}"`,
|
|
44
|
+
status: 'fail',
|
|
45
|
+
details: [
|
|
46
|
+
'Credentials file is missing remote.url or remote.token.',
|
|
47
|
+
],
|
|
48
|
+
fix: 'Run `npx regressionproof invite accept <token>` to refresh credentials.',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
name: `Credentials for project "${context.projectName}"`,
|
|
53
|
+
status: 'ok',
|
|
54
|
+
details: [`Credentials found at ${configPath}.`],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
safeReadJson(filePath) {
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
61
|
+
}
|
|
62
|
+
catch (_a) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import JestReporterConfigInspector from '../../jest/JestReporterConfigInspector.js.js';
|
|
11
|
+
export default class JestReporterCheck {
|
|
12
|
+
run(context) {
|
|
13
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
14
|
+
const inspector = new JestReporterConfigInspector();
|
|
15
|
+
const inspection = inspector.inspect(context.cwd);
|
|
16
|
+
if (!inspection.hasPackageJson) {
|
|
17
|
+
return {
|
|
18
|
+
name: 'Jest reporter configured',
|
|
19
|
+
status: 'warn',
|
|
20
|
+
details: ['No package.json found in the current directory.'],
|
|
21
|
+
fix: 'Run `npx regressionproof init` from the project root.',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (inspection.isInstalled && inspection.isConfigured) {
|
|
25
|
+
return {
|
|
26
|
+
name: 'Jest reporter configured',
|
|
27
|
+
status: 'ok',
|
|
28
|
+
details: ['Reporter is installed and configured.'],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const details = [];
|
|
32
|
+
if (!inspection.isInstalled) {
|
|
33
|
+
details.push('Reporter package is not installed.');
|
|
34
|
+
}
|
|
35
|
+
if (!inspection.isConfigured) {
|
|
36
|
+
details.push('Jest config does not include the reporter.');
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
name: 'Jest reporter configured',
|
|
40
|
+
status: 'warn',
|
|
41
|
+
details,
|
|
42
|
+
fix: 'Run `npx regressionproof init` or add the reporter to your Jest config.',
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
export default class LocalConfigCheck {
|
|
11
|
+
run(context) {
|
|
12
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
13
|
+
if (!context.localConfigPath) {
|
|
14
|
+
return {
|
|
15
|
+
name: 'Local project config (.regressionproof.json)',
|
|
16
|
+
status: 'warn',
|
|
17
|
+
details: [`Missing .regressionproof.json in ${context.cwd}.`],
|
|
18
|
+
fix: 'Run `npx regressionproof init` from the project root.',
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
name: 'Local project config (.regressionproof.json)',
|
|
23
|
+
status: 'ok',
|
|
24
|
+
details: [`Found at ${context.localConfigPath}.`],
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|