@regressionproof/cli 0.3.6 → 0.3.7

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,60 +0,0 @@
1
- import ConfigManager from '../../config/ConfigManager.js'
2
- import { getRepoNameFromGit, toSlug } from '../../utilities/slug.js'
3
-
4
- const API_URL =
5
- process.env.REGRESSIONPROOF_API_URL ?? 'https://api.regressionproof.ai'
6
-
7
- class InviteCreator {
8
- public constructor(private configManager = new ConfigManager()) {}
9
-
10
- public async run(projectNameArg?: string, note?: string): Promise<void> {
11
- const projectName = this.resolveProjectName(projectNameArg)
12
- const creds = this.configManager.loadCredentials(projectName)
13
- if (!creds) {
14
- throw new Error(
15
- `No credentials found for ${projectName}. Run regressionproof init first.`
16
- )
17
- }
18
-
19
- const response = await fetch(`${API_URL}/invites`, {
20
- method: 'POST',
21
- headers: {
22
- 'Content-Type': 'application/json',
23
- Authorization: `Bearer ${creds.token}`,
24
- },
25
- body: JSON.stringify({ name: projectName, note }),
26
- })
27
-
28
- if (!response.ok) {
29
- const text = await response.text()
30
- throw new Error(`Invite create failed: ${response.status} ${text}`)
31
- }
32
-
33
- const data = (await response.json()) as InviteCreateResponse
34
- console.log('Invite token:', data.token)
35
- }
36
-
37
- private resolveProjectName(projectNameArg?: string): string {
38
- const provided = projectNameArg ? toSlug(projectNameArg) : ''
39
- const name = provided || getRepoNameFromGit()
40
- if (!name) {
41
- throw new Error(
42
- 'Project name is required. Provide it explicitly or ensure git origin is set.'
43
- )
44
- }
45
- return name
46
- }
47
- }
48
-
49
- export default async function createInvite(
50
- projectName?: string,
51
- note?: string
52
- ): Promise<void> {
53
- const creator = new InviteCreator()
54
- await creator.run(projectName, note)
55
- }
56
-
57
- interface InviteCreateResponse {
58
- token: string
59
- projectName: string
60
- }
@@ -1,33 +0,0 @@
1
- const API_URL =
2
- process.env.REGRESSIONPROOF_API_URL ?? 'https://api.regressionproof.ai'
3
-
4
- export default async function listInvites(projectName?: string): Promise<void> {
5
- const query = projectName ? `?name=${encodeURIComponent(projectName)}` : ''
6
- const response = await fetch(`${API_URL}/invites${query}`)
7
-
8
- if (!response.ok) {
9
- const text = await response.text()
10
- throw new Error(`Invite list failed: ${response.status} ${text}`)
11
- }
12
-
13
- const data = (await response.json()) as {
14
- projectName: string
15
- createdAt: string
16
- usedAt?: string | null
17
- revokedAt?: string | null
18
- note?: string | null
19
- status: 'active' | 'used' | 'revoked'
20
- }[]
21
-
22
- if (data.length === 0) {
23
- console.log('No invites found.')
24
- return
25
- }
26
-
27
- for (const invite of data) {
28
- console.log(
29
- `${invite.projectName} | ${invite.status} | created ${invite.createdAt}` +
30
- (invite.note ? ` | ${invite.note}` : '')
31
- )
32
- }
33
- }
@@ -1,18 +0,0 @@
1
- const API_URL =
2
- process.env.REGRESSIONPROOF_API_URL ?? 'https://api.regressionproof.ai'
3
-
4
- export default async function revokeInvite(token: string): Promise<void> {
5
- const response = await fetch(
6
- `${API_URL}/invites/${encodeURIComponent(token)}`,
7
- {
8
- method: 'DELETE',
9
- }
10
- )
11
-
12
- if (!response.ok) {
13
- const text = await response.text()
14
- throw new Error(`Invite revoke failed: ${response.status} ${text}`)
15
- }
16
-
17
- console.log('Invite revoked.')
18
- }
@@ -1,436 +0,0 @@
1
- import { spawnSync } from 'node:child_process'
2
- import { existsSync, readFileSync } from 'node:fs'
3
- import client from '@regressionproof/client'
4
- import { Box, Text, useApp } from 'ink'
5
- import BigText from 'ink-big-text'
6
- import TextInput from 'ink-text-input'
7
- import React from 'react'
8
- import ConfigManager, { Credentials } from '../config/ConfigManager.js'
9
- import JestConfigurator, { JestConfigResult } from '../jest/JestConfigurator.js'
10
- import { getRepoNameFromGit, toSlug } from '../utilities/slug.js'
11
- const RegressionProofClient = client.default ?? client
12
-
13
- const API_URL =
14
- process.env.REGRESSIONPROOF_API_URL ?? 'https://api.regressionproof.ai'
15
-
16
- class InitComponent extends React.Component<Props, State> {
17
- private checkTimeout: NodeJS.Timeout | null = null
18
- private apiClient: InstanceType<typeof RegressionProofClient>
19
- private configManager: ConfigManager
20
-
21
- public constructor(props: Props) {
22
- super(props)
23
-
24
- const providedName = props.projectName
25
- const defaultName = providedName ?? getRepoNameFromGit()
26
-
27
- this.configManager = new ConfigManager()
28
- this.apiClient = new RegressionProofClient(API_URL)
29
-
30
- // Check if already registered (idempotent)
31
- const existingCreds = this.configManager.loadCredentials(defaultName)
32
- if (existingCreds) {
33
- this.state = {
34
- name: defaultName,
35
- step: 'installing',
36
- availability: 'available',
37
- errorMessage: '',
38
- credentials: existingCreds,
39
- jestConfig: null,
40
- }
41
- } else {
42
- this.state = {
43
- name: defaultName,
44
- step: providedName ? 'registering' : 'input',
45
- availability: 'idle',
46
- errorMessage: '',
47
- credentials: null,
48
- jestConfig: null,
49
- }
50
- }
51
- }
52
-
53
- public componentDidMount(): void {
54
- // If we have a provided name and not already registered, start registration
55
- if (this.props.projectName && this.state.step === 'registering') {
56
- void this.register()
57
- } else if (this.state.step === 'installing') {
58
- void this.installAndConfigure()
59
- } else {
60
- void this.checkAvailability()
61
- }
62
- }
63
-
64
- public componentDidUpdate(_: Props, prevState: State): void {
65
- if (prevState.name !== this.state.name && this.state.step === 'input') {
66
- void this.checkAvailability()
67
- }
68
- }
69
-
70
- public componentWillUnmount(): void {
71
- this.clearCheckTimeout()
72
- }
73
-
74
- private clearCheckTimeout(): void {
75
- if (this.checkTimeout) {
76
- clearTimeout(this.checkTimeout)
77
- this.checkTimeout = null
78
- }
79
- }
80
-
81
- private async checkAvailability(): Promise<void> {
82
- this.clearCheckTimeout()
83
-
84
- const { name } = this.state
85
- if (name.length < 3) {
86
- this.setState({ availability: 'idle', errorMessage: '' })
87
- return
88
- }
89
-
90
- this.setState({ availability: 'checking' })
91
-
92
- this.checkTimeout = setTimeout(async () => {
93
- try {
94
- const isAvailable =
95
- await this.apiClient.checkNameAvailability(name)
96
- this.setState({
97
- availability: isAvailable ? 'available' : 'taken',
98
- errorMessage: '',
99
- })
100
- } catch (err) {
101
- this.setState({
102
- availability: 'error',
103
- errorMessage: this.formatError(err),
104
- })
105
- }
106
- }, 300)
107
- }
108
-
109
- private formatError(err: unknown): string {
110
- const message = err instanceof Error ? err.message : String(err)
111
- const cause =
112
- err instanceof Error && 'cause' in err ? ` (${err.cause})` : ''
113
- return `${message}${cause} - ${API_URL}`
114
- }
115
-
116
- private handleNameChange = (value: string): void => {
117
- this.setState({ name: toSlug(value) })
118
- }
119
-
120
- private handleSubmit = async (): Promise<void> => {
121
- const { availability, name } = this.state
122
- if (availability !== 'available' || name.length < 3) {
123
- return
124
- }
125
-
126
- this.setState({ step: 'registering' })
127
- await this.register()
128
- }
129
-
130
- private async register(): Promise<void> {
131
- const { name } = this.state
132
-
133
- try {
134
- const credentials = await this.apiClient.registerProject({ name })
135
- this.setState({ credentials })
136
-
137
- this.configManager.saveCredentials(name, credentials)
138
- await this.installAndConfigure()
139
- } catch (err) {
140
- this.setState({
141
- step: 'error',
142
- errorMessage: err instanceof Error ? err.message : String(err),
143
- })
144
- }
145
- }
146
-
147
- private async installAndConfigure(): Promise<void> {
148
- this.setState({ step: 'installing' })
149
- const installResult = this.installDependencies()
150
- if (!installResult.success) {
151
- this.setState({
152
- step: 'error',
153
- errorMessage:
154
- installResult.message ?? 'Failed to install dependencies.',
155
- })
156
- return
157
- }
158
-
159
- this.setState({ step: 'configuring' })
160
- const jestConfigurator = new JestConfigurator()
161
- const jestConfig = jestConfigurator.configure()
162
- this.setState({ jestConfig, step: 'success' })
163
-
164
- setTimeout(() => this.props.exit(), 3000)
165
- }
166
-
167
- private installDependencies(): InstallResult {
168
- if (!existsSync('package.json')) {
169
- return {
170
- success: false,
171
- message:
172
- 'No package.json found. regressionproof currently supports Node.js + Jest projects.',
173
- }
174
- }
175
-
176
- let packageJson: {
177
- dependencies?: Record<string, string>
178
- devDependencies?: Record<string, string>
179
- }
180
-
181
- try {
182
- packageJson = JSON.parse(readFileSync('package.json', 'utf8')) as {
183
- dependencies?: Record<string, string>
184
- devDependencies?: Record<string, string>
185
- }
186
- } catch (err) {
187
- const message = err instanceof Error ? err.message : String(err)
188
- return {
189
- success: false,
190
- message: `Failed to read package.json: ${message}`,
191
- }
192
- }
193
-
194
- const hasReporter = Boolean(
195
- packageJson.dependencies?.['@regressionproof/jest-reporter'] ??
196
- packageJson.devDependencies?.['@regressionproof/jest-reporter']
197
- )
198
- if (hasReporter) {
199
- return { success: true }
200
- }
201
-
202
- const packageManager = this.getPackageManager()
203
- const result = spawnSync(
204
- packageManager.command,
205
- [...packageManager.args, '@regressionproof/jest-reporter'],
206
- {
207
- encoding: 'utf8',
208
- shell: true,
209
- }
210
- )
211
-
212
- if (result.error || result.status !== 0) {
213
- const details =
214
- result.stderr?.trim() ||
215
- result.stdout?.trim() ||
216
- result.error?.message
217
- return {
218
- success: false,
219
- message: `Failed to install dependencies${
220
- details ? `: ${details}` : ''
221
- }`,
222
- }
223
- }
224
-
225
- return { success: true }
226
- }
227
-
228
- private getPackageManager(): PackageManager {
229
- if (existsSync('pnpm-lock.yaml')) {
230
- return { command: 'pnpm', args: ['add', '-D'] }
231
- }
232
-
233
- if (existsSync('yarn.lock')) {
234
- return { command: 'yarn', args: ['add', '-D'] }
235
- }
236
-
237
- if (existsSync('package-lock.json')) {
238
- return { command: 'npm', args: ['install', '-D'] }
239
- }
240
-
241
- return { command: 'npm', args: ['install', '-D'] }
242
- }
243
-
244
- private renderStatusIndicator(): React.ReactNode {
245
- const { availability, errorMessage } = this.state
246
-
247
- switch (availability) {
248
- case 'idle':
249
- return null
250
- case 'checking':
251
- return <Text color="yellow">checking...</Text>
252
- case 'available':
253
- return <Text color="green">available (press Enter)</Text>
254
- case 'taken':
255
- return <Text color="red">already taken</Text>
256
- case 'error':
257
- return <Text color="red">{errorMessage}</Text>
258
- }
259
- }
260
-
261
- private renderRegistering(): React.ReactElement {
262
- return (
263
- <Box flexDirection="column" padding={1}>
264
- <Text color="yellow">
265
- Registering project "{this.state.name}"...
266
- </Text>
267
- </Box>
268
- )
269
- }
270
-
271
- private renderConfiguring(): React.ReactElement {
272
- return (
273
- <Box flexDirection="column" padding={1}>
274
- <Text color="yellow">Configuring Jest reporter...</Text>
275
- </Box>
276
- )
277
- }
278
-
279
- private renderInstalling(): React.ReactElement {
280
- return (
281
- <Box flexDirection="column" padding={1}>
282
- <Text color="yellow">Installing dependencies...</Text>
283
- </Box>
284
- )
285
- }
286
-
287
- private renderSuccess(): React.ReactElement {
288
- const { name, credentials, jestConfig } = this.state
289
- const configDir = this.configManager.getConfigDir(name)
290
-
291
- return (
292
- <Box flexDirection="column" padding={1}>
293
- <Text color="green" bold>
294
- Project registered successfully!
295
- </Text>
296
- <Box marginTop={1} flexDirection="column">
297
- <Text>
298
- Config saved to:{' '}
299
- <Text color="cyan">{configDir}/config.json</Text>
300
- </Text>
301
- <Text>
302
- Git remote: <Text color="cyan">{credentials?.url}</Text>
303
- </Text>
304
- {jestConfig?.configured ? (
305
- <Text>
306
- Jest reporter added to:{' '}
307
- <Text color="cyan">{jestConfig.location}</Text>
308
- </Text>
309
- ) : (
310
- <Text color="yellow">
311
- Could not auto-configure Jest. Add manually:
312
- </Text>
313
- )}
314
- </Box>
315
- {!jestConfig?.configured && (
316
- <Box marginTop={1} flexDirection="column">
317
- <Text color="gray">// jest.config.js</Text>
318
- <Text color="gray">
319
- reporters: ['default',
320
- '@regressionproof/jest-reporter']
321
- </Text>
322
- </Box>
323
- )}
324
- <Box marginTop={1}>
325
- <Text color="green">
326
- Run your tests and snapshots will be captured
327
- automatically!
328
- </Text>
329
- </Box>
330
- </Box>
331
- )
332
- }
333
-
334
- private renderError(): React.ReactElement {
335
- return (
336
- <Box flexDirection="column" padding={1}>
337
- <Text color="red" bold>
338
- Registration failed
339
- </Text>
340
- <Text color="red">{this.state.errorMessage}</Text>
341
- </Box>
342
- )
343
- }
344
-
345
- private renderInput(): React.ReactElement {
346
- const { name } = this.state
347
-
348
- return (
349
- <Box flexDirection="column" padding={1}>
350
- <BigText
351
- text="regressionproof.ai"
352
- font="tiny"
353
- colors={['magenta', 'cyan']}
354
- />
355
- <Text color="gray">Teaching LLMs to write better code.</Text>
356
-
357
- <Box marginTop={1} flexDirection="column">
358
- <Text bold>Project name:</Text>
359
- <Box>
360
- <TextInput
361
- value={name}
362
- onChange={this.handleNameChange}
363
- onSubmit={this.handleSubmit}
364
- placeholder="my-awesome-project"
365
- />
366
- <Box marginLeft={2}>{this.renderStatusIndicator()}</Box>
367
- </Box>
368
- {name.length > 0 && name.length < 3 && (
369
- <Text color="gray">
370
- Name must be at least 3 characters
371
- </Text>
372
- )}
373
- </Box>
374
- </Box>
375
- )
376
- }
377
-
378
- public render(): React.ReactElement {
379
- const { step } = this.state
380
-
381
- switch (step) {
382
- case 'registering':
383
- return this.renderRegistering()
384
- case 'installing':
385
- return this.renderInstalling()
386
- case 'configuring':
387
- return this.renderConfiguring()
388
- case 'success':
389
- return this.renderSuccess()
390
- case 'error':
391
- return this.renderError()
392
- default:
393
- return this.renderInput()
394
- }
395
- }
396
- }
397
-
398
- export default function Init(props: {
399
- projectName?: string
400
- }): React.ReactElement {
401
- const { exit } = useApp()
402
- return <InitComponent exit={exit} projectName={props.projectName} />
403
- }
404
-
405
- type Step =
406
- | 'input'
407
- | 'registering'
408
- | 'installing'
409
- | 'configuring'
410
- | 'success'
411
- | 'error'
412
- type Availability = 'idle' | 'checking' | 'available' | 'taken' | 'error'
413
-
414
- interface Props {
415
- exit: () => void
416
- projectName?: string
417
- }
418
-
419
- interface State {
420
- name: string
421
- step: Step
422
- availability: Availability
423
- errorMessage: string
424
- credentials: Credentials | null
425
- jestConfig: JestConfigResult | null
426
- }
427
-
428
- interface PackageManager {
429
- command: string
430
- args: string[]
431
- }
432
-
433
- interface InstallResult {
434
- success: boolean
435
- message?: string
436
- }
@@ -1,64 +0,0 @@
1
- import fs from 'fs'
2
- import os from 'os'
3
- import path from 'path'
4
-
5
- export default class ConfigManager {
6
- private baseDir: string
7
-
8
- public constructor(
9
- baseDir: string = path.join(os.homedir(), '.regressionproof')
10
- ) {
11
- this.baseDir = baseDir
12
- }
13
-
14
- public getConfigDir(projectName: string): string {
15
- return path.join(this.baseDir, projectName)
16
- }
17
-
18
- public saveCredentials(
19
- projectName: string,
20
- credentials: Credentials
21
- ): void {
22
- const configDir = this.getConfigDir(projectName)
23
- fs.mkdirSync(configDir, { recursive: true })
24
-
25
- const configPath = path.join(configDir, 'config.json')
26
- const config: ProjectConfig = {
27
- remote: {
28
- url: credentials.url,
29
- token: credentials.token,
30
- },
31
- }
32
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
33
- }
34
-
35
- public loadCredentials(projectName: string): Credentials | null {
36
- const configPath = path.join(
37
- this.getConfigDir(projectName),
38
- 'config.json'
39
- )
40
- if (!fs.existsSync(configPath)) {
41
- return null
42
- }
43
-
44
- const config: ProjectConfig = JSON.parse(
45
- fs.readFileSync(configPath, 'utf-8')
46
- )
47
- return {
48
- url: config.remote.url,
49
- token: config.remote.token,
50
- }
51
- }
52
- }
53
-
54
- export interface Credentials {
55
- url: string
56
- token: string
57
- }
58
-
59
- export interface ProjectConfig {
60
- remote: {
61
- url: string
62
- token: string
63
- }
64
- }
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- //exports go here