| … | … | @@ -27,6 +27,10 @@ try { config = { ...config, ...JSON.parse(fs.readFileSync(configPath, 'utf-8')) |
| 27 | 27 | const serverFlag = args.indexOf('--server') |
| 28 | 28 | if (serverFlag >= 0) config.server = args[serverFlag + 1] |
| 29 | 29 | |
| 30 | // Token from env takes priority |
| 31 | const envToken = typeof Bare !== 'undefined' ? Bare.env?.PGH_TOKEN : process.env.PGH_TOKEN |
| 32 | if (envToken) config.token = envToken |
| 33 | |
| 30 | 34 | const client = new PghClient(config) |
| 31 | 35 | |
| 32 | 36 | async function run () { |
| … | … | @@ -104,15 +108,71 @@ async function run () { |
| 104 | 108 | case 'app': { |
| 105 | 109 | if (sub === 'install') { |
| 106 | 110 | const repo = args[2] |
| 107 | | const name = args[3] || repo |
| 108 | | if (!repo) return usage('pgh app install <repo> [name]') |
| 111 | const nameFlag = args.indexOf('--name') |
| 112 | const name = nameFlag >= 0 ? args[nameFlag + 1] : (args[3] || repo) |
| 113 | if (!repo) return usage('pgh app install <repo> [--name name]') |
| 114 | |
| 109 | 115 | const installDir = path.join(os.homedir(), '.local', 'share', name) |
| 110 | | console.log(`Cloning ${repo} release branch to ${installDir}...`) |
| 111 | | // TODO: implement git clone of release branch |
| 112 | | console.log('Not yet implemented — use: git clone --depth 1 -b release <url>') |
| 116 | const binDir = path.join(os.homedir(), '.local', 'bin') |
| 117 | const binPath = path.join(binDir, name) |
| 118 | |
| 119 | console.log(`Installing ${repo} → ${installDir}`) |
| 120 | |
| 121 | // Determine clone URL (punch:// key or server-based) |
| 122 | const cloneUrl = repo.startsWith('punch://') ? repo : `${config.server}/${repo}.git` |
| 123 | |
| 124 | const { spawn: spawnChild } = await import('bare-subprocess') |
| 125 | try { |
| 126 | await execCmd(spawnChild, ['git', 'clone', '--depth', '1', '-b', 'release', cloneUrl, installDir]) |
| 127 | } catch (err) { |
| 128 | // If dir exists, do a pull instead |
| 129 | if (err.message.includes('already exists')) { |
| 130 | console.log('Directory exists, pulling latest...') |
| 131 | await execCmd(spawnChild, ['git', '-C', installDir, 'fetch', 'origin', 'release']) |
| 132 | await execCmd(spawnChild, ['git', '-C', installDir, 'reset', '--hard', 'FETCH_HEAD']) |
| 133 | } else { |
| 134 | throw err |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | // Create launcher script |
| 139 | try { fs.mkdirSync(binDir, { recursive: true }) } catch {} |
| 140 | fs.writeFileSync(binPath, `#!/bin/sh\nexec bare "${installDir}/index.js" "$@"\n`) |
| 141 | fs.chmodSync(binPath, 0o755) |
| 142 | |
| 143 | console.log(`Installed. Run: ${name}`) |
| 113 | 144 | return |
| 114 | 145 | } |
| 115 | | return usage('pgh app install <repo> [name]') |
| 146 | |
| 147 | if (sub === 'list') { |
| 148 | const shareDir = path.join(os.homedir(), '.local', 'share') |
| 149 | const binDir = path.join(os.homedir(), '.local', 'bin') |
| 150 | try { |
| 151 | const bins = fs.readdirSync(binDir) |
| 152 | for (const b of bins) { |
| 153 | try { |
| 154 | const content = fs.readFileSync(path.join(binDir, b), 'utf-8') |
| 155 | if (content.includes('.local/share/') && content.includes('bare')) { |
| 156 | console.log(b) |
| 157 | } |
| 158 | } catch {} |
| 159 | } |
| 160 | } catch { console.log('No apps installed') } |
| 161 | return |
| 162 | } |
| 163 | |
| 164 | if (sub === 'remove') { |
| 165 | const name = args[2] |
| 166 | if (!name) return usage('pgh app remove <name>') |
| 167 | const installDir = path.join(os.homedir(), '.local', 'share', name) |
| 168 | const binPath = path.join(os.homedir(), '.local', 'bin', name) |
| 169 | try { fs.rmSync(binPath) } catch {} |
| 170 | try { fs.rmSync(installDir, { recursive: true }) } catch {} |
| 171 | console.log(`Removed ${name}`) |
| 172 | return |
| 173 | } |
| 174 | |
| 175 | return usage('pgh app install|list|remove') |
| 116 | 176 | } |
| 117 | 177 | |
| 118 | 178 | default: |
| … | … | @@ -132,14 +192,30 @@ Commands: |
| 132 | 192 | pgh auth Start auth flow |
| 133 | 193 | pgh config Show config |
| 134 | 194 | pgh config-set <key> <value> Set config |
| 135 | | pgh app install <repo> [name] Install app |
| 195 | pgh app install <repo> [--name n] Install app from release branch |
| 196 | pgh app list List installed apps |
| 197 | pgh app remove <name> Uninstall app |
| 136 | 198 | |
| 137 | 199 | Options: |
| 138 | 200 | --server <url> Override server URL |
| 201 | |
| 202 | Environment: |
| 203 | PGH_TOKEN Auth token (overrides config) |
| 139 | 204 | `) |
| 140 | 205 | } |
| 141 | 206 | } |
| 142 | 207 | |
| 208 | function execCmd (spawnChild, [cmd, ...cmdArgs]) { |
| 209 | return new Promise((resolve, reject) => { |
| 210 | const child = spawnChild(cmd, cmdArgs, { stdio: ['ignore', 'pipe', 'pipe'] }) |
| 211 | const out = []; const err = [] |
| 212 | child.stdout.on('data', d => out.push(d)) |
| 213 | child.stderr.on('data', d => err.push(d)) |
| 214 | child.on('exit', code => code === 0 ? resolve(Buffer.concat(out).toString()) : reject(new Error(Buffer.concat(err).toString() || `exit ${code}`))) |
| 215 | child.on('error', reject) |
| 216 | }) |
| 217 | } |
| 218 | |
| 143 | 219 | function log (data) { console.log(JSON.stringify(data, null, 2)) } |
| 144 | 220 | function logList (arr, fmt) { arr.forEach(item => console.log(fmt(item))) } |
| 145 | 221 | function usage (msg) { console.log('Usage:', msg) } |