Skip to content

Commit d61d44a

Browse files
committed
chore: wip
1 parent 3ab2c10 commit d61d44a

30 files changed

+3819
-597
lines changed

bun.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"@stacksjs/docs": "^0.70.23",
88
"@stacksjs/eslint-config": "^4.14.0-beta.3",
99
"@types/bun": "^1.2.19",
10-
"buddy-bot": "^0.4.4",
10+
"buddy-bot": "^0.5.27",
1111
"bumpp": "^10.2.0",
1212
"bun-plugin-dtsx": "0.9.5",
1313
"changelogen": "^0.6.2",
@@ -848,7 +848,7 @@
848848

849849
"browserslist": ["[email protected]", "", { "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw=="],
850850

851-
"buddy-bot": ["buddy-bot@0.4.4", "", { "dependencies": { "@types/prompts": "^2.4.9", "bunfig": "^0.10.1", "cac": "6.7.13", "prompts": "^2.4.2", "ts-pkgx": "0.4.4" }, "bin": { "buddy-bot": "dist/bin/cli.js" } }, "sha512-TjAnFlK7oc8jjRCy1TVMWzx5g8MfAcaN+FeWZ+8uf2nHfV2DyZ/SYZgr3so+6rtY7IEJIRIuYjs3FcUgBCmfzw=="],
851+
"buddy-bot": ["buddy-bot@0.5.27", "", { "dependencies": { "@types/prompts": "^2.4.9", "bunfig": "^0.10.1", "cac": "6.7.13", "prompts": "^2.4.2", "ts-pkgx": "0.4.4" }, "bin": { "buddy-bot": "dist/bin/cli.js" } }, "sha512-hN2y+Bw6FNnYjJHQjnW34LsAw0g9ppWbk3eZOJe/Kp7qAUavgRzVYnbnwi6/r8SO11IQfOvEAPFJinRd9spcIA=="],
852852

853853
"buffer-crc32": ["[email protected]", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
854854

launchpad

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env bun
2+
import('./packages/launchpad/bin/cli')

packages/launchpad/bin/cli.ts

Lines changed: 204 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#!/usr/bin/env bun
22
import fs from 'node:fs'
3+
import { homedir } from 'node:os'
34
import path from 'node:path'
45
import process from 'node:process'
56
import { CAC } from 'cac'
7+
import { Glob } from 'bun'
68
import { install, install_prefix, list, uninstall } from '../src'
79
import { config } from '../src/config'
810
import { dump, integrate, shellcode } from '../src/dev'
@@ -381,51 +383,236 @@ async function performSetup(options: {
381383
}
382384
}
383385

386+
/**
387+
* Check if a path is a directory
388+
*/
389+
async function isDirectory(path: string): Promise<boolean> {
390+
try {
391+
const stats = await fs.promises.stat(path)
392+
return stats.isDirectory()
393+
}
394+
catch {
395+
return false
396+
}
397+
}
398+
399+
/**
400+
* Set up development environment for a project directory
401+
*/
402+
async function setupDevelopmentEnvironment(
403+
targetDir: string,
404+
options: { dryRun?: boolean, quiet?: boolean, shell?: boolean },
405+
): Promise<void> {
406+
// For shell integration, force quiet mode and set environment variable
407+
const isShellIntegration = options?.shell || false
408+
if (isShellIntegration) {
409+
process.env.LAUNCHPAD_SHELL_INTEGRATION = '1'
410+
}
411+
412+
await dump(targetDir, {
413+
dryrun: options?.dryRun || false,
414+
quiet: options?.quiet || isShellIntegration,
415+
shellOutput: isShellIntegration,
416+
skipGlobal: process.env.NODE_ENV === 'test' || process.env.LAUNCHPAD_SKIP_GLOBAL_AUTO_SCAN === 'true',
417+
})
418+
}
419+
420+
/**
421+
* Find all dependency files across the machine and install global dependencies
422+
*/
423+
async function installGlobalDependencies(options: {
424+
dryRun?: boolean
425+
quiet?: boolean
426+
verbose?: boolean
427+
}): Promise<void> {
428+
if (!options.quiet) {
429+
console.log('🔍 Scanning machine for dependency files...')
430+
}
431+
432+
const { DEPENDENCY_FILE_NAMES } = await import('../src/env')
433+
const globalDepLocations = [
434+
homedir(),
435+
path.join(homedir(), '.dotfiles'),
436+
path.join(homedir(), '.config'),
437+
path.join(homedir(), 'Desktop'),
438+
path.join(homedir(), 'Documents'),
439+
path.join(homedir(), 'Projects'),
440+
path.join(homedir(), 'Code'),
441+
path.join(homedir(), 'Development'),
442+
'/opt',
443+
'/usr/local',
444+
]
445+
446+
const foundFiles: string[] = []
447+
const allPackages = new Set<string>()
448+
449+
// Use Bun's glob for efficient file searching
450+
const dependencyPatterns = DEPENDENCY_FILE_NAMES.map(name => `**/${name}`)
451+
452+
// Search for dependency files in all potential locations using Bun glob
453+
for (const location of globalDepLocations) {
454+
if (!fs.existsSync(location))
455+
continue
456+
457+
try {
458+
// Search for all dependency file types in parallel using Bun's glob
459+
for (const pattern of dependencyPatterns) {
460+
const glob = new Glob(pattern)
461+
for await (const file of glob.scan({
462+
cwd: location,
463+
onlyFiles: true,
464+
followSymlinks: false
465+
})) {
466+
const fullPath = path.resolve(location, file)
467+
foundFiles.push(fullPath)
468+
}
469+
}
470+
}
471+
catch {
472+
// Ignore permission errors or other issues
473+
continue
474+
}
475+
}
476+
477+
if (!options.quiet) {
478+
console.log(`📁 Found ${foundFiles.length} dependency files`)
479+
}
480+
481+
// Parse all found dependency files and extract packages
482+
const { default: sniff } = await import('../src/dev/sniff')
483+
484+
for (const file of foundFiles) {
485+
try {
486+
const dir = path.dirname(file)
487+
const sniffResult = await sniff({ string: dir })
488+
489+
for (const pkg of sniffResult.pkgs) {
490+
allPackages.add(pkg.project)
491+
}
492+
493+
if (options.verbose) {
494+
console.log(` 📄 ${file}: ${sniffResult.pkgs.length} packages`)
495+
}
496+
}
497+
catch (error) {
498+
if (options.verbose) {
499+
console.warn(`Failed to parse ${file}: ${error instanceof Error ? error.message : String(error)}`)
500+
}
501+
}
502+
}
503+
504+
const packageList = Array.from(allPackages)
505+
506+
if (!options.quiet) {
507+
console.log(`📦 Found ${packageList.length} unique global dependencies`)
508+
if (options.dryRun) {
509+
console.log('🔍 Packages that would be installed:')
510+
packageList.forEach(pkg => console.log(` • ${pkg}`))
511+
return
512+
}
513+
}
514+
515+
if (packageList.length === 0) {
516+
if (!options.quiet) {
517+
console.log('ℹ️ No global dependencies found')
518+
}
519+
return
520+
}
521+
522+
// Install all global dependencies
523+
const globalEnvDir = path.join(homedir(), '.local', 'share', 'launchpad', 'global')
524+
525+
try {
526+
const results = await install(packageList, globalEnvDir)
527+
528+
if (!options.quiet) {
529+
if (results.length > 0) {
530+
console.log(`🎉 Successfully installed ${packageList.length} global dependencies (${results.length} binaries)`)
531+
}
532+
else {
533+
console.log('✅ All global dependencies were already installed')
534+
}
535+
}
536+
}
537+
catch (error) {
538+
if (!options.quiet) {
539+
console.error('❌ Failed to install global dependencies:', error instanceof Error ? error.message : String(error))
540+
}
541+
throw error
542+
}
543+
}
544+
384545
const cli = new CAC('launchpad')
385546

386547
cli.version(version)
387548
cli.help()
388549

389550
// Main installation command
390551
cli
391-
.command('install [packages...]', 'Install packages')
552+
.command('install [packages...]', 'Install packages or set up development environment')
392553
.alias('i')
393554
.alias('add')
394555
.option('--verbose', 'Enable verbose output')
395556
.option('--path <path>', 'Custom installation path')
557+
.option('--global-deps', 'Install all global dependencies found across the machine')
558+
.option('--dry-run', 'Show packages that would be installed without installing them')
559+
.option('--quiet', 'Suppress non-error output')
560+
.option('--shell', 'Output shell code for evaluation (use with eval)')
396561
.example('launchpad install node python')
397562
.example('launchpad install --path ~/.local node python')
563+
.example('launchpad install')
564+
.example('launchpad install ./my-project')
565+
.example('launchpad install --global-deps')
398566
.example('launchpad add node python')
399-
.action(async (packages: string[], options: { verbose?: boolean, path?: string }) => {
567+
.action(async (packages: string[], options: {
568+
verbose?: boolean
569+
path?: string
570+
globalDeps?: boolean
571+
dryRun?: boolean
572+
quiet?: boolean
573+
shell?: boolean
574+
}) => {
400575
if (options.verbose) {
401576
config.verbose = true
402577
}
403578

404579
// Ensure packages is an array
405580
const packageList = Array.isArray(packages) ? packages : [packages].filter(Boolean)
406581

407-
if (packageList.length === 0) {
408-
console.error('No packages specified')
409-
process.exit(1)
410-
}
411-
412582
try {
413-
const installPath = options.path || install_prefix().string
583+
// Handle global dependencies installation
584+
if (options.globalDeps) {
585+
await installGlobalDependencies(options)
586+
return
587+
}
414588

589+
// Handle development environment setup (no packages specified or directory path given)
590+
if (packageList.length === 0 || (packageList.length === 1 && await isDirectory(packageList[0]))) {
591+
const targetDir = packageList.length === 1 ? path.resolve(packageList[0]) : process.cwd()
592+
await setupDevelopmentEnvironment(targetDir, options)
593+
return
594+
}
595+
596+
// Handle regular package installation
597+
const installPath = options.path || install_prefix().string
415598
const results = await install(packageList, installPath)
416599

417-
if (results.length > 0) {
418-
console.log(`🎉 Successfully installed ${packageList.join(', ')} (${results.length} ${results.length === 1 ? 'binary' : 'binaries'})`)
419-
results.forEach((file) => {
420-
console.log(` ${file}`)
421-
})
422-
}
423-
else {
424-
console.log('⚠️ No binaries were installed')
600+
if (!options.quiet) {
601+
if (results.length > 0) {
602+
console.log(`🎉 Successfully installed ${packageList.join(', ')} (${results.length} ${results.length === 1 ? 'binary' : 'binaries'})`)
603+
results.forEach((file) => {
604+
console.log(` ${file}`)
605+
})
606+
}
607+
else {
608+
console.log('⚠️ No binaries were installed')
609+
}
425610
}
426611
}
427612
catch (error) {
428-
console.error('Installation failed:', error instanceof Error ? error.message : String(error))
613+
if (!options.quiet) {
614+
console.error('Installation failed:', error instanceof Error ? error.message : String(error))
615+
}
429616
process.exit(1)
430617
}
431618
})
@@ -1164,52 +1351,6 @@ cli
11641351
console.log(shellcode(testMode))
11651352
})
11661353

1167-
cli
1168-
.command('dev [dir]', 'Set up development environment for project dependencies')
1169-
.option('--dry-run', 'Show packages that would be installed without installing them')
1170-
.option('--quiet', 'Suppress non-error output')
1171-
.option('--shell', 'Output shell code for evaluation (use with eval)')
1172-
.action(async (dir?: string, options?: { dryRun?: boolean, quiet?: boolean, shell?: boolean }) => {
1173-
try {
1174-
const targetDir = dir ? path.resolve(dir) : process.cwd()
1175-
1176-
// For shell integration, force quiet mode and set environment variable
1177-
const isShellIntegration = options?.shell || false
1178-
if (isShellIntegration) {
1179-
process.env.LAUNCHPAD_SHELL_INTEGRATION = '1'
1180-
}
1181-
1182-
await dump(targetDir, {
1183-
dryrun: options?.dryRun || false,
1184-
quiet: options?.quiet || isShellIntegration, // Force quiet for shell integration
1185-
shellOutput: isShellIntegration,
1186-
skipGlobal: process.env.NODE_ENV === 'test' || process.env.LAUNCHPAD_SKIP_GLOBAL_AUTO_SCAN === 'true', // Skip global packages only in test mode or when explicitly disabled
1187-
})
1188-
}
1189-
catch (error) {
1190-
if (!options?.quiet && !options?.shell) {
1191-
console.error('Failed to set up dev environment:', error instanceof Error ? error.message : String(error))
1192-
}
1193-
else if (options?.shell) {
1194-
// For shell mode, output robust fallback that ensures basic system tools are available
1195-
// This prevents shell integration from hanging or failing
1196-
console.log('# Environment setup failed, using system fallback')
1197-
console.log('# Ensure basic system paths are available')
1198-
console.log('for sys_path in /usr/local/bin /usr/bin /bin /usr/sbin /sbin; do')
1199-
console.log(' if [[ -d "$sys_path" && ":$PATH:" != *":$sys_path:"* ]]; then')
1200-
console.log(' export PATH="$PATH:$sys_path"')
1201-
console.log(' fi')
1202-
console.log('done')
1203-
console.log('# Clear command hash to ensure fresh lookups')
1204-
console.log('hash -r 2>/dev/null || true')
1205-
return
1206-
}
1207-
if (!options?.shell) {
1208-
process.exit(1)
1209-
}
1210-
}
1211-
})
1212-
12131354
cli
12141355
.command('dev:integrate', 'Install shell integration hooks')
12151356
.option('--uninstall', 'Remove shell integration hooks')

packages/launchpad/launchpad

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)