feat(tx): project dir loader

chore: cleanups
This commit is contained in:
2024-02-13 03:14:53 +02:00
parent 735fa358d9
commit 744a14a608
6 changed files with 145 additions and 35 deletions

View File

@@ -137,3 +137,4 @@ alias hli="hl && hi"
alias keypresses="xxd -psd"
alias install-utils="pushd \$DOTFILES/utils; pnpm install && pnpm build && pnpm ginst; popd"
alias lg="lazygit"
alias txp="tx p"

View File

@@ -21,10 +21,10 @@
"author": "",
"license": "ISC",
"dependencies": {
"@types/node": "^20.11.16",
"@types/node": "^20.11.17",
"cosmiconfig": "^9.0.0",
"massarg": "2.0.0",
"prettier": "^3.2.4",
"prettier": "^3.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}

22
utils/pnpm-lock.yaml generated
View File

@@ -6,8 +6,8 @@ settings:
dependencies:
'@types/node':
specifier: ^20.11.16
version: 20.11.16
specifier: ^20.11.17
version: 20.11.17
cosmiconfig:
specifier: ^9.0.0
version: 9.0.0(typescript@5.3.3)
@@ -15,11 +15,11 @@ dependencies:
specifier: 2.0.0
version: 2.0.0
prettier:
specifier: ^3.2.4
version: 3.2.4
specifier: ^3.2.5
version: 3.2.5
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@20.11.16)(typescript@5.3.3)
version: 10.9.2(@types/node@20.11.17)(typescript@5.3.3)
typescript:
specifier: ^5.3.3
version: 5.3.3
@@ -87,8 +87,8 @@ packages:
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
dev: false
/@types/node@20.11.16:
resolution: {integrity: sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==}
/@types/node@20.11.17:
resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==}
dependencies:
undici-types: 5.26.5
dev: false
@@ -247,8 +247,8 @@ packages:
lines-and-columns: 1.2.4
dev: false
/prettier@3.2.4:
resolution: {integrity: sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==}
/prettier@3.2.5:
resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
engines: {node: '>=14'}
hasBin: true
dev: false
@@ -265,7 +265,7 @@ packages:
has-flag: 3.0.0
dev: false
/ts-node@10.9.2(@types/node@20.11.16)(typescript@5.3.3):
/ts-node@10.9.2(@types/node@20.11.17)(typescript@5.3.3):
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true
peerDependencies:
@@ -284,7 +284,7 @@ packages:
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 20.11.16
'@types/node': 20.11.17
acorn: 8.11.3
acorn-walk: 8.3.2
arg: 4.1.3

View File

@@ -9,6 +9,7 @@ import { showCmd } from './show_cmd'
import { editCmd } from './edit_cmd'
import { rmCmd } from './rm_cmd'
import { attachCmd } from './attach_cmd'
import { prjCmd } from './prj_cmd'
// ================================================================================
// Commands
@@ -57,4 +58,5 @@ mainCmd
.command(rmCmd)
.command(createCmd)
.command(attachCmd)
.command(prjCmd)
.parse()

70
utils/src/tmux/prj_cmd.ts Normal file
View File

@@ -0,0 +1,70 @@
import * as path from 'node:path'
import * as os from 'node:os'
import * as fs from 'node:fs/promises'
import { Opts, log } from '../common'
import { MassargCommand } from 'massarg/command'
import { fzf, isDirectory, nameFix, parseConfig, pathExists } from './utils'
import { createFromConfig } from './command_builder'
import { format } from 'massarg/style'
export type CreateOpts = Opts & {
rootDir?: string
window?: string[]
save?: boolean
saveOnly?: boolean
local?: boolean
}
export const prjCmd = new MassargCommand<CreateOpts>({
name: 'prj',
aliases: ['p'],
description: 'Create a new tmux session (temporary) from project folder',
run: async (opts) => {
try {
const devProjects = await getProjects(opts)
const output = await fzf(opts, devProjects, { allowCustom: true })
if (!output) {
throw new Error('No selection')
}
const projectDir = path.join(os.homedir(), 'Dev', output)
const exists = await pathExists(projectDir)
if (!exists) {
log(opts, `Creating dir: ${projectDir}`)
await fs.mkdir(projectDir, { recursive: true })
}
const config = parseConfig(output, {
name: nameFix(output),
root: projectDir,
windows: ['.'],
})
return createFromConfig(opts, config)
} catch (error) {
console.log(format(error?.toString() ?? 'Unknown error', { color: 'red' }))
}
},
})
.flag({
name: 'verbose',
aliases: ['v'],
description: 'Verbose logs',
})
.flag({
name: 'dry',
aliases: ['d'],
description: 'Dry run',
})
.help({ bindOption: true, bindCommand: true })
async function getProjects(_opts: Opts) {
const devDir = path.join(os.homedir(), 'Dev')
const devFiles = await fs.readdir(devDir)
let devProjects = [] as string[]
for (const file of devFiles) {
if (await isDirectory(path.join(devDir, file))) {
devProjects.push(file)
}
}
return devProjects
}

View File

@@ -1,6 +1,7 @@
import { CosmiconfigResult, cosmiconfig } from 'cosmiconfig'
import * as path from 'node:path'
import * as os from 'node:os'
import * as fs from 'node:fs/promises'
import { Opts, getCommandOutput, runCommand } from '../common'
import { spawn } from 'node:child_process'
@@ -203,21 +204,21 @@ export function throwNoConfigFound() {
[
'tmux config file not found, searched in:',
'\t' +
searchDirs
.map((x) =>
searchPatterns('tmux')
.map((y) => path.join(x, y))
.join('\n\t'),
)
.join('\n\t'),
searchDirs
.map((x) =>
searchPatterns('tmux')
.map((y) => path.join(x, y))
.join('\n\t'),
)
.join('\n\t'),
'\t' +
searchDirs
.map((x) =>
searchPatterns('tmux_local')
.map((y) => path.join(x, y))
.join('\n\t'),
)
.join('\n\t'),
searchDirs
.map((x) =>
searchPatterns('tmux_local')
.map((y) => path.join(x, y))
.join('\n\t'),
)
.join('\n\t'),
// searchInFor('tmux').map(x => path.join(d)).join('\n\t'),
// searchInFor('tmux_local').join('\n\t'),
].join('\n'),
@@ -241,8 +242,20 @@ export async function sessionExists(opts: Opts, sessionName: string): Promise<bo
}
}
export async function fzf(_opts: Opts, inputs: string[]): Promise<string> {
const fzf = spawn(`echo "${inputs.join('\n')}" | fzf`, {
export type FzfOptions = {
allowCustom?: boolean
}
export async function fzf(
_opts: Opts,
inputs: string[],
fzfOpts: FzfOptions = {},
): Promise<string> {
let cmd = `echo "${inputs.join('\n')}" | fzf`
if (fzfOpts.allowCustom) {
cmd += ' --print-query | tail -1'
}
const fzf = spawn(cmd, {
stdio: ['inherit', 'pipe', 'inherit'],
shell: true,
})
@@ -250,14 +263,20 @@ export async function fzf(_opts: Opts, inputs: string[]): Promise<string> {
fzf.stdout.setEncoding('utf-8')
return new Promise((resolve, reject) => {
fzf.stdout.on('readable', function () {
fzf.stdout.on('readable', function() {
const value = fzf.stdout.read()
if (value !== null) {
resolve(value.toString().trim())
return
}
reject()
reject(new Error('fzf cancelled or encountered an error'))
})
fzf.on('exit', (code) => {
if (code === 1) {
reject(new Error('fzf cancelled or encountered an error'))
}
})
})
}
@@ -306,10 +325,28 @@ function parseLayout(layoutInput: TmuxLayoutInput | undefined, root: string): Tm
zoom: layout.zoom,
split: layout.split
? ({
direction:
typeof layout.split === 'string' ? layout.split : layout.split.direction || 'h',
child: parseLayout(layout.split.child, path.resolve(root, layout.cwd)),
} as TmuxSplitLayout)
direction:
typeof layout.split === 'string' ? layout.split : layout.split.direction || 'h',
child: parseLayout(layout.split.child, path.resolve(root, layout.cwd)),
} as TmuxSplitLayout)
: undefined,
}
}
export async function pathExists(path: string) {
return fs.stat(path).catch(() => false)
}
export async function isDirectory(path: string) {
return fs
.stat(path)
.then((stat) => stat.isDirectory())
.catch(() => false)
}
export async function isFile(path: string) {
return fs
.stat(path)
.then((stat) => stat.isFile())
.catch(() => false)
}