Skip to content

Commit 09d81ac

Browse files
committed
chore: wip
1 parent 05306d8 commit 09d81ac

9 files changed

+112
-39
lines changed

packages/launchpad/src/binary-downloader.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,9 @@ exec "${shimPath}" "$@"
921921
}
922922
}
923923
catch (e) {
924-
console.warn(`⚠️ install_name_tool failed for ${libName}: ${e instanceof Error ? e.message : String(e)}`)
924+
if (config.verbose) {
925+
console.warn(`⚠️ install_name_tool failed for ${libName}: ${e instanceof Error ? e.message : String(e)}`)
926+
}
925927
}
926928
}
927929
}

packages/launchpad/src/dev/dump.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,8 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
595595
if (message.includes('🔄') // Processing dependency
596596
|| message.includes('⬇️') // Download progress
597597
|| message.includes('🔧') // Extracting
598+
|| message.includes('🚀') // Service start messages
599+
|| message.includes('⏳') // Waiting messages
598600
|| message.includes('✅') // Success messages
599601
|| message.includes('⚠️') // Warnings
600602
|| message.includes('❌') // Errors
@@ -619,6 +621,8 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
619621
if (message.includes('🔄') // Processing dependency
620622
|| message.includes('⬇️') // Download progress
621623
|| message.includes('🔧') // Extracting
624+
|| message.includes('🚀') // Service start messages
625+
|| message.includes('⏳') // Waiting messages
622626
|| message.includes('✅') // Success messages
623627
|| message.includes('⚠️') // Warnings
624628
|| message.includes('❌') // Errors
@@ -693,10 +697,14 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
693697
sniffResult = { pkgs: [], env: {} }
694698
}
695699

696-
// In shell integration fast path, skip services and heavy post-setup
697-
// Only ensure php.ini exists if already marked ready
700+
// In shell integration fast path, ensure php.ini and start services when configured
701+
// Only ensure php.ini if already marked ready
698702
if (fs.existsSync(path.join(envDir, '.launchpad_ready'))) {
699703
await ensureProjectPhpIni(projectDir, envDir)
704+
try {
705+
await setupProjectServices(projectDir, sniffResult, true)
706+
}
707+
catch {}
700708
}
701709

702710
outputShellCode(
@@ -879,7 +887,11 @@ export async function dump(dir: string, options: DumpOptions = {}): Promise<void
879887
}
880888
await createPhpShimsAfterInstall(envDir)
881889

882-
// Do not auto-start services during shell integration
890+
// Start services during shell integration when configured
891+
try {
892+
await setupProjectServices(projectDir, sniffResult, true)
893+
}
894+
catch {}
883895

884896
// Ensure project php.ini exists only
885897
await ensureProjectPhpIni(projectDir, envDir)
@@ -1542,6 +1554,20 @@ async function setupProjectServices(projectDir: string, sniffResult: any, showMe
15421554
console.log('🔧 Creating project PostgreSQL database...')
15431555
const projectName = path.basename(projectDir).replace(/\W/g, '_')
15441556
try {
1557+
// Ensure DB utilities resolve from project environment first
1558+
const projectHash = generateProjectHash(projectDir)
1559+
const envDir = path.join(process.env.HOME || '', '.local', 'share', 'launchpad', 'envs', projectHash)
1560+
const globalEnvDir = path.join(process.env.HOME || '', '.local', 'share', 'launchpad', 'global')
1561+
const envBinPath = path.join(envDir, 'bin')
1562+
const envSbinPath = path.join(envDir, 'sbin')
1563+
const globalBinPath = path.join(globalEnvDir, 'bin')
1564+
const globalSbinPath = path.join(globalEnvDir, 'sbin')
1565+
const originalPath = process.env.PATH || ''
1566+
const augmentedPath = [envBinPath, envSbinPath, globalBinPath, globalSbinPath, originalPath]
1567+
.filter(Boolean)
1568+
.join(':')
1569+
process.env.PATH = augmentedPath
1570+
15451571
await createProjectDatabase(projectName, {
15461572
type: 'postgres',
15471573
host: '127.0.0.1',

packages/launchpad/src/dev/shellcode.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -723,8 +723,10 @@ __launchpad_chpwd() {
723723
# Run environment setup without timeout - let it take as long as needed
724724
local temp_file=$(mktemp)
725725
726-
# Always run shell-mode setup quietly for speed; capture only the shell code
727-
${launchpadBinary} dev "$project_dir" --shell --quiet > "$temp_file" 2>/dev/null
726+
# Immediate, persistent status line
727+
printf "⏳ Preparing environment for \\033[3m$(basename \"$project_dir\")\\033[0m...\n" >&2
728+
# Run quietly but keep stderr for incremental progress messages
729+
${launchpadBinary} dev "$project_dir" --shell --quiet > "$temp_file"
728730
setup_exit_code=$?
729731
730732
# Extract shell code from output
@@ -777,14 +779,10 @@ __launchpad_chpwd() {
777779
__launchpad_ensure_system_path
778780
hash -r 2>/dev/null || true
779781
780-
if [[ "\${LAUNCHPAD_SHOW_ENV_MESSAGES:-${showMessages}}" != "false" ]]; then
781-
printf "\\r\\033[K${activationMessage}\\n" >&2
782-
fi
782+
printf "${activationMessage}\n" >&2
783783
else
784784
# Clear any progress message on failure
785-
if [[ "\${LAUNCHPAD_SHOW_ENV_MESSAGES:-${showMessages}}" != "false" ]]; then
786-
printf "\\r\\033[K" >&2
787-
fi
785+
:
788786
fi
789787
fi
790788
fi

packages/launchpad/src/services/database.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,49 @@ export async function createProjectDatabase(dbName: string, options: DatabaseOpt
5050
* Create PostgreSQL database
5151
*/
5252
async function createPostgreSQLDatabase(dbName: string, options: DatabaseOptions): Promise<DatabaseConnectionInfo> {
53-
const { host = 'localhost', port = 5432, user = process.env.USER || 'postgres' } = options
53+
const { host = '127.0.0.1', port = 5432, user = 'postgres' } = options
5454

5555
try {
56+
// Ensure server is accepting TCP connections before attempting to create DB
57+
const pgIsReady = findBinaryInPath('pg_isready') || 'pg_isready'
58+
let ready = false
59+
for (let i = 0; i < 12; i++) {
60+
try {
61+
await executeCommand([pgIsReady, '-h', host, '-p', String(port)])
62+
ready = true
63+
break
64+
}
65+
catch {
66+
await new Promise(r => setTimeout(r, 500 + i * 250))
67+
}
68+
}
69+
if (!ready) {
70+
throw new Error('PostgreSQL not accepting connections yet')
71+
}
72+
5673
// Check if database exists
5774
const checkCommand = ['psql', '-h', host, '-p', String(port), '-U', user, '-lqt']
5875
const existing = await executeCommand(checkCommand)
5976

6077
if (!existing.includes(dbName)) {
6178
// Create the database
6279
const createCommand = ['createdb', '-h', host, '-p', String(port), '-U', user, dbName]
63-
await executeCommand(createCommand)
80+
let created = false
81+
let lastErr: unknown
82+
for (let i = 0; i < 5; i++) {
83+
try {
84+
await executeCommand(createCommand)
85+
created = true
86+
break
87+
}
88+
catch (e) {
89+
lastErr = e
90+
await new Promise(r => setTimeout(r, 500 + i * 500))
91+
}
92+
}
93+
if (!created) {
94+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr))
95+
}
6496
console.log(`✅ PostgreSQL database ${dbName} created successfully`)
6597
}
6698
else {
@@ -175,7 +207,7 @@ async function detectPreferredDatabaseType(): Promise<'postgres' | 'mysql' | 'sq
175207
async function isServiceRunning(service: string, port: number): Promise<boolean> {
176208
try {
177209
if (service === 'postgres') {
178-
await executeCommand(['pg_isready', '-p', String(port)])
210+
await executeCommand(['pg_isready', '-h', '127.0.0.1', '-p', String(port)])
179211
return true
180212
}
181213
else if (service === 'mysql') {

packages/launchpad/src/services/definitions.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const SERVICE_DEFINITIONS: Record<string, ServiceDefinition> = {
3131
'unicode.org^73',
3232
],
3333
healthCheck: {
34-
command: ['pg_isready', '-p', '5432'],
34+
command: ['pg_isready', '-h', '127.0.0.1', '-p', '5432'],
3535
expectedExitCode: 0,
3636
timeout: 5,
3737
interval: 30,
@@ -43,12 +43,10 @@ export const SERVICE_DEFINITIONS: Record<string, ServiceDefinition> = {
4343
['createdb', '-h', '127.0.0.1', '-p', '5432', '{projectDatabase}'],
4444
// Ensure default postgres role exists for framework defaults
4545
['psql', '-h', '127.0.0.1', '-p', '5432', '-d', 'postgres', '-c', 'DO $$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = \'postgres\') THEN CREATE ROLE postgres SUPERUSER LOGIN; END IF; END $$;'],
46-
// Create project-specific user (tests expect CREATE USER IF NOT EXISTS syntax)
47-
['psql', '-h', '127.0.0.1', '-p', '5432', '-d', 'postgres', '-c', 'CREATE USER IF NOT EXISTS {dbUsername} WITH PASSWORD \'{dbPassword}\';'],
46+
// Create project-specific user idempotently
47+
['psql', '-h', '127.0.0.1', '-p', '5432', '-d', 'postgres', '-c', 'DO $$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = \'{dbUsername}\') THEN CREATE ROLE {dbUsername} LOGIN PASSWORD \'{dbPassword}\'; ELSE ALTER ROLE {dbUsername} WITH PASSWORD \'{dbPassword}\'; END IF; END $$;'],
4848
// Grant permissions and set ownership
4949
['psql', '-h', '127.0.0.1', '-p', '5432', '-d', 'postgres', '-c', 'ALTER DATABASE {projectDatabase} OWNER TO {dbUsername}; GRANT ALL PRIVILEGES ON DATABASE {projectDatabase} TO {dbUsername};'],
50-
// Explicit grant for tests expecting root user grant example
51-
['psql', '-h', '127.0.0.1', '-p', '5432', '-d', 'postgres', '-c', 'GRANT ALL PRIVILEGES ON DATABASE {projectDatabase} TO root;'],
5250
['psql', '-h', '127.0.0.1', '-p', '5432', '-d', 'postgres', '-c', 'GRANT CREATE ON SCHEMA public TO {dbUsername};'],
5351
['psql', '-h', '127.0.0.1', '-p', '5432', '-d', 'postgres', '-c', 'GRANT USAGE ON SCHEMA public TO {dbUsername};'],
5452
],

packages/launchpad/test/database-service-integration.test.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -421,13 +421,20 @@ describe('Database Service Integration', () => {
421421
// Check PostgreSQL commands use template variables consistently
422422
const pgCommands = postgres!.postStartCommands!
423423
const pgCreateDbCmd = pgCommands.find(cmd => cmd[0] === 'createdb')
424-
const pgUserCmd = pgCommands.find(cmd =>
425-
cmd[0] === 'psql' && cmd.join(' ').includes('CREATE USER'),
426-
)
424+
const pgUserCmd = pgCommands.find((cmd) => {
425+
if (cmd[0] !== 'psql')
426+
return false
427+
const s = cmd.join(' ')
428+
// Prefer commands that target the app user
429+
if (!s.includes('{dbUsername}'))
430+
return false
431+
return s.includes('CREATE USER') || (s.includes('DO $$') && s.includes('CREATE ROLE'))
432+
})
427433

428434
expect(pgCreateDbCmd).toContain('{projectDatabase}')
429-
expect(pgUserCmd!.join(' ')).toContain('{dbUsername}')
430-
expect(pgUserCmd!.join(' ')).toContain('{dbPassword}')
435+
const pgUserJoined = pgUserCmd!.join(' ')
436+
expect(pgUserJoined).toContain('{dbUsername}')
437+
expect(pgUserJoined).toContain('{dbPassword}')
431438

432439
// Check MySQL commands use template variables consistently
433440
const mysqlCommands = mysql!.postStartCommands!

packages/launchpad/test/postgres-full-setup-integration.test.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,12 @@ DB_PASSWORD=launchpad123
127127
expect(postgresService.initCommand).toContain('--auth-host={authMethod}')
128128

129129
// Verify post-start commands create database user
130-
const createUserCommand = postgresService.postStartCommands?.find(cmd =>
131-
Array.isArray(cmd) && cmd.join(' ').includes('CREATE USER') && cmd.join(' ').includes('{dbUsername}'),
132-
)
130+
const createUserCommand = postgresService.postStartCommands?.find((cmd) => {
131+
if (!Array.isArray(cmd))
132+
return false
133+
const s = cmd.join(' ')
134+
return s.includes('CREATE USER') || (s.includes('DO $$') && s.includes('CREATE ROLE'))
135+
})
133136
expect(createUserCommand).toBeDefined()
134137
})
135138

@@ -142,7 +145,11 @@ DB_PASSWORD=launchpad123
142145

143146
// Verify health check configuration
144147
expect(postgresService.healthCheck).toBeDefined()
145-
expect(postgresService.healthCheck?.command).toEqual(['pg_isready', '-p', '5432'])
148+
const hc = postgresService.healthCheck?.command as string[]
149+
expect(Array.isArray(hc)).toBe(true)
150+
expect(hc[0]).toBe('pg_isready')
151+
expect(hc).toContain('-p')
152+
expect(hc).toContain('5432')
146153
expect(postgresService.healthCheck?.expectedExitCode).toBe(0)
147154

148155
// Verify graceful shutdown support
@@ -188,17 +195,18 @@ dependencies:
188195
const createDbCmd = resolvedCommands?.find(cmd => cmd.includes('createdb'))
189196
expect(createDbCmd).toContain('the_one_otc_api')
190197

191-
// Verify user creation
192-
const createUserCmd = resolvedCommands?.find(cmd =>
193-
cmd.join(' ').includes('CREATE USER') && cmd.join(' ').includes('root'),
194-
)
198+
// Verify user creation (allow idempotent role block)
199+
const createUserCmd = resolvedCommands?.find((cmd) => {
200+
const s = cmd.join(' ')
201+
return s.includes('CREATE USER') || (s.includes('DO $$') && s.includes('CREATE ROLE'))
202+
})
195203
expect(createUserCmd).toBeDefined()
196204

197205
// Verify permissions
198-
const grantCmd = resolvedCommands?.find(cmd =>
199-
cmd.join(' ').includes('GRANT ALL PRIVILEGES ON DATABASE the_one_otc_api TO root')
200-
|| cmd.join(' ').includes('GRANT ALL PRIVILEGES ON DATABASE the_one_otc_api TO'),
201-
)
206+
const grantCmd = resolvedCommands?.find((cmd) => {
207+
const s = cmd.join(' ')
208+
return s.includes('GRANT ALL PRIVILEGES ON DATABASE') && s.includes('the_one_otc_api')
209+
})
202210
expect(grantCmd).toBeDefined()
203211
})
204212

packages/launchpad/test/service-manager-binary-resolution-simple.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ describe('service Manager Binary Resolution (Simplified)', () => {
8080
Array.isArray(cmd) && cmd.some(arg => arg.includes('createdb')),
8181
)
8282
const hasCreateUser = postStartCommands!.some(cmd =>
83-
Array.isArray(cmd) && cmd.some(arg => arg.includes('CREATE USER')),
83+
Array.isArray(cmd)
84+
&& cmd.some(arg => arg.includes('CREATE USER') || (arg.includes('DO $$') && arg.includes('CREATE ROLE'))),
8485
)
8586
const hasGrantPrivileges = postStartCommands!.some(cmd =>
8687
Array.isArray(cmd) && cmd.some(arg => arg.includes('GRANT')),

packages/launchpad/test/service-manager-binary-resolution.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ describe('service Manager Binary Resolution', () => {
178178
Array.isArray(cmd) && cmd.some(arg => arg.includes('createdb')),
179179
)
180180
const hasCreateUser = postStartCommands!.some(cmd =>
181-
Array.isArray(cmd) && cmd.some(arg => arg.includes('CREATE USER')),
181+
Array.isArray(cmd)
182+
&& cmd.some(arg => arg.includes('CREATE USER') || (arg.includes('DO $$') && arg.includes('CREATE ROLE'))),
182183
)
183184
const hasGrantPrivileges = postStartCommands!.some(cmd =>
184185
Array.isArray(cmd) && cmd.some(arg => arg.includes('GRANT')),

0 commit comments

Comments
 (0)