@regressionproof/cli 0.7.4 → 0.9.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 +21 -45
- package/build/commands/CommandRunner.d.ts +7 -0
- package/build/commands/CommandRunner.js +15 -0
- package/build/commands/commands.d.ts +7 -0
- package/build/commands/commands.js +118 -0
- package/build/components/Banner.d.ts +2 -0
- package/build/components/Banner.js +13 -0
- package/build/components/Banner.tsx +20 -0
- package/build/components/CommandHeading.d.ts +6 -0
- package/build/components/CommandHeading.js +5 -0
- package/build/components/CommandHeading.tsx +10 -0
- package/build/components/CommandLayout.d.ts +7 -0
- package/build/components/CommandLayout.js +8 -0
- package/build/components/CommandLayout.tsx +17 -0
- package/build/components/Doctor.d.ts +7 -0
- package/build/components/Doctor.js +43 -0
- package/build/components/Doctor.tsx +63 -0
- package/build/components/Init.js +2 -5
- package/build/components/Init.tsx +2 -10
- package/build/doctor/Doctor.d.ts +8 -0
- package/build/doctor/Doctor.js +55 -0
- package/build/doctor/DoctorContext.d.ts +14 -0
- package/build/doctor/DoctorContext.js +48 -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 +9 -0
- package/build/doctor/DoctorRunner.js +13 -0
- package/build/doctor/checks/ApiReachabilityCheck.d.ts +5 -0
- package/build/doctor/checks/ApiReachabilityCheck.js +41 -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 +12 -0
- package/build/doctor/checks/MirrorAccessCheck.js +210 -0
- package/build/esm/cli.js +21 -45
- package/build/esm/commands/CommandRunner.d.ts +7 -0
- package/build/esm/commands/CommandRunner.js +26 -0
- package/build/esm/commands/commands.d.ts +7 -0
- package/build/esm/commands/commands.js +128 -0
- package/build/esm/components/Banner.d.ts +2 -0
- package/build/esm/components/Banner.js +13 -0
- package/build/esm/components/CommandHeading.d.ts +6 -0
- package/build/esm/components/CommandHeading.js +5 -0
- package/build/esm/components/CommandLayout.d.ts +7 -0
- package/build/esm/components/CommandLayout.js +8 -0
- package/build/esm/components/Doctor.d.ts +7 -0
- package/build/esm/components/Doctor.js +43 -0
- package/build/esm/components/Init.js +2 -5
- package/build/esm/doctor/Doctor.d.ts +8 -0
- package/build/esm/doctor/Doctor.js +67 -0
- package/build/esm/doctor/DoctorContext.d.ts +14 -0
- package/build/esm/doctor/DoctorContext.js +43 -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 +9 -0
- package/build/esm/doctor/DoctorRunner.js +23 -0
- package/build/esm/doctor/checks/ApiReachabilityCheck.d.ts +5 -0
- package/build/esm/doctor/checks/ApiReachabilityCheck.js +52 -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 +12 -0
- package/build/esm/doctor/checks/MirrorAccessCheck.js +224 -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/esm/utilities/renderBanner.d.ts +1 -0
- package/build/esm/utilities/renderBanner.js +7 -0
- package/build/jest/JestConfigurator.js +9 -0
- package/build/jest/JestReporterConfigInspector.d.ts +13 -0
- package/build/jest/JestReporterConfigInspector.js +56 -0
- package/build/utilities/renderBanner.d.ts +1 -0
- package/build/utilities/renderBanner.js +7 -0
- package/package.json +4 -3
package/build/cli.js
CHANGED
|
@@ -1,52 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import 'dotenv/config';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import acceptInvite from './commands/invite/AcceptInvite.js';
|
|
6
|
-
import createInvite from './commands/invite/CreateInvite.js';
|
|
7
|
-
import listInvites from './commands/invite/ListInvites.js';
|
|
8
|
-
import revokeInvite from './commands/invite/RevokeInvite.js';
|
|
9
|
-
import Init from './components/Init.js';
|
|
3
|
+
import { runCommand } from './commands/CommandRunner.js';
|
|
4
|
+
import { getCommandHandler } from './commands/commands.js';
|
|
10
5
|
const command = process.argv[2];
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
else if (subcommand === 'accept') {
|
|
24
|
-
if (!arg) {
|
|
25
|
-
console.error('Usage: regressionproof invite accept <token>');
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
void acceptInvite(arg);
|
|
29
|
-
}
|
|
30
|
-
else if (subcommand === 'list') {
|
|
31
|
-
void listInvites(arg);
|
|
32
|
-
}
|
|
33
|
-
else if (subcommand === 'revoke') {
|
|
34
|
-
if (!arg) {
|
|
35
|
-
console.error('Usage: regressionproof invite revoke <token>');
|
|
6
|
+
const commandArgs = process.argv.slice(3);
|
|
7
|
+
const handler = getCommandHandler(command);
|
|
8
|
+
if (!handler) {
|
|
9
|
+
void runCommand({
|
|
10
|
+
heading: 'RegressionProof CLI',
|
|
11
|
+
handler: () => {
|
|
12
|
+
console.log('Usage: regressionproof <command>');
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log('Commands:');
|
|
15
|
+
console.log(' init [projectName] Initialize a new project');
|
|
16
|
+
console.log(' invite ... Manage project invites');
|
|
17
|
+
console.log(' doctor Check project configuration');
|
|
36
18
|
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
console.error('Usage: regressionproof invite <create|accept|list|revoke>');
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
19
|
+
},
|
|
20
|
+
});
|
|
44
21
|
}
|
|
45
22
|
else {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
process.exit(1);
|
|
23
|
+
void runCommand({
|
|
24
|
+
heading: handler.heading,
|
|
25
|
+
isInk: handler.isInk,
|
|
26
|
+
handler: () => handler.run(commandArgs),
|
|
27
|
+
});
|
|
52
28
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { renderBanner } from '../utilities/renderBanner.js';
|
|
2
|
+
export async function runCommand(options) {
|
|
3
|
+
renderBanner();
|
|
4
|
+
if (!options.isInk && options.heading) {
|
|
5
|
+
console.log(options.heading);
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
await options.handler();
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12
|
+
console.error(message);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { render } from 'ink';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import CommandLayout from '../components/CommandLayout.js';
|
|
4
|
+
import Doctor from '../components/Doctor.js';
|
|
5
|
+
import Init from '../components/Init.js';
|
|
6
|
+
import DoctorOutput from '../doctor/DoctorOutput.js';
|
|
7
|
+
import DoctorRunner from '../doctor/DoctorRunner.js';
|
|
8
|
+
import acceptInvite from './invite/AcceptInvite.js';
|
|
9
|
+
import createInvite from './invite/CreateInvite.js';
|
|
10
|
+
import listInvites from './invite/ListInvites.js';
|
|
11
|
+
import revokeInvite from './invite/RevokeInvite.js';
|
|
12
|
+
const commandHandlers = {
|
|
13
|
+
init: {
|
|
14
|
+
heading: 'RegressionProof Init',
|
|
15
|
+
isInk: true,
|
|
16
|
+
run: (args) => {
|
|
17
|
+
const projectName = args[0];
|
|
18
|
+
render(React.createElement(CommandLayout, { heading: 'RegressionProof Init' }, React.createElement(Init, { projectName })));
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
invite: {
|
|
22
|
+
heading: 'RegressionProof Invite',
|
|
23
|
+
run: async (args) => {
|
|
24
|
+
const subcommand = args[0];
|
|
25
|
+
const arg = args[1];
|
|
26
|
+
if (subcommand === 'create') {
|
|
27
|
+
const noteArg = args.find((value) => value.startsWith('--note='));
|
|
28
|
+
const note = noteArg
|
|
29
|
+
? noteArg.replace('--note=', '')
|
|
30
|
+
: undefined;
|
|
31
|
+
await createInvite(arg, note);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (subcommand === 'accept') {
|
|
35
|
+
if (!arg) {
|
|
36
|
+
throw new Error('Usage: regressionproof invite accept <token>');
|
|
37
|
+
}
|
|
38
|
+
await acceptInvite(arg);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (subcommand === 'list') {
|
|
42
|
+
await listInvites(arg);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (subcommand === 'revoke') {
|
|
46
|
+
if (!arg) {
|
|
47
|
+
throw new Error('Usage: regressionproof invite revoke <token>');
|
|
48
|
+
}
|
|
49
|
+
await revokeInvite(arg);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
throw new Error('Usage: regressionproof invite <create|accept|list|revoke>');
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
doctor: {
|
|
56
|
+
heading: 'RegressionProof Doctor',
|
|
57
|
+
isInk: true,
|
|
58
|
+
run: async (args) => {
|
|
59
|
+
const options = parseDoctorArgs(args);
|
|
60
|
+
if (options.help) {
|
|
61
|
+
console.log('RegressionProof Doctor');
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log('Usage:\n regressionproof doctor [--fix] [--json] [--cwd <path>]');
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log('Options:');
|
|
66
|
+
console.log(' --fix Attempt safe fixes (currently only mirror sync)');
|
|
67
|
+
console.log(' --json Output machine-readable JSON');
|
|
68
|
+
console.log(' --cwd Run checks for a specific directory');
|
|
69
|
+
console.log(' --help Show this help');
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
const results = await DoctorRunner.run({
|
|
73
|
+
cwd: options.cwd,
|
|
74
|
+
fix: options.fix,
|
|
75
|
+
});
|
|
76
|
+
if (options.json) {
|
|
77
|
+
console.log(JSON.stringify(results, null, 2));
|
|
78
|
+
process.exit(DoctorOutput.exitCode(results));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const { waitUntilExit } = render(React.createElement(CommandLayout, { heading: 'RegressionProof Doctor' }, React.createElement(Doctor, { results })));
|
|
82
|
+
await waitUntilExit();
|
|
83
|
+
process.exit(DoctorOutput.exitCode(results));
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
export function getCommandHandler(command) {
|
|
88
|
+
if (!command) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return commandHandlers[command] ?? null;
|
|
92
|
+
}
|
|
93
|
+
function parseDoctorArgs(args) {
|
|
94
|
+
const options = { json: false, fix: false, help: false };
|
|
95
|
+
for (let i = 0; i < args.length; i++) {
|
|
96
|
+
const arg = args[i];
|
|
97
|
+
if (arg === '--json') {
|
|
98
|
+
options.json = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (arg === '--fix') {
|
|
102
|
+
options.fix = true;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (arg === '--help' || arg === '-h') {
|
|
106
|
+
options.help = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (arg === '--cwd') {
|
|
110
|
+
const value = args[i + 1];
|
|
111
|
+
if (value) {
|
|
112
|
+
options.cwd = value;
|
|
113
|
+
i++;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return options;
|
|
118
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Box, Text } from 'ink';
|
|
2
|
+
import BigText from 'ink-big-text';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { getCliVersion } from '../utilities/version.js';
|
|
5
|
+
export default function Banner() {
|
|
6
|
+
const version = getCliVersion();
|
|
7
|
+
return (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(Text, { color: "gray" },
|
|
11
|
+
"CLI v",
|
|
12
|
+
version)));
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Box, Text } from 'ink'
|
|
2
|
+
import BigText from 'ink-big-text'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { getCliVersion } from '../utilities/version.js'
|
|
5
|
+
|
|
6
|
+
export default function Banner(): React.ReactElement {
|
|
7
|
+
const version = getCliVersion()
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Box flexDirection="column" padding={1}>
|
|
11
|
+
<BigText
|
|
12
|
+
text="regressionproof.ai"
|
|
13
|
+
font="tiny"
|
|
14
|
+
colors={['magenta', 'cyan']}
|
|
15
|
+
/>
|
|
16
|
+
<Text color="gray">Teaching LLMs to write better code.</Text>
|
|
17
|
+
<Text color="gray">CLI v{version}</Text>
|
|
18
|
+
</Box>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Box } from 'ink';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import CommandHeading from './CommandHeading.js';
|
|
4
|
+
export default function CommandLayout(props) {
|
|
5
|
+
return (React.createElement(Box, { flexDirection: "column", paddingX: 1, paddingBottom: 1 },
|
|
6
|
+
React.createElement(CommandHeading, { heading: props.heading }),
|
|
7
|
+
React.createElement(Box, { marginTop: 1 }, props.children)));
|
|
8
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Box } from 'ink'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import CommandHeading from './CommandHeading.js'
|
|
4
|
+
|
|
5
|
+
export default function CommandLayout(props: Props): React.ReactElement {
|
|
6
|
+
return (
|
|
7
|
+
<Box flexDirection="column" paddingX={1} paddingBottom={1}>
|
|
8
|
+
<CommandHeading heading={props.heading} />
|
|
9
|
+
<Box marginTop={1}>{props.children}</Box>
|
|
10
|
+
</Box>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
heading: string
|
|
16
|
+
children?: React.ReactNode
|
|
17
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Box, Text } from 'ink';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
class DoctorComponent extends React.Component {
|
|
4
|
+
render() {
|
|
5
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
6
|
+
React.createElement(Box, { flexDirection: "column", paddingX: 1, paddingBottom: 1 }, this.props.results.map((result) => (React.createElement(Box, { key: result.name, flexDirection: "column" },
|
|
7
|
+
React.createElement(Box, null,
|
|
8
|
+
React.createElement(Text, { color: this.colorFor(result.status) }, this.labelFor(result.status)),
|
|
9
|
+
React.createElement(Text, null,
|
|
10
|
+
" ",
|
|
11
|
+
result.name)),
|
|
12
|
+
result.details.map((detail) => (React.createElement(Text, { key: detail },
|
|
13
|
+
" ",
|
|
14
|
+
detail))),
|
|
15
|
+
result.fix ? (React.createElement(Text, null,
|
|
16
|
+
" Fix: ",
|
|
17
|
+
result.fix)) : null,
|
|
18
|
+
React.createElement(Text, null, " ")))))));
|
|
19
|
+
}
|
|
20
|
+
labelFor(status) {
|
|
21
|
+
switch (status) {
|
|
22
|
+
case 'ok':
|
|
23
|
+
return 'OK';
|
|
24
|
+
case 'warn':
|
|
25
|
+
return 'WARN';
|
|
26
|
+
case 'fail':
|
|
27
|
+
return 'FAIL';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
colorFor(status) {
|
|
31
|
+
switch (status) {
|
|
32
|
+
case 'ok':
|
|
33
|
+
return 'green';
|
|
34
|
+
case 'warn':
|
|
35
|
+
return 'yellow';
|
|
36
|
+
case 'fail':
|
|
37
|
+
return 'red';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export default function Doctor(props) {
|
|
42
|
+
return React.createElement(DoctorComponent, { results: props.results });
|
|
43
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Box, Text } from 'ink'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import type { DoctorResult } from '../doctor/DoctorResult.js'
|
|
4
|
+
|
|
5
|
+
class DoctorComponent extends React.Component<Props> {
|
|
6
|
+
public render(): React.ReactElement {
|
|
7
|
+
return (
|
|
8
|
+
<Box flexDirection="column">
|
|
9
|
+
<Box flexDirection="column" paddingX={1} paddingBottom={1}>
|
|
10
|
+
{this.props.results.map((result) => (
|
|
11
|
+
<Box key={result.name} flexDirection="column">
|
|
12
|
+
<Box>
|
|
13
|
+
<Text color={this.colorFor(result.status)}>
|
|
14
|
+
{this.labelFor(result.status)}
|
|
15
|
+
</Text>
|
|
16
|
+
<Text> {result.name}</Text>
|
|
17
|
+
</Box>
|
|
18
|
+
{result.details.map((detail) => (
|
|
19
|
+
<Text key={detail}> {detail}</Text>
|
|
20
|
+
))}
|
|
21
|
+
{result.fix ? (
|
|
22
|
+
<Text> Fix: {result.fix}</Text>
|
|
23
|
+
) : null}
|
|
24
|
+
<Text> </Text>
|
|
25
|
+
</Box>
|
|
26
|
+
))}
|
|
27
|
+
</Box>
|
|
28
|
+
</Box>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private labelFor(status: DoctorResult['status']): string {
|
|
33
|
+
switch (status) {
|
|
34
|
+
case 'ok':
|
|
35
|
+
return 'OK'
|
|
36
|
+
case 'warn':
|
|
37
|
+
return 'WARN'
|
|
38
|
+
case 'fail':
|
|
39
|
+
return 'FAIL'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private colorFor(
|
|
44
|
+
status: DoctorResult['status']
|
|
45
|
+
): 'green' | 'yellow' | 'red' {
|
|
46
|
+
switch (status) {
|
|
47
|
+
case 'ok':
|
|
48
|
+
return 'green'
|
|
49
|
+
case 'warn':
|
|
50
|
+
return 'yellow'
|
|
51
|
+
case 'fail':
|
|
52
|
+
return 'red'
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default function Doctor(props: Props): React.ReactElement {
|
|
58
|
+
return <DoctorComponent results={props.results} />
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface Props {
|
|
62
|
+
results: DoctorResult[]
|
|
63
|
+
}
|
package/build/components/Init.js
CHANGED
|
@@ -2,7 +2,6 @@ import { spawnSync } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { buildRegressionProofClient } from '@regressionproof/client';
|
|
4
4
|
import { Box, Text, useApp } from 'ink';
|
|
5
|
-
import BigText from 'ink-big-text';
|
|
6
5
|
import TextInput from 'ink-text-input';
|
|
7
6
|
import React from 'react';
|
|
8
7
|
import ConfigManager from '../config/ConfigManager.js';
|
|
@@ -253,10 +252,8 @@ class InitComponent extends React.Component {
|
|
|
253
252
|
}
|
|
254
253
|
renderInput() {
|
|
255
254
|
const { name } = this.state;
|
|
256
|
-
return (React.createElement(Box, { flexDirection: "column"
|
|
257
|
-
React.createElement(
|
|
258
|
-
React.createElement(Text, { color: "gray" }, "Teaching LLMs to write better code."),
|
|
259
|
-
React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
255
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
256
|
+
React.createElement(Box, { padding: 1, flexDirection: "column" },
|
|
260
257
|
React.createElement(Text, { bold: true }, "Project name:"),
|
|
261
258
|
React.createElement(Box, null,
|
|
262
259
|
React.createElement(TextInput, { value: name, onChange: this.handleNameChange, onSubmit: this.handleSubmit, placeholder: "my-awesome-project" }),
|
|
@@ -3,7 +3,6 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
3
3
|
import type { RegressionProofClient } from '@regressionproof/client'
|
|
4
4
|
import { buildRegressionProofClient } from '@regressionproof/client'
|
|
5
5
|
import { Box, Text, useApp } from 'ink'
|
|
6
|
-
import BigText from 'ink-big-text'
|
|
7
6
|
import TextInput from 'ink-text-input'
|
|
8
7
|
import React from 'react'
|
|
9
8
|
import ConfigManager, { Credentials } from '../config/ConfigManager.js'
|
|
@@ -347,15 +346,8 @@ class InitComponent extends React.Component<Props, State> {
|
|
|
347
346
|
const { name } = this.state
|
|
348
347
|
|
|
349
348
|
return (
|
|
350
|
-
<Box flexDirection="column"
|
|
351
|
-
<
|
|
352
|
-
text="regressionproof.ai"
|
|
353
|
-
font="tiny"
|
|
354
|
-
colors={['magenta', 'cyan']}
|
|
355
|
-
/>
|
|
356
|
-
<Text color="gray">Teaching LLMs to write better code.</Text>
|
|
357
|
-
|
|
358
|
-
<Box marginTop={1} flexDirection="column">
|
|
349
|
+
<Box flexDirection="column">
|
|
350
|
+
<Box padding={1} flexDirection="column">
|
|
359
351
|
<Text bold>Project name:</Text>
|
|
360
352
|
<Box>
|
|
361
353
|
<TextInput
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import ApiReachabilityCheck from './checks/ApiReachabilityCheck.js';
|
|
2
|
+
import CredentialsCheck from './checks/CredentialsCheck.js';
|
|
3
|
+
import JestReporterCheck from './checks/JestReporterCheck.js';
|
|
4
|
+
import LocalConfigCheck from './checks/LocalConfigCheck.js';
|
|
5
|
+
import MirrorAccessCheck from './checks/MirrorAccessCheck.js';
|
|
6
|
+
export default class Doctor {
|
|
7
|
+
context;
|
|
8
|
+
constructor(context) {
|
|
9
|
+
this.context = context;
|
|
10
|
+
}
|
|
11
|
+
async run() {
|
|
12
|
+
const checks = [
|
|
13
|
+
new LocalConfigCheck(),
|
|
14
|
+
new JestReporterCheck(),
|
|
15
|
+
new CredentialsCheck(),
|
|
16
|
+
new MirrorAccessCheck(),
|
|
17
|
+
new ApiReachabilityCheck(),
|
|
18
|
+
];
|
|
19
|
+
const results = [];
|
|
20
|
+
if (this.context.fix) {
|
|
21
|
+
return this.runFixOnly(checks, results);
|
|
22
|
+
}
|
|
23
|
+
for (const check of checks) {
|
|
24
|
+
results.push(await check.run(this.context));
|
|
25
|
+
}
|
|
26
|
+
return results;
|
|
27
|
+
}
|
|
28
|
+
async runFixOnly(checks, results) {
|
|
29
|
+
let mirrorResult;
|
|
30
|
+
for (const check of checks) {
|
|
31
|
+
if (check instanceof MirrorAccessCheck) {
|
|
32
|
+
mirrorResult = await check.run(this.context);
|
|
33
|
+
results.push(mirrorResult);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!mirrorResult) {
|
|
38
|
+
throw new Error('Doctor --fix not implemented (missing mirror check).');
|
|
39
|
+
}
|
|
40
|
+
if (mirrorResult.status === 'fail') {
|
|
41
|
+
throw new Error('Doctor --fix not implemented for this mirror issue.');
|
|
42
|
+
}
|
|
43
|
+
for (const check of checks) {
|
|
44
|
+
if (check instanceof MirrorAccessCheck) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const result = await check.run(this.context);
|
|
48
|
+
results.push(result);
|
|
49
|
+
if (result.status !== 'ok') {
|
|
50
|
+
throw new Error(`Doctor --fix not implemented for ${result.name}.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default class DoctorContext {
|
|
2
|
+
cwd: string;
|
|
3
|
+
projectName: string | null;
|
|
4
|
+
localConfigPath: string | null;
|
|
5
|
+
homeConfigDir: string;
|
|
6
|
+
apiUrl: string;
|
|
7
|
+
fix: boolean;
|
|
8
|
+
constructor(cwd: string, projectName: string | null, localConfigPath: string | null, homeConfigDir: string, apiUrl: string, fix: boolean);
|
|
9
|
+
static fromCwd(cwd: string, apiUrl: string, fix: boolean): DoctorContext;
|
|
10
|
+
get configDir(): string | null;
|
|
11
|
+
get credentialsPath(): string | null;
|
|
12
|
+
get mirrorPath(): string | null;
|
|
13
|
+
private static readProjectName;
|
|
14
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
export default class DoctorContext {
|
|
5
|
+
cwd;
|
|
6
|
+
projectName;
|
|
7
|
+
localConfigPath;
|
|
8
|
+
homeConfigDir;
|
|
9
|
+
apiUrl;
|
|
10
|
+
fix;
|
|
11
|
+
constructor(cwd, projectName, localConfigPath, homeConfigDir, apiUrl, fix) {
|
|
12
|
+
this.cwd = cwd;
|
|
13
|
+
this.projectName = projectName;
|
|
14
|
+
this.localConfigPath = localConfigPath;
|
|
15
|
+
this.homeConfigDir = homeConfigDir;
|
|
16
|
+
this.apiUrl = apiUrl;
|
|
17
|
+
this.fix = fix;
|
|
18
|
+
}
|
|
19
|
+
static fromCwd(cwd, apiUrl, fix) {
|
|
20
|
+
const localConfigPath = path.join(cwd, '.regressionproof.json');
|
|
21
|
+
const localExists = fs.existsSync(localConfigPath);
|
|
22
|
+
const projectName = localExists
|
|
23
|
+
? DoctorContext.readProjectName(localConfigPath)
|
|
24
|
+
: null;
|
|
25
|
+
const homeConfigDir = path.join(os.homedir(), '.regressionproof');
|
|
26
|
+
return new DoctorContext(cwd, projectName, localExists ? localConfigPath : null, homeConfigDir, apiUrl, fix);
|
|
27
|
+
}
|
|
28
|
+
get configDir() {
|
|
29
|
+
return this.projectName
|
|
30
|
+
? path.join(this.homeConfigDir, this.projectName)
|
|
31
|
+
: null;
|
|
32
|
+
}
|
|
33
|
+
get credentialsPath() {
|
|
34
|
+
return this.configDir ? path.join(this.configDir, 'config.json') : null;
|
|
35
|
+
}
|
|
36
|
+
get mirrorPath() {
|
|
37
|
+
return this.configDir ? path.join(this.configDir, 'mirror') : null;
|
|
38
|
+
}
|
|
39
|
+
static readProjectName(localConfigPath) {
|
|
40
|
+
try {
|
|
41
|
+
const raw = JSON.parse(fs.readFileSync(localConfigPath, 'utf-8'));
|
|
42
|
+
return raw.projectName ?? null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -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
|
+
}
|