@regressionproof/cli 0.0.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigManager.js","sourceRoot":"","sources":["../../src/config/ConfigManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,MAAM,CAAC,OAAO,OAAO,aAAa;IACtB,OAAO,CAAQ;IAEvB,YACI,UAAkB,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,kBAAkB,CAAC;QAE7D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IAC1B,CAAC;IAEM,YAAY,CAAC,WAAmB;QACnC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IAC/C,CAAC;IAEM,eAAe,CAClB,WAAmB,EACnB,WAAwB;QAExB,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA;QAChD,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAE5C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAA;QACtD,MAAM,MAAM,GAAkB;YAC1B,MAAM,EAAE;gBACJ,GAAG,EAAE,WAAW,CAAC,GAAG;gBACpB,KAAK,EAAE,WAAW,CAAC,KAAK;aAC3B;SACJ,CAAA;QACD,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;IACjE,CAAC;IAEM,eAAe,CAAC,WAAmB;QACtC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CACxB,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,EAC9B,aAAa,CAChB,CAAA;QACD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAA;QACf,CAAC;QAED,MAAM,MAAM,GAAkB,IAAI,CAAC,KAAK,CACpC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CACvC,CAAA;QACD,OAAO;YACH,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG;YACtB,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK;SAC7B,CAAA;IACL,CAAC;CACJ"}
@@ -0,0 +1 @@
1
+ export {};
package/build/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export {};
2
+ //exports go here
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,iBAAiB"}
@@ -0,0 +1,12 @@
1
+ export default class JestConfigurator {
2
+ private cwd;
3
+ private reporterPackage;
4
+ constructor(cwd?: string);
5
+ configure(): JestConfigResult;
6
+ private tryConfigurePackageJson;
7
+ private tryConfigureJestConfig;
8
+ }
9
+ export interface JestConfigResult {
10
+ configured: boolean;
11
+ location: string;
12
+ }
@@ -0,0 +1,60 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ export default class JestConfigurator {
4
+ cwd;
5
+ reporterPackage = '@regressionproof/jest-reporter';
6
+ constructor(cwd = process.cwd()) {
7
+ this.cwd = cwd;
8
+ }
9
+ configure() {
10
+ return (this.tryConfigurePackageJson() ??
11
+ this.tryConfigureJestConfig('jest.config.ts') ??
12
+ this.tryConfigureJestConfig('jest.config.js') ?? {
13
+ configured: false,
14
+ location: '',
15
+ });
16
+ }
17
+ tryConfigurePackageJson() {
18
+ const packageJsonPath = path.join(this.cwd, 'package.json');
19
+ if (!fs.existsSync(packageJsonPath)) {
20
+ return null;
21
+ }
22
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
23
+ if (!packageJson.jest) {
24
+ return null;
25
+ }
26
+ if (!packageJson.jest.reporters) {
27
+ packageJson.jest.reporters = ['default'];
28
+ }
29
+ if (!packageJson.jest.reporters.includes(this.reporterPackage)) {
30
+ packageJson.jest.reporters.push(this.reporterPackage);
31
+ }
32
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
33
+ return { configured: true, location: 'package.json' };
34
+ }
35
+ tryConfigureJestConfig(filename) {
36
+ const configPath = path.join(this.cwd, filename);
37
+ if (!fs.existsSync(configPath)) {
38
+ return null;
39
+ }
40
+ let content = fs.readFileSync(configPath, 'utf-8');
41
+ if (content.includes(this.reporterPackage)) {
42
+ return {
43
+ configured: true,
44
+ location: `${filename} (already configured)`,
45
+ };
46
+ }
47
+ if (content.includes('reporters:')) {
48
+ content = content.replace(/(reporters:\s*\[)/, `$1'${this.reporterPackage}', `);
49
+ }
50
+ else {
51
+ const exportPattern = filename.endsWith('.ts')
52
+ ? /(export\s+default\s*\{)/
53
+ : /(module\.exports\s*=\s*\{)/;
54
+ content = content.replace(exportPattern, `$1\n reporters: ['default', '${this.reporterPackage}'],`);
55
+ }
56
+ fs.writeFileSync(configPath, content);
57
+ return { configured: true, location: filename };
58
+ }
59
+ }
60
+ //# sourceMappingURL=JestConfigurator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JestConfigurator.js","sourceRoot":"","sources":["../../src/jest/JestConfigurator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function toSlug(input: string): string;
2
+ export declare function getRepoNameFromGit(): string;
@@ -0,0 +1,22 @@
1
+ import { execSync } from 'child_process';
2
+ export function toSlug(input) {
3
+ return input
4
+ .toLowerCase()
5
+ .replace(/\s+/g, '-')
6
+ .replace(/[^a-z0-9-_]/g, '')
7
+ .replace(/-+/g, '-')
8
+ .replace(/^-|-$/g, '');
9
+ }
10
+ export function getRepoNameFromGit() {
11
+ try {
12
+ const remoteUrl = execSync('git remote get-url origin', {
13
+ encoding: 'utf-8',
14
+ }).trim();
15
+ const match = remoteUrl.match(/[/:]([^/:]+?)(\.git)?$/);
16
+ return toSlug(match?.[1] ?? '');
17
+ }
18
+ catch {
19
+ return '';
20
+ }
21
+ }
22
+ //# sourceMappingURL=slug.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug.js","sourceRoot":"","sources":["../../src/utilities/slug.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,MAAM,UAAU,MAAM,CAAC,KAAa;IAChC,OAAO,KAAK;SACP,WAAW,EAAE;SACb,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;SAC3B,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;AAC9B,CAAC;AAED,MAAM,UAAU,kBAAkB;IAC9B,IAAI,CAAC;QACD,MAAM,SAAS,GAAG,QAAQ,CAAC,2BAA2B,EAAE;YACpD,QAAQ,EAAE,OAAO;SACpB,CAAC,CAAC,IAAI,EAAE,CAAA;QACT,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAA;QACvD,OAAO,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IACnC,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAA;IACb,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import eslintConfigSpruce from 'eslint-config-spruce'
2
+
3
+ export default eslintConfigSpruce
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@regressionproof/cli",
3
+ "version": "0.0.1",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "main": "build/index.js",
9
+ "types": "build/index.d.ts",
10
+ "bin": {
11
+ "regressionproof": "./build/cli.js"
12
+ },
13
+ "scripts": {
14
+ "build.ci": "yarn run build.tsc && yarn run build.resolve-paths && yarn run lint",
15
+ "build.dev": "yarn run build.tsc --sourceMap ; yarn run resolve-paths.lint",
16
+ "build.copy-files": "mkdir -p build && rsync -avzq --exclude='*.ts' ./src/ ./build/",
17
+ "build.resolve-paths": "resolve-path-aliases --target build --patterns '**/*.js,**/*.d.ts'",
18
+ "build.tsc": "yarn run build.copy-files && tsc",
19
+ "clean": "yarn run clean.build",
20
+ "clean.all": "yarn run clean.dependencies && yarn run clean.build",
21
+ "clean.build": "rm -rf build/",
22
+ "clean.dependencies": "rm -rf node_modules/ package-lock.json yarn.lock",
23
+ "fix.lint": "eslint --fix --cache '**/*.ts[x]'",
24
+ "lint": "eslint --cache '**/*.ts'",
25
+ "lint.tsc": "tsc -p . --noEmit",
26
+ "post.watch.build": "yarn run build.copy-files && yarn run build.resolve-paths",
27
+ "rebuild": "yarn run clean.all && yarn install && yarn run build.dev",
28
+ "update.dependencies": "yarn run clean.dependencies && yarn",
29
+ "resolve-paths.lint": "yarn run build.resolve-paths ; yarn run lint",
30
+ "test": "jest --passWithNoTests",
31
+ "watch.build.dev": "tsc-watch --sourceMap --onCompilationComplete 'yarn run post.watch.build'",
32
+ "watch.rebuild": "yarn run clean.all && yarn install && yarn run watch.build.dev",
33
+ "watch.tsc": "tsc -w"
34
+ },
35
+ "dependencies": {
36
+ "@regressionproof/client": "^0.0.1",
37
+ "dotenv": "^17.2.3",
38
+ "ink": "^5.1.0",
39
+ "ink-big-text": "^2.0.0",
40
+ "ink-text-input": "^6.0.0",
41
+ "react": "^18.3.1"
42
+ },
43
+ "devDependencies": {
44
+ "@sprucelabs/resolve-path-aliases": "^4.0.12",
45
+ "@types/node": "^25.0.3",
46
+ "@types/react": "^18.3.18",
47
+ "chokidar-cli": "^3.0.0",
48
+ "eslint": "^9.39.2",
49
+ "eslint-config-spruce": "^11.2.26",
50
+ "prettier": "^3.7.4",
51
+ "ts-node": "^10.9.2",
52
+ "tsc-watch": "^7.2.0",
53
+ "typescript": "^5.9.3"
54
+ },
55
+ "description": "The cli to setup regression proof training locally",
56
+ "skill": {
57
+ "namespace": "regressionproof-cli"
58
+ }
59
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "scriptUpdater": {
3
+ "skipped": []
4
+ },
5
+ "writer": {
6
+ "skipped": [
7
+ "tsconfig.json"
8
+ ]
9
+ }
10
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config'
3
+ import { render } from 'ink'
4
+ import React from 'react'
5
+ import Init from './components/Init.js'
6
+
7
+ const command = process.argv[2]
8
+ const projectNameArg = process.argv[3]
9
+
10
+ if (command === 'init') {
11
+ render(React.createElement(Init, { projectName: projectNameArg }))
12
+ } else {
13
+ console.log('Usage: regressionproof <command>')
14
+ console.log('')
15
+ console.log('Commands:')
16
+ console.log(' init [projectName] Initialize a new project')
17
+ process.exit(1)
18
+ }
@@ -0,0 +1,320 @@
1
+ import client from '@regressionproof/client'
2
+ const RegressionProofClient = client.default ?? client
3
+ import { Box, Text, useApp } from 'ink'
4
+ import BigText from 'ink-big-text'
5
+ import TextInput from 'ink-text-input'
6
+ import React from 'react'
7
+ import ConfigManager, { Credentials } from '../config/ConfigManager.js'
8
+ import JestConfigurator, { JestConfigResult } from '../jest/JestConfigurator.js'
9
+ import { getRepoNameFromGit, toSlug } from '../utilities/slug.js'
10
+
11
+ const API_URL =
12
+ process.env.REGRESSIONPROOF_API_URL ?? 'https://api.regressionproof.ai'
13
+
14
+ class InitComponent extends React.Component<Props, State> {
15
+ private checkTimeout: NodeJS.Timeout | null = null
16
+ private apiClient: InstanceType<typeof RegressionProofClient>
17
+ private configManager: ConfigManager
18
+
19
+ public constructor(props: Props) {
20
+ super(props)
21
+
22
+ const providedName = props.projectName
23
+ const defaultName = providedName ?? getRepoNameFromGit()
24
+
25
+ this.configManager = new ConfigManager()
26
+ this.apiClient = new RegressionProofClient(API_URL)
27
+
28
+ // Check if already registered (idempotent)
29
+ const existingCreds = this.configManager.loadCredentials(defaultName)
30
+ if (existingCreds) {
31
+ this.state = {
32
+ name: defaultName,
33
+ step: 'success',
34
+ availability: 'available',
35
+ errorMessage: '',
36
+ credentials: existingCreds,
37
+ jestConfig: null,
38
+ }
39
+ } else {
40
+ this.state = {
41
+ name: defaultName,
42
+ step: providedName ? 'registering' : 'input',
43
+ availability: 'idle',
44
+ errorMessage: '',
45
+ credentials: null,
46
+ jestConfig: null,
47
+ }
48
+ }
49
+ }
50
+
51
+ public componentDidMount(): void {
52
+ // If we have a provided name and not already registered, start registration
53
+ if (this.props.projectName && this.state.step === 'registering') {
54
+ void this.register()
55
+ } else if (this.state.step === 'success') {
56
+ // Already registered, configure Jest and exit
57
+ const jestConfigurator = new JestConfigurator()
58
+ const jestConfig = jestConfigurator.configure()
59
+ this.setState({ jestConfig })
60
+ setTimeout(() => this.props.exit(), 1000)
61
+ } else {
62
+ void this.checkAvailability()
63
+ }
64
+ }
65
+
66
+ public componentDidUpdate(_: Props, prevState: State): void {
67
+ if (prevState.name !== this.state.name && this.state.step === 'input') {
68
+ void this.checkAvailability()
69
+ }
70
+ }
71
+
72
+ public componentWillUnmount(): void {
73
+ this.clearCheckTimeout()
74
+ }
75
+
76
+ private clearCheckTimeout(): void {
77
+ if (this.checkTimeout) {
78
+ clearTimeout(this.checkTimeout)
79
+ this.checkTimeout = null
80
+ }
81
+ }
82
+
83
+ private async checkAvailability(): Promise<void> {
84
+ this.clearCheckTimeout()
85
+
86
+ const { name } = this.state
87
+ if (name.length < 3) {
88
+ this.setState({ availability: 'idle', errorMessage: '' })
89
+ return
90
+ }
91
+
92
+ this.setState({ availability: 'checking' })
93
+
94
+ this.checkTimeout = setTimeout(async () => {
95
+ try {
96
+ const isAvailable =
97
+ await this.apiClient.checkNameAvailability(name)
98
+ this.setState({
99
+ availability: isAvailable ? 'available' : 'taken',
100
+ errorMessage: '',
101
+ })
102
+ } catch (err) {
103
+ this.setState({
104
+ availability: 'error',
105
+ errorMessage: this.formatError(err),
106
+ })
107
+ }
108
+ }, 300)
109
+ }
110
+
111
+ private formatError(err: unknown): string {
112
+ const message = err instanceof Error ? err.message : String(err)
113
+ const cause =
114
+ err instanceof Error && 'cause' in err ? ` (${err.cause})` : ''
115
+ return `${message}${cause} - ${API_URL}`
116
+ }
117
+
118
+ private handleNameChange = (value: string): void => {
119
+ this.setState({ name: toSlug(value) })
120
+ }
121
+
122
+ private handleSubmit = async (): Promise<void> => {
123
+ const { availability, name } = this.state
124
+ if (availability !== 'available' || name.length < 3) {
125
+ return
126
+ }
127
+
128
+ this.setState({ step: 'registering' })
129
+ await this.register()
130
+ }
131
+
132
+ private async register(): Promise<void> {
133
+ const { name } = this.state
134
+
135
+ try {
136
+ const credentials = await this.apiClient.registerProject({ name })
137
+ this.setState({ credentials })
138
+
139
+ this.configManager.saveCredentials(name, credentials)
140
+
141
+ this.setState({ step: 'configuring' })
142
+
143
+ const jestConfigurator = new JestConfigurator()
144
+ const jestConfig = jestConfigurator.configure()
145
+ this.setState({ jestConfig, step: 'success' })
146
+
147
+ setTimeout(() => this.props.exit(), 3000)
148
+ } catch (err) {
149
+ this.setState({
150
+ step: 'error',
151
+ errorMessage: err instanceof Error ? err.message : String(err),
152
+ })
153
+ }
154
+ }
155
+
156
+ private renderStatusIndicator(): React.ReactNode {
157
+ const { availability, errorMessage } = this.state
158
+
159
+ switch (availability) {
160
+ case 'idle':
161
+ return null
162
+ case 'checking':
163
+ return <Text color="yellow">checking...</Text>
164
+ case 'available':
165
+ return <Text color="green">available (press Enter)</Text>
166
+ case 'taken':
167
+ return <Text color="red">already taken</Text>
168
+ case 'error':
169
+ return <Text color="red">{errorMessage}</Text>
170
+ }
171
+ }
172
+
173
+ private renderRegistering(): React.ReactElement {
174
+ return (
175
+ <Box flexDirection="column" padding={1}>
176
+ <Text color="yellow">
177
+ Registering project "{this.state.name}"...
178
+ </Text>
179
+ </Box>
180
+ )
181
+ }
182
+
183
+ private renderConfiguring(): React.ReactElement {
184
+ return (
185
+ <Box flexDirection="column" padding={1}>
186
+ <Text color="yellow">Configuring Jest reporter...</Text>
187
+ </Box>
188
+ )
189
+ }
190
+
191
+ private renderSuccess(): React.ReactElement {
192
+ const { name, credentials, jestConfig } = this.state
193
+ const configDir = this.configManager.getConfigDir(name)
194
+
195
+ return (
196
+ <Box flexDirection="column" padding={1}>
197
+ <Text color="green" bold>
198
+ Project registered successfully!
199
+ </Text>
200
+ <Box marginTop={1} flexDirection="column">
201
+ <Text>
202
+ Config saved to:{' '}
203
+ <Text color="cyan">{configDir}/config.json</Text>
204
+ </Text>
205
+ <Text>
206
+ Git remote: <Text color="cyan">{credentials?.url}</Text>
207
+ </Text>
208
+ {jestConfig?.configured ? (
209
+ <Text>
210
+ Jest reporter added to:{' '}
211
+ <Text color="cyan">{jestConfig.location}</Text>
212
+ </Text>
213
+ ) : (
214
+ <Text color="yellow">
215
+ Could not auto-configure Jest. Add manually:
216
+ </Text>
217
+ )}
218
+ </Box>
219
+ {!jestConfig?.configured && (
220
+ <Box marginTop={1} flexDirection="column">
221
+ <Text color="gray">// jest.config.js</Text>
222
+ <Text color="gray">
223
+ reporters: ['default',
224
+ '@regressionproof/jest-reporter']
225
+ </Text>
226
+ </Box>
227
+ )}
228
+ <Box marginTop={1}>
229
+ <Text color="green">
230
+ Run your tests and snapshots will be captured
231
+ automatically!
232
+ </Text>
233
+ </Box>
234
+ </Box>
235
+ )
236
+ }
237
+
238
+ private renderError(): React.ReactElement {
239
+ return (
240
+ <Box flexDirection="column" padding={1}>
241
+ <Text color="red" bold>
242
+ Registration failed
243
+ </Text>
244
+ <Text color="red">{this.state.errorMessage}</Text>
245
+ </Box>
246
+ )
247
+ }
248
+
249
+ private renderInput(): React.ReactElement {
250
+ const { name } = this.state
251
+
252
+ return (
253
+ <Box flexDirection="column" padding={1}>
254
+ <BigText
255
+ text="regressionproof.ai"
256
+ font="tiny"
257
+ colors={['magenta', 'cyan']}
258
+ />
259
+ <Text color="gray">Teaching LLMs to write better code.</Text>
260
+
261
+ <Box marginTop={1} flexDirection="column">
262
+ <Text bold>Project name:</Text>
263
+ <Box>
264
+ <TextInput
265
+ value={name}
266
+ onChange={this.handleNameChange}
267
+ onSubmit={this.handleSubmit}
268
+ placeholder="my-awesome-project"
269
+ />
270
+ <Box marginLeft={2}>{this.renderStatusIndicator()}</Box>
271
+ </Box>
272
+ {name.length > 0 && name.length < 3 && (
273
+ <Text color="gray">
274
+ Name must be at least 3 characters
275
+ </Text>
276
+ )}
277
+ </Box>
278
+ </Box>
279
+ )
280
+ }
281
+
282
+ public render(): React.ReactElement {
283
+ const { step } = this.state
284
+
285
+ switch (step) {
286
+ case 'registering':
287
+ return this.renderRegistering()
288
+ case 'configuring':
289
+ return this.renderConfiguring()
290
+ case 'success':
291
+ return this.renderSuccess()
292
+ case 'error':
293
+ return this.renderError()
294
+ default:
295
+ return this.renderInput()
296
+ }
297
+ }
298
+ }
299
+
300
+ export default function Init(props: { projectName?: string }): React.ReactElement {
301
+ const { exit } = useApp()
302
+ return <InitComponent exit={exit} projectName={props.projectName} />
303
+ }
304
+
305
+ type Step = 'input' | 'registering' | 'configuring' | 'success' | 'error'
306
+ type Availability = 'idle' | 'checking' | 'available' | 'taken' | 'error'
307
+
308
+ interface Props {
309
+ exit: () => void
310
+ projectName?: string
311
+ }
312
+
313
+ interface State {
314
+ name: string
315
+ step: Step
316
+ availability: Availability
317
+ errorMessage: string
318
+ credentials: Credentials | null
319
+ jestConfig: JestConfigResult | null
320
+ }
@@ -0,0 +1,64 @@
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 ADDED
@@ -0,0 +1 @@
1
+ //exports go here