Skip to content

Commit 6069578

Browse files
committed
chore: wip
1 parent 3eb8cbd commit 6069578

File tree

12 files changed

+724
-401
lines changed

12 files changed

+724
-401
lines changed

packages/launchpad/bin/cli.ts

Lines changed: 381 additions & 393 deletions
Large diffs are not rendered by default.

packages/launchpad/src/cli/router.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import process from 'node:process'
2-
import { parseArgv } from './parse'
32
import { listCommands, resolveCommand } from '../commands'
3+
import { parseArgv } from './parse'
44

55
export async function runCLI(rawArgv: string[] = process.argv.slice(2)): Promise<number> {
66
const { command, argv } = parseArgv(rawArgv)
@@ -23,7 +23,7 @@ export async function runCLI(rawArgv: string[] = process.argv.slice(2)): Promise
2323

2424
function printHelp() {
2525
const commands = listCommands()
26-
console.log('Launchpad CLI (modular)')
26+
console.log('Launchpad CLI')
2727
console.log('')
2828
console.log('Available commands:')
2929
for (const c of commands) console.log(` • ${c}`)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* eslint-disable no-console */
2+
import type { Command } from '../cli/types'
3+
import { execSync } from 'node:child_process'
4+
import fs from 'node:fs'
5+
import { homedir } from 'node:os'
6+
import path from 'node:path'
7+
8+
const cmd: Command = {
9+
name: 'build-env',
10+
aliases: ['env'],
11+
description: 'Set up build environment for launchpad-installed packages',
12+
help: `
13+
Examples:
14+
launchpad build-env
15+
launchpad build-env --path ~/.local/share/launchpad/global
16+
launchpad build-env --shell | source /dev/stdin
17+
`,
18+
async run({ argv }): Promise<number> {
19+
// parse flags
20+
let customPath: string | undefined
21+
let shell = false
22+
for (let i = 0; i < argv.length; i++) {
23+
const a = argv[i]
24+
if (a === '--path') {
25+
customPath = argv[i + 1]
26+
i++
27+
}
28+
else if (a === '--shell') {
29+
shell = true
30+
}
31+
}
32+
33+
const defaultInstallPath = path.join(homedir(), '.local', 'share', 'launchpad', 'global')
34+
const installPath = customPath || defaultInstallPath
35+
const buildEnvScript = path.join(installPath, 'build-env.sh')
36+
37+
if (!fs.existsSync(buildEnvScript)) {
38+
console.error('❌ Build environment script not found. Please install some packages first.')
39+
console.error(` Expected location: ${buildEnvScript}`)
40+
return 1
41+
}
42+
43+
if (shell) {
44+
const scriptContent = fs.readFileSync(buildEnvScript, 'utf-8')
45+
console.log(scriptContent)
46+
return 0
47+
}
48+
49+
execSync(`source "${buildEnvScript}"`, { stdio: 'inherit', shell: '/bin/sh' })
50+
return 0
51+
},
52+
}
53+
54+
export default cmd
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Command } from '../cli/types'
2+
import { disableService } from '../services'
3+
4+
function parseArgs(argv: string[]): { service?: string } {
5+
const args = argv.filter(a => !a.startsWith('--'))
6+
return { service: args[0] }
7+
}
8+
9+
const cmd: Command = {
10+
name: 'disable',
11+
description: 'Disable a service from auto-starting on boot',
12+
async run({ argv }): Promise<number> {
13+
const { service } = parseArgs(argv)
14+
if (!service) {
15+
console.error('No service specified')
16+
return 1
17+
}
18+
const success = await disableService(service)
19+
return success ? 0 : 1
20+
},
21+
}
22+
23+
export default cmd
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Command } from '../cli/types'
2+
import { enableService } from '../services'
3+
4+
function parseArgs(argv: string[]): { service?: string } {
5+
const args = argv.filter(a => !a.startsWith('--'))
6+
return { service: args[0] }
7+
}
8+
9+
const cmd: Command = {
10+
name: 'enable',
11+
description: 'Enable a service for auto-start on boot',
12+
async run({ argv }): Promise<number> {
13+
const { service } = parseArgs(argv)
14+
if (!service) {
15+
console.error('No service specified')
16+
return 1
17+
}
18+
const success = await enableService(service)
19+
return success ? 0 : 1
20+
},
21+
}
22+
23+
export default cmd

packages/launchpad/src/commands/index.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,35 @@ const registry: Record<string, () => Promise<Command>> = {
3232
'outdated': async () => (await import('./outdated')).default,
3333
'update': async () => (await import('./update')).default,
3434
'debug:deps': async () => (await import('./debug/deps')).default,
35+
// services
36+
'start': async () => (await import('./start')).default,
37+
'stop': async () => (await import('./stop')).default,
38+
'restart': async () => (await import('./restart')).default,
39+
'enable': async () => (await import('./enable')).default,
40+
'disable': async () => (await import('./disable')).default,
41+
'status': async () => (await import('./status')).default,
42+
'services': async () => (await import('./services')).default,
43+
// build env
44+
'build-env': async () => (await import('./build-env')).default,
3545
}
3646

3747
// Aliases map to canonical command names
3848
const aliases: Record<string, string> = {
39-
remove: 'uninstall',
40-
packages: 'tags',
49+
'remove': 'uninstall',
50+
'packages': 'tags',
4151
'cache:info': 'cache:stats',
42-
up: 'update',
52+
'up': 'update',
4353
'self-update': 'upgrade',
54+
'service': 'services',
4455
}
4556

4657
export async function resolveCommand(name?: string): Promise<Command | undefined> {
4758
if (!name)
4859
return undefined
4960
const key = aliases[name] || name
5061
const loader = registry[key]
51-
if (!loader) return undefined
62+
if (!loader)
63+
return undefined
5264
return loader()
5365
}
5466

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Command } from '../cli/types'
2+
import { restartService } from '../services'
3+
4+
function parseArgs(argv: string[]): { service?: string } {
5+
const args = argv.filter(a => !a.startsWith('--'))
6+
return { service: args[0] }
7+
}
8+
9+
const cmd: Command = {
10+
name: 'restart',
11+
description: 'Restart a service',
12+
async run({ argv }): Promise<number> {
13+
const { service } = parseArgs(argv)
14+
if (!service) {
15+
console.error('No service specified')
16+
return 1
17+
}
18+
const success = await restartService(service)
19+
return success ? 0 : 1
20+
},
21+
}
22+
23+
export default cmd
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { Command } from '../cli/types'
2+
import process from 'node:process'
3+
4+
const cmd: Command = {
5+
name: 'services',
6+
aliases: ['service'],
7+
description: 'List all services and their status',
8+
async run({ argv }) {
9+
// Delegate to status without a service name
10+
const mod = await import('./status')
11+
return mod.default.run({ argv, env: process.env })
12+
},
13+
}
14+
15+
export default cmd
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Command } from '../cli/types'
2+
import { startService } from '../services'
3+
4+
function parseArgs(argv: string[]): { service?: string } {
5+
const args = argv.filter(a => !a.startsWith('--'))
6+
return { service: args[0] }
7+
}
8+
9+
const cmd: Command = {
10+
name: 'start',
11+
description: 'Start a service',
12+
async run({ argv }): Promise<number> {
13+
const { service } = parseArgs(argv)
14+
if (!service) {
15+
console.error('No service specified')
16+
return 1
17+
}
18+
const success = await startService(service)
19+
return success ? 0 : 1
20+
},
21+
}
22+
23+
export default cmd
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import type { Command } from '../cli/types'
2+
import { getServiceStatus, listServices, getAllServiceDefinitions } from '../services'
3+
4+
interface Options {
5+
format?: 'table' | 'json' | 'simple'
6+
}
7+
8+
function parse(argv: string[]): { service?: string, opts: Options } {
9+
const args: string[] = []
10+
const opts: Options = {}
11+
for (let i = 0; i < argv.length; i++) {
12+
const a = argv[i]
13+
if (a === '--format') {
14+
const val = argv[i + 1]
15+
if (val) {
16+
opts.format = val as any
17+
i++
18+
}
19+
}
20+
else if (!a.startsWith('--')) {
21+
args.push(a)
22+
}
23+
}
24+
return { service: args[0], opts }
25+
}
26+
27+
const cmd: Command = {
28+
name: 'status',
29+
description: 'Show service status',
30+
async run({ argv }): Promise<number> {
31+
const { service, opts } = parse(argv)
32+
33+
if (service) {
34+
const status = await getServiceStatus(service)
35+
const format = opts.format || 'simple'
36+
if (format === 'json') {
37+
console.log(JSON.stringify({ service, status }, null, 2))
38+
}
39+
else {
40+
const statusEmoji: Record<string, string> = {
41+
running: '🟢',
42+
stopped: '🔴',
43+
starting: '🟡',
44+
stopping: '🟡',
45+
failed: '🔴',
46+
unknown: '⚪',
47+
}
48+
console.log(`${statusEmoji[status]} ${service}: ${status}`)
49+
}
50+
return 0
51+
}
52+
53+
const services = await listServices()
54+
const format = opts.format || 'table'
55+
56+
if (format === 'json') {
57+
const result = services.map(service => ({
58+
name: service.definition?.name,
59+
displayName: service.definition?.displayName,
60+
status: service.status,
61+
enabled: service.enabled,
62+
pid: service.pid,
63+
startedAt: service.startedAt,
64+
port: service.definition?.port,
65+
}))
66+
console.log(JSON.stringify(result, null, 2))
67+
return 0
68+
}
69+
70+
if (format === 'simple') {
71+
if (services.length === 0) {
72+
console.log('No services found')
73+
return 0
74+
}
75+
services.forEach((service) => {
76+
const statusEmoji: Record<string, string> = {
77+
running: '🟢',
78+
stopped: '🔴',
79+
starting: '🟡',
80+
stopping: '🟡',
81+
failed: '🔴',
82+
unknown: '⚪',
83+
}
84+
console.log(`${statusEmoji[service.status]} ${service.definition?.name}: ${service.status}`)
85+
})
86+
return 0
87+
}
88+
89+
// table
90+
if (services.length === 0) {
91+
console.log('No services found')
92+
console.log('')
93+
console.log('Available services:')
94+
const definitions = getAllServiceDefinitions()
95+
definitions.forEach((def) => {
96+
console.log(` ${def.name?.padEnd(12)} ${def.displayName}`)
97+
})
98+
return 0
99+
}
100+
101+
console.log('Service Status:')
102+
console.log('')
103+
console.log(`${'Name'.padEnd(12) + 'Status'.padEnd(12) + 'Enabled'.padEnd(10) + 'PID'.padEnd(8) + 'Port'.padEnd(8)}Description`)
104+
console.log('─'.repeat(70))
105+
106+
services.forEach((service) => {
107+
const statusEmoji: Record<string, string> = {
108+
running: '🟢',
109+
stopped: '🔴',
110+
starting: '🟡',
111+
stopping: '🟡',
112+
failed: '🔴',
113+
unknown: '⚪',
114+
}
115+
116+
const name = service.definition?.name?.padEnd(12) || 'unknown'.padEnd(12)
117+
const status = `${statusEmoji[service.status]} ${service.status}`.padEnd(12)
118+
const enabled = (service.enabled ? '✅' : '❌').padEnd(10)
119+
const pid = (service.pid ? String(service.pid) : '-').padEnd(8)
120+
const port = (service.definition?.port ? String(service.definition.port) : '-').padEnd(8)
121+
const description = service.definition?.description || ''
122+
123+
console.log(`${name}${status}${enabled}${pid}${port}${description}`)
124+
})
125+
126+
return 0
127+
},
128+
}
129+
130+
export default cmd

0 commit comments

Comments
 (0)