open source is social v-it.org
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add source-install workflow: vit hack, vit link, standalone hack script

+273 -4
+3 -3
Makefile
··· 1 - .PHONY: install install-user test test-node clean 1 + .PHONY: install link test test-node clean 2 2 3 3 install: 4 4 bun install 5 5 6 - install-user: 7 - bun link 6 + link: 7 + bun install && node bin/vit.js link 8 8 9 9 test: test-node 10 10 bun test
+42
hack
··· 1 + #!/bin/sh 2 + set -e 3 + 4 + SELF="solpbc/vit" 5 + 6 + command_exists() { 7 + command -v "$1" >/dev/null 2>&1 8 + } 9 + 10 + echo "installing vit from source ($SELF)..." 11 + 12 + if ! command_exists git; then 13 + echo "error: git is required. install git and try again." 14 + exit 1 15 + fi 16 + 17 + if ! command_exists bun; then 18 + echo "bun not found, installing..." 19 + curl -fsSL https://bun.sh/install | bash 20 + export BUN_INSTALL="$HOME/.bun" 21 + export PATH="$BUN_INSTALL/bin:$PATH" 22 + fi 23 + 24 + DIR_NAME=$(echo "$SELF" | sed 's|.*/||') 25 + if [ -d "$DIR_NAME" ]; then 26 + echo "$DIR_NAME/ already exists, skipping clone" 27 + else 28 + if command_exists gh; then 29 + gh repo clone "$SELF" "$DIR_NAME" 30 + else 31 + git clone "https://github.com/$SELF.git" "$DIR_NAME" 32 + fi 33 + fi 34 + 35 + cd "$DIR_NAME" 36 + 37 + bun install 38 + node bin/vit.js link 39 + 40 + echo "" 41 + echo "vit installed from source" 42 + echo "run: cd $DIR_NAME"
+4
src/cli.js
··· 17 17 import registerVouch from './cmd/vouch.js'; 18 18 import registerFollow from './cmd/follow.js'; 19 19 import registerSetup from './cmd/setup.js'; 20 + import registerHack from './cmd/hack.js'; 21 + import registerLink from './cmd/link.js'; 20 22 21 23 const program = new Command(); 22 24 program ··· 38 40 registerVouch(program); 39 41 registerFollow(program); 40 42 registerSetup(program); 43 + registerHack(program); 44 + registerLink(program); 41 45 42 46 export { program };
+19 -1
src/cmd/doctor.js
··· 4 4 import { loadConfig } from '../lib/config.js'; 5 5 import { restoreAgent } from '../lib/oauth.js'; 6 6 import { readProjectConfig } from '../lib/vit-dir.js'; 7 - import { existsSync } from 'node:fs'; 7 + import { existsSync, lstatSync } from 'node:fs'; 8 8 import { join } from 'node:path'; 9 9 import { mark, name } from '../lib/brand.js'; 10 + import { which } from '../lib/compat.js'; 10 11 11 12 export default function register(program) { 12 13 async function checkHealth() { ··· 17 18 console.log(`${mark} setup: ok (${when})`); 18 19 } else { 19 20 console.log(`${mark} setup: not done (run ${name} setup)`); 21 + } 22 + 23 + const vitPath = which(name); 24 + if (!vitPath) { 25 + console.log(`${mark} install: not on PATH`); 26 + } else { 27 + try { 28 + if (lstatSync(vitPath).isSymbolicLink()) { 29 + console.log(`${mark} install: linked (${vitPath})`); 30 + } else if (vitPath.includes('node_modules')) { 31 + console.log(`${mark} install: global`); 32 + } else { 33 + console.log(`${mark} install: source (${vitPath})`); 34 + } 35 + } catch { 36 + console.log(`${mark} install: source (${vitPath})`); 37 + } 20 38 } 21 39 22 40 const projConfig = readProjectConfig();
+83
src/cmd/hack.js
··· 1 + // SPDX-License-Identifier: AGPL-3.0-only 2 + // Copyright (c) 2026 sol pbc 3 + 4 + import { execFileSync } from 'node:child_process'; 5 + import { existsSync, readFileSync, writeFileSync } from 'node:fs'; 6 + import { join, resolve } from 'node:path'; 7 + import { which } from '../lib/compat.js'; 8 + import { mark, name } from '../lib/brand.js'; 9 + 10 + export default function register(program) { 11 + program 12 + .command('hack') 13 + .description('Fork and install vit from source') 14 + .option('--from <repo>', 'Clone an existing fork instead of creating a new one (e.g. user/vit)') 15 + .action(async (opts) => { 16 + try { 17 + const ghPath = which('gh'); 18 + if (!opts.from && !ghPath) { 19 + console.error(`${name} hack requires gh (GitHub CLI). Install it from https://cli.github.com`); 20 + process.exitCode = 1; 21 + return; 22 + } 23 + 24 + const gitPath = which('git'); 25 + if (opts.from && !gitPath) { 26 + console.error('missing required tool: git'); 27 + process.exitCode = 1; 28 + return; 29 + } 30 + 31 + const repo = opts.from || 'solpbc/vit'; 32 + const dirName = repo.split('/').pop(); 33 + const dirPath = resolve(process.cwd(), dirName); 34 + const cloned = !existsSync(dirPath); 35 + 36 + if (!cloned) { 37 + console.log(`${mark} ${dirName}/ already exists, skipping clone`); 38 + } else if (opts.from) { 39 + execFileSync('git', ['clone', 'https://github.com/' + opts.from + '.git', dirName], { stdio: 'inherit' }); 40 + } else { 41 + execFileSync('gh', ['repo', 'fork', 'solpbc/vit', '--clone'], { stdio: 'inherit' }); 42 + } 43 + 44 + process.chdir(dirName); 45 + 46 + const bunPath = which('bun'); 47 + if (!bunPath) { 48 + console.error('missing required tool: bun'); 49 + process.exitCode = 1; 50 + return; 51 + } 52 + 53 + execFileSync('bun', ['install'], { stdio: 'inherit' }); 54 + execFileSync('node', [join(process.cwd(), 'bin', 'vit.js'), 'link'], { stdio: 'inherit' }); 55 + 56 + if (!opts.from) { 57 + const remote = execFileSync( 58 + 'gh', 59 + ['repo', 'view', '--json', 'nameWithOwner', '-q', '.nameWithOwner'], 60 + { encoding: 'utf-8' } 61 + ).trim(); 62 + const hackPath = join(process.cwd(), 'hack'); 63 + if (existsSync(hackPath)) { 64 + const current = readFileSync(hackPath, 'utf-8'); 65 + writeFileSync(hackPath, current.replace('SELF="solpbc/vit"', `SELF="${remote}"`)); 66 + } 67 + } 68 + 69 + if (opts.from && cloned) { 70 + try { 71 + execFileSync('git', ['remote', 'add', 'upstream', 'https://github.com/solpbc/vit.git'], { stdio: 'inherit' }); 72 + } catch {} 73 + } 74 + 75 + console.log(''); 76 + console.log(`${mark} ${name} installed from source`); 77 + console.log(`run: cd ${dirName}`); 78 + } catch (err) { 79 + console.error(err instanceof Error ? err.message : String(err)); 80 + process.exitCode = 1; 81 + } 82 + }); 83 + }
+75
src/cmd/link.js
··· 1 + // SPDX-License-Identifier: AGPL-3.0-only 2 + // Copyright (c) 2026 sol pbc 3 + 4 + import { existsSync, mkdirSync, symlinkSync, unlinkSync, readlinkSync, writeFileSync } from 'node:fs'; 5 + import { join, resolve } from 'node:path'; 6 + import { mark, name } from '../lib/brand.js'; 7 + 8 + export default function register(program) { 9 + program 10 + .command('link') 11 + .description('Link vit binary into ~/.local/bin (or create .cmd shim on Windows)') 12 + .action(async () => { 13 + try { 14 + const vitBin = resolve(import.meta.dirname, '..', '..', 'bin', 'vit.js'); 15 + if (!existsSync(vitBin)) { 16 + console.error(`${name} link could not find ${vitBin}`); 17 + process.exitCode = 1; 18 + return; 19 + } 20 + 21 + let binDir; 22 + if (process.platform === 'win32') { 23 + const home = process.env.USERPROFILE || process.env.HOME; 24 + binDir = join(home, '.local', 'bin'); 25 + mkdirSync(binDir, { recursive: true }); 26 + const target = join(binDir, 'vit.cmd'); 27 + writeFileSync(target, `@node "${vitBin}" %*\n`); 28 + console.log(`${mark} link: ${target} -> ${vitBin}`); 29 + } else { 30 + binDir = join(process.env.HOME, '.local', 'bin'); 31 + mkdirSync(binDir, { recursive: true }); 32 + const target = join(binDir, 'vit'); 33 + if (existsSync(target)) { 34 + try { 35 + const current = readlinkSync(target); 36 + if (current === vitBin) { 37 + console.log(`${mark} link: already linked`); 38 + } else { 39 + unlinkSync(target); 40 + symlinkSync(vitBin, target); 41 + console.log(`${mark} link: ${target} -> ${vitBin}`); 42 + } 43 + } catch { 44 + unlinkSync(target); 45 + symlinkSync(vitBin, target); 46 + console.log(`${mark} link: ${target} -> ${vitBin}`); 47 + } 48 + } else { 49 + symlinkSync(vitBin, target); 50 + console.log(`${mark} link: ${target} -> ${vitBin}`); 51 + } 52 + } 53 + 54 + const pathDirs = (process.env.PATH || '').split(process.platform === 'win32' ? ';' : ':'); 55 + if (!pathDirs.includes(binDir)) { 56 + const shell = (process.env.SHELL || '').split('/').pop(); 57 + let instruction = '[Environment]::SetEnvironmentVariable("PATH", "$env:USERPROFILE\\.local\\bin;" + $env:PATH, "User")'; 58 + if (shell === 'fish') { 59 + instruction = 'set -Ua fish_user_paths ~/.local/bin'; 60 + } else if (shell === 'zsh') { 61 + instruction = 'echo \'export PATH="$HOME/.local/bin:$PATH"\' >> ~/.zshrc'; 62 + } else if (shell === 'bash') { 63 + instruction = 'echo \'export PATH="$HOME/.local/bin:$PATH"\' >> ~/.bashrc'; 64 + } 65 + console.log(`${mark} note: add ~/.local/bin to your PATH:`); 66 + console.log(instruction); 67 + } else { 68 + console.log(`${mark} PATH: ok`); 69 + } 70 + } catch (err) { 71 + console.error(err instanceof Error ? err.message : String(err)); 72 + process.exitCode = 1; 73 + } 74 + }); 75 + }
+5
test/doctor.test.js
··· 25 25 expect(result.stdout).toMatch(/bluesky:/); 26 26 }); 27 27 28 + test('reports install type', () => { 29 + const result = run('doctor'); 30 + expect(result.stdout).toMatch(/install:/); 31 + }); 32 + 28 33 test('vit status is an alias for doctor', () => { 29 34 const result = run('status'); 30 35 expect(result.stdout).toMatch(/setup:/);
+18
test/hack.test.js
··· 1 + // SPDX-License-Identifier: AGPL-3.0-only 2 + // Copyright (c) 2026 sol pbc 3 + 4 + import { describe, test, expect } from 'bun:test'; 5 + import { run } from './helpers.js'; 6 + 7 + describe('vit hack', () => { 8 + test('--help shows usage', () => { 9 + const result = run('hack --help'); 10 + expect(result.stdout).toContain('Fork'); 11 + expect(result.exitCode).toBe(0); 12 + }); 13 + 14 + test('--help shows --from option', () => { 15 + const result = run('hack --help'); 16 + expect(result.stdout).toContain('--from'); 17 + }); 18 + });
+24
test/link.test.js
··· 1 + // SPDX-License-Identifier: AGPL-3.0-only 2 + // Copyright (c) 2026 sol pbc 3 + 4 + import { describe, test, expect } from 'bun:test'; 5 + import { run } from './helpers.js'; 6 + 7 + describe('vit link', () => { 8 + test('--help shows usage', () => { 9 + const result = run('link --help'); 10 + expect(result.stdout).toContain('Link'); 11 + expect(result.exitCode).toBe(0); 12 + }); 13 + 14 + test('creates symlink without error', () => { 15 + const result = run('link'); 16 + expect(result.exitCode).toBe(0); 17 + }); 18 + 19 + test('is idempotent', () => { 20 + run('link'); 21 + const result = run('link'); 22 + expect(result.exitCode).toBe(0); 23 + }); 24 + });