Skip to content

Commit 2490f0f

Browse files
committed
chore: wip
1 parent e00a816 commit 2490f0f

File tree

13 files changed

+229
-81
lines changed

13 files changed

+229
-81
lines changed

CLAUDE.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# CLAUDE.md - Launchpad Project Rules
2+
3+
This file contains specific rules and guidelines for working with the Launchpad project in Claude Code.
4+
5+
## Project Overview
6+
7+
Launchpad is a modern dependency manager for both system and project environments. It's like Homebrew but faster, with intelligent environment isolation and project-aware dependency management. Built on TypeScript with Bun runtime.
8+
9+
**Core Technologies:**
10+
11+
- TypeScript with strict typing
12+
- Bun runtime and package manager
13+
- pkgx ecosystem integration via ts-pkgx
14+
- CAC for CLI framework
15+
- Cross-platform (macOS, Linux, Windows)
16+
17+
## Project Structure
18+
19+
```
20+
launchpad/
21+
├── packages/
22+
│ ├── launchpad/ # Main package
23+
│ │ ├── src/ # Core source code
24+
│ │ │ ├── commands/ # CLI command implementations
25+
│ │ │ ├── services/ # Service management
26+
│ │ │ ├── cli/ # CLI parsing and routing
27+
│ │ │ ├── dev/ # Development utilities
28+
│ │ │ └── *.ts # Core modules (install, config, types, etc.)
29+
│ │ ├── bin/cli.ts # CLI entry point
30+
│ │ └── package.json # Package configuration
31+
│ └── action/ # GitHub Action package
32+
├── docs/ # Documentation
33+
├── scripts/ # Build and utility scripts
34+
└── launchpad.config.ts # Project configuration
35+
```
36+
37+
## Development Guidelines
38+
39+
### Code Style & Patterns
40+
41+
1. **TypeScript Standards:**
42+
- Use strict TypeScript with full type safety
43+
- Leverage ts-pkgx types for package management
44+
- Export types alongside implementations
45+
- Use interface/type definitions from `src/types.ts`
46+
47+
2. **Architecture Patterns:**
48+
- Commands follow the pattern: `src/commands/<command>.ts`
49+
- Each command exports a default function that handles the operation
50+
- Services are organized in `src/services/` with clear separation of concerns
51+
- Configuration is centralized in `src/config.ts`
52+
53+
3. **CLI Structure:**
54+
- Main CLI entry point: `packages/launchpad/bin/cli.ts`
55+
- Uses CAC framework for command parsing
56+
- Command resolution happens via `src/commands/index.ts`
57+
- All commands support `--verbose`, `--dry-run` where applicable
58+
59+
### Key Modules & Their Purpose
60+
61+
- `src/install.ts` - Package installation logic
62+
- `src/uninstall.ts` - Package removal logic
63+
- `src/config.ts` - Configuration management
64+
- `src/types.ts` - Type definitions (extends ts-pkgx types)
65+
- `src/services/` - Service management (PostgreSQL, Redis, etc.)
66+
- `src/dev/` - Development environment management
67+
- `src/commands/` - CLI command implementations
68+
69+
### Important Configuration Files
70+
71+
- `launchpad.config.ts` - Project-level configuration with typed dependencies
72+
- `packages/launchpad/package.json` - Package metadata and scripts
73+
- Root `package.json` - Monorepo workspace configuration
74+
75+
## Development Commands
76+
77+
```bash
78+
# Build the project
79+
bun run build
80+
81+
# Run tests
82+
bun test
83+
84+
# Lint code
85+
bun run lint
86+
bun run lint:fix
87+
88+
# Type checking
89+
bun run typecheck
90+
91+
# Development docs
92+
bun run dev:docs
93+
94+
# Generate changelog
95+
bun run changelog
96+
```
97+
98+
## Key Features to Understand
99+
100+
1. **Dual Installation Mode:**
101+
- System-wide: `/usr/local` (default) or `~/.local`
102+
- Project-specific: Automatic environment isolation
103+
104+
2. **Service Management:**
105+
- 30+ pre-configured services (PostgreSQL, Redis, etc.)
106+
- Cross-platform service control (launchd/systemd)
107+
- Auto-configuration with sane defaults
108+
109+
3. **Environment Management:**
110+
- Automatic project detection via dependency files
111+
- Human-readable environment identifiers
112+
- Seamless environment switching on `cd`
113+
114+
4. **Package Resolution:**
115+
- Leverages pkgx ecosystem via ts-pkgx
116+
- Full type safety for package names and versions
117+
- Intelligent dependency resolution
118+
119+
## Common Development Tasks
120+
121+
### Adding New Commands
122+
123+
1. Create command file in `src/commands/<name>.ts`
124+
2. Export default function with command logic
125+
3. Add command to `src/commands/index.ts` resolution map
126+
4. Update CLI definitions in `bin/cli.ts`
127+
5. Add corresponding types to `src/types.ts` if needed
128+
129+
### Adding New Services
130+
131+
1. Add service definition to `src/services/definitions.ts`
132+
2. Update service management logic in `src/services/manager.ts`
133+
3. Add platform-specific configurations if needed
134+
4. Update documentation and examples
135+
136+
### Configuration Changes
137+
138+
1. Update `LaunchpadConfig` interface in `src/types.ts`
139+
2. Update default config in `src/config.ts`
140+
3. Update example `launchpad.config.ts` if needed
141+
4. Document new options in README
142+
143+
## Testing & Quality
144+
145+
- Use `bun test` for running tests
146+
- All code should pass `bun run lint` and `bun run typecheck`
147+
- Test both system-wide and project-specific installation modes
148+
- Verify cross-platform compatibility where applicable
149+
150+
## Dependencies & Ecosystem
151+
152+
- **ts-pkgx**: Provides typed package definitions and ecosystem integration
153+
- **CAC**: CLI argument parsing framework
154+
- **bunfig**: Configuration management utilities
155+
- Built for Bun runtime but Node.js compatible
156+
157+
## Special Considerations
158+
159+
1. **Cross-platform Compatibility:** Code must work on macOS, Linux, and Windows
160+
2. **Permission Handling:** Smart handling of system vs user installations
161+
3. **Environment Isolation:** Never pollute global environment unintentionally
162+
4. **Performance:** Leverage Bun's speed advantages where possible
163+
5. **Type Safety:** Full TypeScript compliance with ts-pkgx integration
164+
165+
## Integration Points
166+
167+
- **GitHub Actions:** `packages/action/` provides CI/CD integration
168+
- **Shell Integration:** Automatic PATH management and environment switching
169+
- **pkgx Ecosystem:** Full compatibility with pkgx package registry
170+
- **Service Orchestration:** Native integration with system service managers
171+
172+
When working on this project, always consider the dual nature of system-wide and project-specific dependency management, maintain cross-platform compatibility, and leverage the strong typing provided by ts-pkgx for package management operations.

packages/launchpad/src/cache.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/* eslint-disable no-console */
22
import type { CacheMetadata } from './types'
33
import fs from 'node:fs'
4+
import { homedir } from 'node:os'
45
import path from 'node:path'
5-
import process from 'node:process'
66
import { config } from './config'
77

88
// Cache configuration for packages
9-
const CACHE_DIR = path.join(process.env.HOME || '.', '.cache', 'launchpad')
9+
const CACHE_DIR = path.join(homedir(), '.cache', 'launchpad')
1010
const BINARY_CACHE_DIR = path.join(CACHE_DIR, 'binaries', 'packages')
1111
const CACHE_METADATA_FILE = path.join(CACHE_DIR, 'cache-metadata.json')
1212

packages/launchpad/src/commands/shim.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Command } from '../cli/types'
2-
import process from 'node:process'
2+
import { homedir } from 'node:os'
3+
import path from 'node:path'
34
import { config, defaultConfig } from '../config'
45
import { create_shim } from '../shim'
56

@@ -64,7 +65,7 @@ const command: Command = {
6465
config.shimPath = shimPath
6566

6667
try {
67-
const basePath = (config.installPath ?? defaultConfig.installPath) ?? (process.env.HOME ? `${process.env.HOME}/.local` : '/usr/local')
68+
const basePath = (config.installPath ?? defaultConfig.installPath) ?? path.join(homedir(), '.local')
6869
const created = await create_shim(pkgs, basePath)
6970
if (created.length > 0) {
7071
console.warn(`Created ${created.length} shims`)

packages/launchpad/src/commands/upgrade.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable no-console */
22
import type { Command } from '../cli/types'
33
import fs from 'node:fs'
4+
import { homedir } from 'node:os'
45
import path from 'node:path'
56
import process from 'node:process'
67
import { config } from '../config'
@@ -50,9 +51,9 @@ async function detectCurrentBinaryPath(_verbose: boolean): Promise<string> {
5051
const realBinaryPaths = [
5152
'/usr/local/bin/launchpad',
5253
'/usr/bin/launchpad',
53-
path.join(process.env.HOME || '~', '.local/bin/launchpad'),
54-
path.join(process.env.HOME || '~', '.bun/bin/launchpad'),
55-
path.join(process.env.HOME || '~', 'bin/launchpad'),
54+
path.join(homedir(), '.local/bin/launchpad'),
55+
path.join(homedir(), '.bun/bin/launchpad'),
56+
path.join(homedir(), 'bin/launchpad'),
5657
]
5758

5859
for (const realPath of realBinaryPaths) {
@@ -86,9 +87,9 @@ async function detectCurrentBinaryPath(_verbose: boolean): Promise<string> {
8687
const commonPaths = [
8788
'/usr/local/bin/launchpad',
8889
'/usr/bin/launchpad',
89-
path.join(process.env.HOME || '~', '.local/bin/launchpad'),
90-
path.join(process.env.HOME || '~', '.bun/bin/launchpad'),
91-
path.join(process.env.HOME || '~', 'bin/launchpad'),
90+
path.join(homedir(), '.local/bin/launchpad'),
91+
path.join(homedir(), '.bun/bin/launchpad'),
92+
path.join(homedir(), 'bin/launchpad'),
9293
]
9394

9495
for (const p of commonPaths) {

packages/launchpad/src/config.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ function getDefaultInstallPath(): string {
2020
return '/usr/local'
2121
}
2222
catch {
23-
const homePath = process.env.HOME || process.env.USERPROFILE || '~'
24-
return path.join(homePath, '.local')
23+
return path.join(homedir(), '.local')
2524
}
2625
}
2726

@@ -31,18 +30,15 @@ function getDefaultShimPath(): string {
3130
return process.env.LAUNCHPAD_SHIM_PATH
3231
}
3332

34-
const homePath = process.env.HOME || process.env.USERPROFILE || '~'
35-
return path.join(homePath, '.local', 'bin')
33+
return path.join(homedir(), '.local', 'bin')
3634
}
3735

3836
function getDefaultCacheDir(): string {
39-
const homePath = process.env.HOME || process.env.USERPROFILE || '~'
40-
return path.join(homePath, '.local', 'share', 'launchpad', 'cache')
37+
return path.join(homedir(), '.local', 'share', 'launchpad', 'cache')
4138
}
4239

4340
function getDefaultLogDir(): string {
44-
const homePath = process.env.HOME || process.env.USERPROFILE || '~'
45-
return path.join(homePath, '.local', 'share', 'launchpad', 'logs')
41+
return path.join(homedir(), '.local', 'share', 'launchpad', 'logs')
4642
}
4743

4844
function isCI(): boolean {

packages/launchpad/src/dev/dump.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -364,8 +364,8 @@ async function executepostSetup(projectDir: string, commands: PostSetupCommand[]
364364

365365
// Build PATH that includes project env first, then global env, then original PATH
366366
const projectHash = generateProjectHash(projectDir)
367-
const envDir = path.join(process.env.HOME || '', '.local', 'share', 'launchpad', 'envs', projectHash)
368-
const globalEnvDir = path.join(process.env.HOME || '', '.local', 'share', 'launchpad', 'global')
367+
const envDir = path.join(homedir(), '.local', 'share', 'launchpad', 'envs', projectHash)
368+
const globalEnvDir = path.join(homedir(), '.local', 'share', 'launchpad', 'global')
369369
const envBinPath = path.join(envDir, 'bin')
370370
const envSbinPath = path.join(envDir, 'sbin')
371371
const globalBinPath = path.join(globalEnvDir, 'bin')
@@ -778,8 +778,8 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
778778
depSuffix = `-d${depHash}`
779779
}
780780
catch {}
781-
const envDir = path.join(process.env.HOME || '', '.local', 'share', 'launchpad', 'envs', `${projectHash}${depSuffix}`)
782-
const globalEnvDir = path.join(process.env.HOME || '', '.local', 'share', 'launchpad', 'global')
781+
const envDir = path.join(homedir(), '.local', 'share', 'launchpad', 'envs', `${projectHash}${depSuffix}`)
782+
const globalEnvDir = path.join(homedir(), '.local', 'share', 'launchpad', 'global')
783783

784784
// Check if environments exist first (quick filesystem check)
785785
const hasLocalEnv = fs.existsSync(path.join(envDir, 'bin'))
@@ -1203,8 +1203,8 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
12031203
depSuffix = `-d${depHash}`
12041204
}
12051205
catch {}
1206-
const envDir = path.join(process.env.HOME || '', '.local', 'share', 'launchpad', 'envs', `${projectHash}${depSuffix}`)
1207-
const globalEnvDir = path.join(process.env.HOME || '', '.local', 'share', 'launchpad', 'global')
1206+
const envDir = path.join(homedir(), '.local', 'share', 'launchpad', 'envs', `${projectHash}${depSuffix}`)
1207+
const globalEnvDir = path.join(homedir(), '.local', 'share', 'launchpad', 'global')
12081208

12091209
// For shell output mode, check if we can skip expensive operations
12101210
if (shellOutput) {

packages/launchpad/src/dev/shellcode.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { homedir } from 'node:os'
12
import { join } from 'node:path'
23
import process from 'node:process'
34
import { config } from '../config'
@@ -506,5 +507,5 @@ export function datadir(): string {
506507
}
507508

508509
function platform_data_home_default(): string {
509-
return join(process.env.HOME || '~', '.local', 'share', 'launchpad')
510+
return join(homedir(), '.local', 'share', 'launchpad')
510511
}

packages/launchpad/src/install-core.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Buffer } from 'node:buffer'
44
import fs from 'node:fs'
55
import path from 'node:path'
66
import process from 'node:process'
7-
87
import { getCachedPackagePath, savePackageToCache } from './cache'
98
import { config } from './config'
109
import { createBuildEnvironmentScript, createPkgConfigSymlinks, createShims, createVersionCompatibilitySymlinks, createVersionSymlinks, validatePackageInstallation } from './install-helpers'

packages/launchpad/src/services/manager.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import type { ServiceInstance, ServiceManagerState, ServiceOperation, ServiceStatus } from '../types'
33
import { spawn } from 'node:child_process'
44
import fs from 'node:fs'
5-
import { platform } from 'node:os'
5+
import { homedir, platform } from 'node:os'
66
import path from 'node:path'
77
import process from 'node:process'
88
import { config } from '../config'
@@ -879,7 +879,7 @@ async function ensureServicePackageInstalled(service: ServiceInstance): Promise<
879879
}
880880

881881
// Install the main service package - this will automatically install all dependencies
882-
const installPath = `${process.env.HOME}/.local`
882+
const installPath = path.join(homedir(), '.local')
883883

884884
// Call install with proper error handling and shorter timeout
885885
try {
@@ -1028,7 +1028,7 @@ async function autoInitializeDatabase(service: ServiceInstance): Promise<boolean
10281028
// PostgreSQL auto-initialization
10291029
if (definition.name === 'postgres' || definition.name === 'postgresql') {
10301030
// Use the definition's dataDirectory for consistency with runtime
1031-
const dataDir = service.dataDir || definition.dataDirectory || path.join(process.env.HOME || '', '.local', 'share', 'launchpad', 'services', 'postgres', 'data')
1031+
const dataDir = service.dataDir || definition.dataDirectory || path.join(homedir(), '.local', 'share', 'launchpad', 'services', 'postgres', 'data')
10321032
const pgVersionFile = path.join(dataDir, 'PG_VERSION')
10331033

10341034
// Check if already initialized
@@ -1137,7 +1137,7 @@ async function autoInitializeDatabase(service: ServiceInstance): Promise<boolean
11371137

11381138
// MySQL auto-initialization
11391139
if (definition.name === 'mysql' || definition.name === 'mariadb') {
1140-
const dataDir = service.dataDir || path.join(process.env.HOME || '', '.local', 'share', 'launchpad', 'mysql-data')
1140+
const dataDir = service.dataDir || path.join(homedir(), '.local', 'share', 'launchpad', 'mysql-data')
11411141
const mysqlDir = path.join(dataDir, 'mysql')
11421142

11431143
// Check if already initialized

packages/launchpad/src/shim.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
import fs from 'node:fs'
33
import { EOL } from 'node:os'
44
import path from 'node:path'
5-
import process from 'node:process'
65
import { config } from './config'
76
import { install } from './install'
87
import { Path } from './path'
9-
import { addToPath, getUserShell, isInPath, isTemporaryDirectory } from './utils'
8+
import { addToPath, expandTildePath, getUserShell, isInPath, isTemporaryDirectory } from './utils'
109

1110
/**
1211
* Create a shim for a package
@@ -175,12 +174,9 @@ function isExecutable(filePath: string): boolean {
175174
export function shim_dir(): Path {
176175
// Use the configured shimPath if available
177176
if (config.shimPath) {
178-
// Handle ~ in the path
179-
if (config.shimPath.startsWith('~')) {
180-
const homePath = process.env.HOME || process.env.USERPROFILE || ''
181-
return new Path(config.shimPath.replace(/^~/, homePath))
182-
}
183-
return new Path(config.shimPath)
177+
// Properly expand tilde in the path using Node.js homedir()
178+
const expandedPath = expandTildePath(config.shimPath)
179+
return new Path(expandedPath)
184180
}
185181

186182
// Fall back to default ~/.local/bin

0 commit comments

Comments
 (0)