Skip to content

Commit 375e818

Browse files
committed
chore: several improvements
chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip
1 parent 89fbe7e commit 375e818

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3973
-117
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ packages/launchpad/bin/launchpad
1515
packages/launchpad/bin/launchpad*
1616
packages/launchpad/test/clean-integration-*
1717
*.bun-build
18+
/.local

bun.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
},
1818
"packages/action": {
1919
"name": "launchpad-installer",
20-
"version": "0.5.1",
20+
"version": "0.5.2",
2121
"bin": {
2222
"launchpad-installer": "dist/index.js",
2323
},
@@ -34,14 +34,14 @@
3434
},
3535
"packages/launchpad": {
3636
"name": "@stacksjs/launchpad",
37-
"version": "0.5.1",
37+
"version": "0.5.2",
3838
"bin": {
3939
"launchpad": "./dist/bin/cli.js",
4040
},
4141
"dependencies": {
4242
"bunfig": "^0.10.1",
4343
"cac": "^6.7.14",
44-
"ts-pkgx": "0.3.171",
44+
"ts-pkgx": "0.4.4",
4545
},
4646
"devDependencies": {
4747
"bun-plugin-dtsx": "^0.9.5",
@@ -1875,7 +1875,7 @@
18751875

18761876
"ts-declaration-location": ["[email protected]", "", { "dependencies": { "picomatch": "^4.0.2" }, "peerDependencies": { "typescript": ">=4.0.0" } }, "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA=="],
18771877

1878-
"ts-pkgx": ["ts-pkgx@0.3.171", "", { "dependencies": { "chromium-bidi": "^7.2.0", "electron": "^37.2.4", "js-yaml": "^4.1.0", "playwright": "^1.54.1" }, "bin": { "pkgx-tools": "dist/bin/cli.js" } }, "sha512-kmoiU3BxiUzm+TQB2xTLVDpMqrPhtFnU+ggTqgx4DhI2H218/rh2PHErc+YusGhn/vkJB/mGIGI9c7WXFusNlg=="],
1878+
"ts-pkgx": ["ts-pkgx@0.4.4", "", { "dependencies": { "chromium-bidi": "^7.2.0", "electron": "^37.2.4", "js-yaml": "^4.1.0", "playwright": "^1.54.1" }, "bin": { "pkgx-tools": "dist/bin/cli.js" } }, "sha512-1ybeSxPjlB3VYp/2mmDx7aTlkH8RQsKs7CONXWyiJXmV+hDqPuCCI4bsdirnspW+iReZiThBa6wIaPM4+KlnQQ=="],
18791879

18801880
"tslib": ["[email protected]", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
18811881

packages/launchpad/bin/cli.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1183,7 +1183,7 @@ cli
11831183
dryrun: options?.dryRun || false,
11841184
quiet: options?.quiet || isShellIntegration, // Force quiet for shell integration
11851185
shellOutput: isShellIntegration,
1186-
skipGlobal: process.env.NODE_ENV === 'test' || process.env.LAUNCHPAD_SKIP_GLOBAL_AUTO_SCAN === 'true' || process.env.LAUNCHPAD_ENABLE_GLOBAL_AUTO_SCAN !== 'true', // Skip global packages by default unless explicitly enabled
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
11871187
})
11881188
}
11891189
catch (error) {
@@ -2665,3 +2665,65 @@ catch (error) {
26652665
console.error('CLI error:', error instanceof Error ? error.message : String(error))
26662666
process.exit(1)
26672667
}
2668+
2669+
// Database management commands
2670+
cli
2671+
.command('db:create', 'Create a database for the current project')
2672+
.option('--name <name>', 'Database name (defaults to project directory name)')
2673+
.option('--type <type>', 'Database type: postgres, mysql, sqlite', { default: 'auto' })
2674+
.option('--host <host>', 'Database host (for postgres/mysql)', { default: 'localhost' })
2675+
.option('--port <port>', 'Database port (postgres: 5432, mysql: 3306)')
2676+
.option('--user <user>', 'Database user')
2677+
.option('--password <password>', 'Database password')
2678+
.example('launchpad db:create # Create database with auto-detection')
2679+
.example('launchpad db:create --type postgres # Create PostgreSQL database')
2680+
.example('launchpad db:create --name myapp --type mysql # Create MySQL database named myapp')
2681+
.action(async (options: {
2682+
name?: string
2683+
type: string
2684+
host: string
2685+
port?: string
2686+
user?: string
2687+
password?: string
2688+
}) => {
2689+
try {
2690+
const { createProjectDatabase, generateLaravelConfig } = await import('../src/services/database')
2691+
const dbName = options.name || path.basename(process.cwd()).replace(/\W/g, '_')
2692+
2693+
const dbOptions = {
2694+
host: options.host,
2695+
port: options.port ? Number.parseInt(options.port, 10) : undefined,
2696+
user: options.user,
2697+
password: options.password,
2698+
type: options.type === 'auto' ? undefined : options.type as any,
2699+
}
2700+
2701+
const connectionInfo = await createProjectDatabase(dbName, dbOptions)
2702+
2703+
console.warn('\n📋 Database Connection Details:')
2704+
console.warn(` Type: ${connectionInfo.type}`)
2705+
if (connectionInfo.host)
2706+
console.warn(` Host: ${connectionInfo.host}`)
2707+
if (connectionInfo.port)
2708+
console.warn(` Port: ${connectionInfo.port}`)
2709+
console.warn(` Database: ${connectionInfo.database}`)
2710+
if (connectionInfo.username)
2711+
console.warn(` Username: ${connectionInfo.username}`)
2712+
if (connectionInfo.path)
2713+
console.warn(` Path: ${connectionInfo.path}`)
2714+
2715+
// Generate Laravel .env configuration
2716+
const envConfig = generateLaravelConfig(connectionInfo)
2717+
console.warn('\n🔧 Laravel .env configuration:')
2718+
console.warn(envConfig)
2719+
2720+
// Check if this is a Laravel project and offer to update .env
2721+
if (fs.existsSync('artisan') && fs.existsSync('.env')) {
2722+
console.warn('\n💡 Laravel project detected! You can update your .env file with the configuration above.')
2723+
}
2724+
}
2725+
catch (error) {
2726+
console.error(`Failed to create database: ${error instanceof Error ? error.message : String(error)}`)
2727+
process.exit(1)
2728+
}
2729+
})

packages/launchpad/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"dependencies": {
6666
"bunfig": "^0.10.1",
6767
"cac": "^6.7.14",
68-
"ts-pkgx": "0.3.171"
68+
"ts-pkgx": "0.4.4"
6969
},
7070
"devDependencies": {
7171
"bun-plugin-dtsx": "^0.9.5"

packages/launchpad/src/bun.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -391,9 +391,6 @@ export async function install_bun(installPath: string, version?: string): Promis
391391
if (config.verbose) {
392392
console.warn(`Downloading from: ${url}`)
393393
}
394-
else {
395-
console.log(`📦 Downloading Bun v${bunVersion}...`)
396-
}
397394

398395
zipPath = path.join(tempDir, filename)
399396

packages/launchpad/src/dev/dump.ts

Lines changed: 151 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,134 @@ export interface DumpOptions {
2323
skipGlobal?: boolean // Skip global package processing for testing
2424
}
2525

26+
/**
27+
* Check if packages are installed in the given environment directory
28+
*/
29+
function checkMissingPackages(packages: string[], envDir: string): string[] {
30+
if (packages.length === 0)
31+
return []
32+
33+
const pkgsDir = path.join(envDir, 'pkgs')
34+
if (!fs.existsSync(pkgsDir)) {
35+
return packages // All packages are missing if pkgs dir doesn't exist
36+
}
37+
38+
const missingPackages: string[] = []
39+
40+
for (const packageSpec of packages) {
41+
// Parse package spec (e.g., "php@^8.4.0" -> "php")
42+
const [packageName] = packageSpec.split('@')
43+
44+
const packageDir = path.join(pkgsDir, packageName)
45+
if (!fs.existsSync(packageDir)) {
46+
missingPackages.push(packageSpec)
47+
continue
48+
}
49+
50+
// Check if any version of this package is installed
51+
try {
52+
const versionDirs = fs.readdirSync(packageDir, { withFileTypes: true })
53+
.filter(entry => entry.isDirectory() && entry.name.startsWith('v'))
54+
55+
if (versionDirs.length === 0) {
56+
missingPackages.push(packageSpec)
57+
}
58+
}
59+
catch {
60+
missingPackages.push(packageSpec)
61+
}
62+
}
63+
64+
return missingPackages
65+
}
66+
67+
/**
68+
* Check if environment needs packages installed based on what's actually missing
69+
*/
70+
function needsPackageInstallation(localPackages: string[], globalPackages: string[], envDir: string, globalEnvDir: string): { needsLocal: boolean, needsGlobal: boolean, missingLocal: string[], missingGlobal: string[] } {
71+
const missingLocal = checkMissingPackages(localPackages, envDir)
72+
const missingGlobal = checkMissingPackages(globalPackages, globalEnvDir)
73+
74+
return {
75+
needsLocal: missingLocal.length > 0,
76+
needsGlobal: missingGlobal.length > 0,
77+
missingLocal,
78+
missingGlobal,
79+
}
80+
}
81+
82+
/**
83+
* Detect if this is a Laravel project and provide setup assistance
84+
*/
85+
function detectLaravelProject(dir: string): { isLaravel: boolean, suggestions: string[] } {
86+
const artisanFile = path.join(dir, 'artisan')
87+
const composerFile = path.join(dir, 'composer.json')
88+
const appDir = path.join(dir, 'app')
89+
90+
if (!fs.existsSync(artisanFile) || !fs.existsSync(composerFile) || !fs.existsSync(appDir)) {
91+
return { isLaravel: false, suggestions: [] }
92+
}
93+
94+
const suggestions: string[] = []
95+
96+
// Check for .env file
97+
const envFile = path.join(dir, '.env')
98+
if (!fs.existsSync(envFile)) {
99+
const envExample = path.join(dir, '.env.example')
100+
if (fs.existsSync(envExample)) {
101+
suggestions.push('Copy .env.example to .env and configure database settings')
102+
}
103+
}
104+
105+
// Check for database configuration
106+
if (fs.existsSync(envFile)) {
107+
try {
108+
const envContent = fs.readFileSync(envFile, 'utf8')
109+
if (envContent.includes('DB_CONNECTION=mysql') && !envContent.includes('DB_PASSWORD=')) {
110+
suggestions.push('Configure MySQL database credentials in .env file')
111+
}
112+
if (envContent.includes('DB_CONNECTION=pgsql') && !envContent.includes('DB_PASSWORD=')) {
113+
suggestions.push('Configure PostgreSQL database credentials in .env file')
114+
}
115+
if (envContent.includes('DB_CONNECTION=sqlite')) {
116+
const dbFile = envContent.match(/DB_DATABASE=(.+)/)?.[1]?.trim()
117+
if (dbFile && !fs.existsSync(dbFile)) {
118+
suggestions.push(`Create SQLite database file: touch ${dbFile}`)
119+
}
120+
}
121+
}
122+
catch {
123+
// Ignore errors reading .env file
124+
}
125+
}
126+
127+
// Check if migrations have been run
128+
try {
129+
const databaseDir = path.join(dir, 'database')
130+
const migrationsDir = path.join(databaseDir, 'migrations')
131+
if (fs.existsSync(migrationsDir)) {
132+
const migrations = fs.readdirSync(migrationsDir).filter(f => f.endsWith('.php'))
133+
if (migrations.length > 0) {
134+
suggestions.push('Run database migrations: php artisan migrate')
135+
136+
// Check for seeders
137+
const seedersDir = path.join(databaseDir, 'seeders')
138+
if (fs.existsSync(seedersDir)) {
139+
const seeders = fs.readdirSync(seedersDir).filter(f => f.endsWith('.php') && f !== 'DatabaseSeeder.php')
140+
if (seeders.length > 0) {
141+
suggestions.push('Seed database with test data: php artisan db:seed')
142+
}
143+
}
144+
}
145+
}
146+
}
147+
catch {
148+
// Ignore errors checking migrations
149+
}
150+
151+
return { isLaravel: true, suggestions }
152+
}
153+
26154
export async function dump(dir: string, options: DumpOptions = {}): Promise<void> {
27155
const { dryrun = false, quiet = false, shellOutput = false, skipGlobal = process.env.NODE_ENV === 'test' || process.env.LAUNCHPAD_SKIP_GLOBAL_AUTO_SCAN === 'true' || process.env.LAUNCHPAD_ENABLE_GLOBAL_AUTO_SCAN !== 'true' } = options
28156

@@ -261,10 +389,19 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
261389
const globalBinPath = path.join(globalEnvDir, 'bin')
262390
const globalSbinPath = path.join(globalEnvDir, 'sbin')
263391

264-
// If environments don't exist, trigger minimal installation
265-
if (!fs.existsSync(envBinPath) && (localPackages.length > 0 || globalPackages.length > 0)) {
266-
// Only install if we have packages and no environment
267-
await installPackagesOptimized(localPackages, globalPackages, envDir, globalEnvDir, dryrun, effectiveQuiet)
392+
// Check what packages are actually missing and need installation
393+
const packageStatus = needsPackageInstallation(localPackages, globalPackages, envDir, globalEnvDir)
394+
395+
// Install missing packages if any are found
396+
if (packageStatus.needsLocal || packageStatus.needsGlobal) {
397+
await installPackagesOptimized(
398+
packageStatus.missingLocal,
399+
packageStatus.missingGlobal,
400+
envDir,
401+
globalEnvDir,
402+
dryrun,
403+
effectiveQuiet,
404+
)
268405
}
269406

270407
outputShellCode(dir, envBinPath, envSbinPath, projectHash, sniffResult, globalBinPath, globalSbinPath)
@@ -277,6 +414,16 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
277414
await installPackagesOptimized(localPackages, globalPackages, envDir, globalEnvDir, dryrun, effectiveQuiet)
278415
}
279416

417+
// Check for Laravel project and provide helpful suggestions
418+
const laravelInfo = detectLaravelProject(projectDir)
419+
if (laravelInfo.isLaravel && laravelInfo.suggestions.length > 0 && !effectiveQuiet) {
420+
console.log('\n🎯 Laravel project detected! Helpful commands:')
421+
laravelInfo.suggestions.forEach((suggestion) => {
422+
console.log(` • ${suggestion}`)
423+
})
424+
console.log()
425+
}
426+
280427
// Output shell code if requested
281428
if (shellOutput) {
282429
const envBinPath = path.join(envDir, 'bin')

packages/launchpad/src/dev/shellcode.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,9 +527,9 @@ __launchpad_chpwd() {
527527
# Ensure global dependencies are available first
528528
__launchpad_setup_global_deps
529529
530-
# Ultra-fast setup: use shell mode with minimal timeout
530+
# Ultra-fast setup: use shell mode with reasonable timeout for package installation
531531
local temp_file=$(mktemp)
532-
if timeout 1 ${launchpadBinary} dev "$project_dir" --shell --quiet > "$temp_file" 2>&1; then
532+
if timeout 30 ${launchpadBinary} dev "$project_dir" --shell --quiet > "$temp_file" 2>&1; then
533533
env_output=$(cat "$temp_file" | ${grepFilter})
534534
setup_exit_code=0
535535
else

0 commit comments

Comments
 (0)