@fiftth/fiftth-cli 0.1.0 → 1.0.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/.github/workflows/publish-npm.yml +62 -0
- package/README.md +123 -111
- package/dist/api/client.d.ts +1 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +4 -0
- package/dist/api/client.js.map +1 -1
- package/dist/commands/checkout.d.ts +1 -1
- package/dist/commands/checkout.d.ts.map +1 -1
- package/dist/commands/checkout.js +63 -31
- package/dist/commands/checkout.js.map +1 -1
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +4 -1
- package/dist/commands/tasks.js.map +1 -1
- package/dist/config/configService.d.ts.map +1 -1
- package/dist/config/configService.js +17 -4
- package/dist/config/configService.js.map +1 -1
- package/dist/index.js +25 -25
- package/dist/index.js.map +1 -1
- package/dist/services/taskService.d.ts +1 -0
- package/dist/services/taskService.d.ts.map +1 -1
- package/dist/services/taskService.js +7 -0
- package/dist/services/taskService.js.map +1 -1
- package/package.json +56 -56
- package/src/api/client.ts +31 -26
- package/src/commands/checkout.ts +101 -68
- package/src/commands/login.ts +145 -145
- package/src/commands/repo.ts +113 -113
- package/src/commands/tasks.ts +86 -83
- package/src/commands/use.ts +149 -149
- package/src/config/configService.ts +56 -40
- package/src/context/runtimeContext.ts +42 -42
- package/src/git/gitService.ts +29 -29
- package/src/index.ts +133 -133
- package/src/services/taskContext.ts +32 -32
- package/src/services/taskService.ts +53 -45
- package/src/utils/api.ts +41 -41
- package/src/utils/config.ts +48 -48
- package/src/utils/ui.ts +46 -46
- package/tsconfig.json +18 -18
- package/vitest.config.ts +8 -8
package/src/commands/login.ts
CHANGED
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import { input, password, confirm } from '@inquirer/prompts'
|
|
3
|
-
import ora from 'ora'
|
|
4
|
-
import { loadConfig, saveConfig } from '../utils/config.js'
|
|
5
|
-
import { cmd, fail, info, kv, muted, ok, section, title } from '../utils/ui.js'
|
|
6
|
-
|
|
7
|
-
interface LoginOptions {
|
|
8
|
-
token?: string
|
|
9
|
-
host?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function normalizeToken(rawToken: string): string {
|
|
13
|
-
return rawToken.trim().replace(/^Bearer\s+/i, '')
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function validateToken(host: string, token: string): Promise<boolean> {
|
|
17
|
-
try {
|
|
18
|
-
const response = await fetch(`${host}/cli-tokens`, {
|
|
19
|
-
headers: {
|
|
20
|
-
Authorization: `Bearer ${token}`,
|
|
21
|
-
'User-Agent': 'fiftth-cli/0.1.0',
|
|
22
|
-
},
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
if (!response.ok) {
|
|
26
|
-
return false
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const body = (await response.json().catch(() => null)) as
|
|
30
|
-
| { valid?: boolean }
|
|
31
|
-
| null
|
|
32
|
-
|
|
33
|
-
return body?.valid === true
|
|
34
|
-
} catch {
|
|
35
|
-
return false
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function loginCommand(options: LoginOptions): Promise<void> {
|
|
40
|
-
const config = loadConfig()
|
|
41
|
-
const host = options.host ?? config.host
|
|
42
|
-
|
|
43
|
-
console.log(`\n${title('Welcome to Fiftth')}\n`)
|
|
44
|
-
console.log(muted('Orchestrate tasks, repos and agent workflows from your terminal.\n'))
|
|
45
|
-
|
|
46
|
-
if (config.token && !options.token) {
|
|
47
|
-
const alreadyLoggedIn = await confirm({
|
|
48
|
-
message: chalk.yellow('You are already logged in. Validate login again?'),
|
|
49
|
-
default: false,
|
|
50
|
-
})
|
|
51
|
-
if (!alreadyLoggedIn) {
|
|
52
|
-
console.log(`\n${muted('No changes made.')}\n`)
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let token: string
|
|
58
|
-
|
|
59
|
-
if (options.token) {
|
|
60
|
-
token = normalizeToken(options.token)
|
|
61
|
-
} else {
|
|
62
|
-
console.log(muted(`Generate an access token at: ${cmd(`${host}/cli-tokens`)}\n`))
|
|
63
|
-
token = await password({
|
|
64
|
-
message: 'Paste your access token:',
|
|
65
|
-
mask: '*',
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
if (!token || token.trim() === '') {
|
|
69
|
-
console.error(`\n${fail('Token cannot be empty.')}\n`)
|
|
70
|
-
process.exit(1)
|
|
71
|
-
}
|
|
72
|
-
token = normalizeToken(token)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const spinner = ora('Validating credentials...').start()
|
|
76
|
-
|
|
77
|
-
const valid = await validateToken(host, token)
|
|
78
|
-
|
|
79
|
-
if (!valid) {
|
|
80
|
-
spinner.fail(fail('Authentication failed.'))
|
|
81
|
-
console.error(`\n${muted('Make sure your token is correct and has not expired.')}\n`)
|
|
82
|
-
process.exit(1)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
spinner.succeed(ok('Authentication validated.'))
|
|
86
|
-
|
|
87
|
-
saveConfig({ ...config, token, host })
|
|
88
|
-
|
|
89
|
-
console.log(`\n${section('Session ready')}`)
|
|
90
|
-
console.log(kv('Host', cmd(host)))
|
|
91
|
-
console.log(kv('Next', `run ${cmd('fiftth use <workspace>')}`))
|
|
92
|
-
console.log()
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function logoutCommand(): Promise<void> {
|
|
96
|
-
const config = loadConfig()
|
|
97
|
-
|
|
98
|
-
if (!config.token) {
|
|
99
|
-
console.log(`\n${info('You are not currently logged in.')}\n`)
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const confirmed = await confirm({
|
|
104
|
-
message: 'Are you sure you want to log out?',
|
|
105
|
-
default: false,
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
if (!confirmed) {
|
|
109
|
-
console.log(`\n${muted('Logout cancelled.')}\n`)
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const { token: _token, ...rest } = config
|
|
114
|
-
saveConfig(rest as ReturnType<typeof loadConfig>)
|
|
115
|
-
|
|
116
|
-
console.log(`\n${ok('Logged out successfully.')}\n`)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export async function loginStatusCommand(): Promise<void> {
|
|
120
|
-
const config = loadConfig()
|
|
121
|
-
|
|
122
|
-
if (!config.token) {
|
|
123
|
-
console.log(`\n${info('Not logged in.')}\n`)
|
|
124
|
-
console.log(`${muted(`Run ${cmd('fiftth login')} to authenticate.`)}\n`)
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const spinner = ora('Checking authentication status...').start()
|
|
129
|
-
const valid = await validateToken(config.host, config.token)
|
|
130
|
-
|
|
131
|
-
if (valid) {
|
|
132
|
-
spinner.succeed(ok('Authenticated'))
|
|
133
|
-
console.log(`\n${kv('Host', cmd(config.host))}`)
|
|
134
|
-
if (config.workspace) {
|
|
135
|
-
console.log(kv('Workspace', cmd(config.workspace)))
|
|
136
|
-
}
|
|
137
|
-
console.log()
|
|
138
|
-
} else {
|
|
139
|
-
spinner.fail(fail('Token is invalid or expired.'))
|
|
140
|
-
console.log(`\n${muted(`Run ${cmd('fiftth login')} to authenticate again.`)}\n`)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export { input }
|
|
145
|
-
export { normalizeToken }
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { input, password, confirm } from '@inquirer/prompts'
|
|
3
|
+
import ora from 'ora'
|
|
4
|
+
import { loadConfig, saveConfig } from '../utils/config.js'
|
|
5
|
+
import { cmd, fail, info, kv, muted, ok, section, title } from '../utils/ui.js'
|
|
6
|
+
|
|
7
|
+
interface LoginOptions {
|
|
8
|
+
token?: string
|
|
9
|
+
host?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeToken(rawToken: string): string {
|
|
13
|
+
return rawToken.trim().replace(/^Bearer\s+/i, '')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function validateToken(host: string, token: string): Promise<boolean> {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(`${host}/cli-tokens`, {
|
|
19
|
+
headers: {
|
|
20
|
+
Authorization: `Bearer ${token}`,
|
|
21
|
+
'User-Agent': 'fiftth-cli/0.1.0',
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const body = (await response.json().catch(() => null)) as
|
|
30
|
+
| { valid?: boolean }
|
|
31
|
+
| null
|
|
32
|
+
|
|
33
|
+
return body?.valid === true
|
|
34
|
+
} catch {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function loginCommand(options: LoginOptions): Promise<void> {
|
|
40
|
+
const config = loadConfig()
|
|
41
|
+
const host = options.host ?? config.host
|
|
42
|
+
|
|
43
|
+
console.log(`\n${title('Welcome to Fiftth')}\n`)
|
|
44
|
+
console.log(muted('Orchestrate tasks, repos and agent workflows from your terminal.\n'))
|
|
45
|
+
|
|
46
|
+
if (config.token && !options.token) {
|
|
47
|
+
const alreadyLoggedIn = await confirm({
|
|
48
|
+
message: chalk.yellow('You are already logged in. Validate login again?'),
|
|
49
|
+
default: false,
|
|
50
|
+
})
|
|
51
|
+
if (!alreadyLoggedIn) {
|
|
52
|
+
console.log(`\n${muted('No changes made.')}\n`)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let token: string
|
|
58
|
+
|
|
59
|
+
if (options.token) {
|
|
60
|
+
token = normalizeToken(options.token)
|
|
61
|
+
} else {
|
|
62
|
+
console.log(muted(`Generate an access token at: ${cmd(`${host}/cli-tokens`)}\n`))
|
|
63
|
+
token = await password({
|
|
64
|
+
message: 'Paste your access token:',
|
|
65
|
+
mask: '*',
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
if (!token || token.trim() === '') {
|
|
69
|
+
console.error(`\n${fail('Token cannot be empty.')}\n`)
|
|
70
|
+
process.exit(1)
|
|
71
|
+
}
|
|
72
|
+
token = normalizeToken(token)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const spinner = ora('Validating credentials...').start()
|
|
76
|
+
|
|
77
|
+
const valid = await validateToken(host, token)
|
|
78
|
+
|
|
79
|
+
if (!valid) {
|
|
80
|
+
spinner.fail(fail('Authentication failed.'))
|
|
81
|
+
console.error(`\n${muted('Make sure your token is correct and has not expired.')}\n`)
|
|
82
|
+
process.exit(1)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
spinner.succeed(ok('Authentication validated.'))
|
|
86
|
+
|
|
87
|
+
saveConfig({ ...config, token, host })
|
|
88
|
+
|
|
89
|
+
console.log(`\n${section('Session ready')}`)
|
|
90
|
+
console.log(kv('Host', cmd(host)))
|
|
91
|
+
console.log(kv('Next', `run ${cmd('fiftth use <workspace>')}`))
|
|
92
|
+
console.log()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function logoutCommand(): Promise<void> {
|
|
96
|
+
const config = loadConfig()
|
|
97
|
+
|
|
98
|
+
if (!config.token) {
|
|
99
|
+
console.log(`\n${info('You are not currently logged in.')}\n`)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const confirmed = await confirm({
|
|
104
|
+
message: 'Are you sure you want to log out?',
|
|
105
|
+
default: false,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
if (!confirmed) {
|
|
109
|
+
console.log(`\n${muted('Logout cancelled.')}\n`)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const { token: _token, ...rest } = config
|
|
114
|
+
saveConfig(rest as ReturnType<typeof loadConfig>)
|
|
115
|
+
|
|
116
|
+
console.log(`\n${ok('Logged out successfully.')}\n`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function loginStatusCommand(): Promise<void> {
|
|
120
|
+
const config = loadConfig()
|
|
121
|
+
|
|
122
|
+
if (!config.token) {
|
|
123
|
+
console.log(`\n${info('Not logged in.')}\n`)
|
|
124
|
+
console.log(`${muted(`Run ${cmd('fiftth login')} to authenticate.`)}\n`)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const spinner = ora('Checking authentication status...').start()
|
|
129
|
+
const valid = await validateToken(config.host, config.token)
|
|
130
|
+
|
|
131
|
+
if (valid) {
|
|
132
|
+
spinner.succeed(ok('Authenticated'))
|
|
133
|
+
console.log(`\n${kv('Host', cmd(config.host))}`)
|
|
134
|
+
if (config.workspace) {
|
|
135
|
+
console.log(kv('Workspace', cmd(config.workspace)))
|
|
136
|
+
}
|
|
137
|
+
console.log()
|
|
138
|
+
} else {
|
|
139
|
+
spinner.fail(fail('Token is invalid or expired.'))
|
|
140
|
+
console.log(`\n${muted(`Run ${cmd('fiftth login')} to authenticate again.`)}\n`)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export { input }
|
|
145
|
+
export { normalizeToken }
|
package/src/commands/repo.ts
CHANGED
|
@@ -1,113 +1,113 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import os from 'os'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import chalk from 'chalk'
|
|
5
|
-
import { Command } from 'commander'
|
|
6
|
-
import {
|
|
7
|
-
addRepository,
|
|
8
|
-
getRepositories,
|
|
9
|
-
removeRepository,
|
|
10
|
-
} from '../config/configService.js'
|
|
11
|
-
import { cmd, fail, info, kv, muted, ok, section } from '../utils/ui.js'
|
|
12
|
-
|
|
13
|
-
function expandHomeDirectory(inputPath: string): string {
|
|
14
|
-
if (inputPath === '~') {
|
|
15
|
-
return os.homedir()
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
|
|
19
|
-
return path.join(os.homedir(), inputPath.slice(2))
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return inputPath
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function resolveRepoPath(inputPath: string): string {
|
|
26
|
-
const expandedPath = expandHomeDirectory(inputPath)
|
|
27
|
-
return path.resolve(expandedPath)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function validateRepositoryPath(repoPath: string): void {
|
|
31
|
-
if (!fs.existsSync(repoPath) || !fs.statSync(repoPath).isDirectory()) {
|
|
32
|
-
console.error(`\n${fail('Path not found.')}\n`)
|
|
33
|
-
process.exit(1)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const gitPath = path.join(repoPath, '.git')
|
|
37
|
-
|
|
38
|
-
if (!fs.existsSync(gitPath) || !fs.statSync(gitPath).isDirectory()) {
|
|
39
|
-
console.error(`\n${fail('The provided path is not a git repository.')}\n`)
|
|
40
|
-
process.exit(1)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function addRepoCommand(repoName: string, repoPathInput: string | string[]): void {
|
|
45
|
-
const normalizedPathInput = Array.isArray(repoPathInput)
|
|
46
|
-
? repoPathInput.join(' ')
|
|
47
|
-
: repoPathInput
|
|
48
|
-
|
|
49
|
-
const absolutePath = resolveRepoPath(normalizedPathInput)
|
|
50
|
-
validateRepositoryPath(absolutePath)
|
|
51
|
-
|
|
52
|
-
const { overwritten } = addRepository(repoName, absolutePath)
|
|
53
|
-
|
|
54
|
-
if (overwritten) {
|
|
55
|
-
console.log(chalk.yellow(`${info('Repository already exists. Overwriting mapping.')}`))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
console.log(`\n${ok('Repository registered')}\n`)
|
|
59
|
-
console.log(kv('Repo', repoName))
|
|
60
|
-
console.log(kv('Path', cmd(absolutePath)))
|
|
61
|
-
console.log()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function listRepoCommand(): void {
|
|
65
|
-
const repositories = getRepositories()
|
|
66
|
-
const entries = Object.entries(repositories)
|
|
67
|
-
|
|
68
|
-
if (entries.length === 0) {
|
|
69
|
-
console.log(`\n${info('No repositories configured yet.')}\n`)
|
|
70
|
-
console.log(`${muted(`Use ${cmd('fiftth repo add <repoName> <path>')}`)}\n`)
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const longestNameLength = Math.max(...entries.map(([name]) => name.length))
|
|
75
|
-
|
|
76
|
-
console.log(`\n${section('Configured repositories')}\n`)
|
|
77
|
-
for (const [repoName, repoPath] of entries) {
|
|
78
|
-
console.log(`${repoName.padEnd(longestNameLength, ' ')} ${muted('->')} ${cmd(repoPath)}`)
|
|
79
|
-
}
|
|
80
|
-
console.log()
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function removeRepoCommand(repoName: string): void {
|
|
84
|
-
const removed = removeRepository(repoName)
|
|
85
|
-
|
|
86
|
-
if (!removed) {
|
|
87
|
-
console.error(`\n${fail(`Repository '${repoName}' is not configured.`)}\n`)
|
|
88
|
-
process.exit(1)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
console.log(`\n${ok('Repository removed')}\n`)
|
|
92
|
-
console.log(kv('Repo', repoName))
|
|
93
|
-
console.log()
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function registerRepoCommands(program: Command): void {
|
|
97
|
-
const repoCmd = program.command('repo').description('Manage local repository mappings')
|
|
98
|
-
|
|
99
|
-
repoCmd
|
|
100
|
-
.command('add <repoName> <path...>')
|
|
101
|
-
.description('Register a local repository path')
|
|
102
|
-
.action(addRepoCommand)
|
|
103
|
-
|
|
104
|
-
repoCmd
|
|
105
|
-
.command('list')
|
|
106
|
-
.description('List all configured repositories')
|
|
107
|
-
.action(listRepoCommand)
|
|
108
|
-
|
|
109
|
-
repoCmd
|
|
110
|
-
.command('remove <repoName>')
|
|
111
|
-
.description('Remove a configured repository')
|
|
112
|
-
.action(removeRepoCommand)
|
|
113
|
-
}
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import os from 'os'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
import { Command } from 'commander'
|
|
6
|
+
import {
|
|
7
|
+
addRepository,
|
|
8
|
+
getRepositories,
|
|
9
|
+
removeRepository,
|
|
10
|
+
} from '../config/configService.js'
|
|
11
|
+
import { cmd, fail, info, kv, muted, ok, section } from '../utils/ui.js'
|
|
12
|
+
|
|
13
|
+
function expandHomeDirectory(inputPath: string): string {
|
|
14
|
+
if (inputPath === '~') {
|
|
15
|
+
return os.homedir()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
|
|
19
|
+
return path.join(os.homedir(), inputPath.slice(2))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return inputPath
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveRepoPath(inputPath: string): string {
|
|
26
|
+
const expandedPath = expandHomeDirectory(inputPath)
|
|
27
|
+
return path.resolve(expandedPath)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function validateRepositoryPath(repoPath: string): void {
|
|
31
|
+
if (!fs.existsSync(repoPath) || !fs.statSync(repoPath).isDirectory()) {
|
|
32
|
+
console.error(`\n${fail('Path not found.')}\n`)
|
|
33
|
+
process.exit(1)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const gitPath = path.join(repoPath, '.git')
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(gitPath) || !fs.statSync(gitPath).isDirectory()) {
|
|
39
|
+
console.error(`\n${fail('The provided path is not a git repository.')}\n`)
|
|
40
|
+
process.exit(1)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function addRepoCommand(repoName: string, repoPathInput: string | string[]): void {
|
|
45
|
+
const normalizedPathInput = Array.isArray(repoPathInput)
|
|
46
|
+
? repoPathInput.join(' ')
|
|
47
|
+
: repoPathInput
|
|
48
|
+
|
|
49
|
+
const absolutePath = resolveRepoPath(normalizedPathInput)
|
|
50
|
+
validateRepositoryPath(absolutePath)
|
|
51
|
+
|
|
52
|
+
const { overwritten } = addRepository(repoName, absolutePath)
|
|
53
|
+
|
|
54
|
+
if (overwritten) {
|
|
55
|
+
console.log(chalk.yellow(`${info('Repository already exists. Overwriting mapping.')}`))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(`\n${ok('Repository registered')}\n`)
|
|
59
|
+
console.log(kv('Repo', repoName))
|
|
60
|
+
console.log(kv('Path', cmd(absolutePath)))
|
|
61
|
+
console.log()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function listRepoCommand(): void {
|
|
65
|
+
const repositories = getRepositories()
|
|
66
|
+
const entries = Object.entries(repositories)
|
|
67
|
+
|
|
68
|
+
if (entries.length === 0) {
|
|
69
|
+
console.log(`\n${info('No repositories configured yet.')}\n`)
|
|
70
|
+
console.log(`${muted(`Use ${cmd('fiftth repo add <repoName> <path>')}`)}\n`)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const longestNameLength = Math.max(...entries.map(([name]) => name.length))
|
|
75
|
+
|
|
76
|
+
console.log(`\n${section('Configured repositories')}\n`)
|
|
77
|
+
for (const [repoName, repoPath] of entries) {
|
|
78
|
+
console.log(`${repoName.padEnd(longestNameLength, ' ')} ${muted('->')} ${cmd(repoPath)}`)
|
|
79
|
+
}
|
|
80
|
+
console.log()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function removeRepoCommand(repoName: string): void {
|
|
84
|
+
const removed = removeRepository(repoName)
|
|
85
|
+
|
|
86
|
+
if (!removed) {
|
|
87
|
+
console.error(`\n${fail(`Repository '${repoName}' is not configured.`)}\n`)
|
|
88
|
+
process.exit(1)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log(`\n${ok('Repository removed')}\n`)
|
|
92
|
+
console.log(kv('Repo', repoName))
|
|
93
|
+
console.log()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function registerRepoCommands(program: Command): void {
|
|
97
|
+
const repoCmd = program.command('repo').description('Manage local repository mappings')
|
|
98
|
+
|
|
99
|
+
repoCmd
|
|
100
|
+
.command('add <repoName> <path...>')
|
|
101
|
+
.description('Register a local repository path')
|
|
102
|
+
.action(addRepoCommand)
|
|
103
|
+
|
|
104
|
+
repoCmd
|
|
105
|
+
.command('list')
|
|
106
|
+
.description('List all configured repositories')
|
|
107
|
+
.action(listRepoCommand)
|
|
108
|
+
|
|
109
|
+
repoCmd
|
|
110
|
+
.command('remove <repoName>')
|
|
111
|
+
.description('Remove a configured repository')
|
|
112
|
+
.action(removeRepoCommand)
|
|
113
|
+
}
|