Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions packages/supacloud-lite/scripts/package-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { access, mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import packageJson from '../package.json' with { type: 'json' }
import { withWindowsSubprocessRef } from './subprocess.js'

const packageDir = resolve(import.meta.dir, '..')
const packDir = await mkdtemp(join(tmpdir(), 'supacloud-lite-pack-'))
Expand Down Expand Up @@ -120,12 +121,14 @@ async function runCommandExpectingFailure(command: string[], cwd: string, env: N

async function executeCommand(command: string[], cwd: string, env: NodeJS.ProcessEnv): Promise<CommandExecution> {
const processHandle = Bun.spawn({ cmd: command, cwd, stdout: 'pipe', stderr: 'pipe', env })
const [exitCode, stdout, stderr] = await Promise.all([
processHandle.exited,
new Response(processHandle.stdout).text(),
new Response(processHandle.stderr).text(),
])
return { exitCode, stdout, stderr }
return await withWindowsSubprocessRef(async () => {
const [exitCode, stdout, stderr] = await Promise.all([
processHandle.exited,
new Response(processHandle.stdout).text(),
new Response(processHandle.stderr).text(),
])
return { exitCode, stdout, stderr }
})
}

interface CommandExecution {
Expand Down
5 changes: 3 additions & 2 deletions packages/supacloud-lite/scripts/standalone-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { access, mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/p
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import packageJson from '../package.json' with { type: 'json' }
import { withWindowsSubprocessRef } from './subprocess.js'

const packageDir = resolve(import.meta.dir, '..')
const binary = resolveStandaloneBinary()
Expand Down Expand Up @@ -225,7 +226,7 @@ async function withServer(options: ServerOptions, check: (url: string) => Promis
} finally {
console.log(`[standalone-smoke] ${options.phaseLabel}: stop`)
processHandle.kill('SIGTERM')
const exitCode = await processHandle.exited
const exitCode = await withWindowsSubprocessRef(() => processHandle.exited)
const expectedExitCode = expectedStandaloneShutdownExitCode()
console.log(
`[standalone-smoke] ${options.phaseLabel}: ${exitCode === expectedExitCode ? 'ok' : 'failed'} (exit ${exitCode})`,
Expand Down Expand Up @@ -276,7 +277,7 @@ async function runCommand(
const processHandle = Bun.spawn({ cmd: command, cwd, env, stdout: 'pipe', stderr: 'pipe' })
const stdoutPromise = new Response(processHandle.stdout).text()
const stderrPromise = new Response(processHandle.stderr).text()
const exitCode = await processHandle.exited
const exitCode = await withWindowsSubprocessRef(() => processHandle.exited)
console.log(`[standalone-smoke] ${commandLabel}: ${exitCode === 0 ? 'ok' : 'failed'} (exit ${exitCode})`)
const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise])
if (exitCode !== 0) throw new Error(`standalone command "${commandLabel}" failed (${exitCode})\n${stdout}\n${stderr}`)
Expand Down
13 changes: 13 additions & 0 deletions packages/supacloud-lite/scripts/subprocess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Bun 1.3.14 stops polling Windows IOCP when only a subprocess exit is pending.
* Remove this compatibility hold after the stable runtime includes oven-sh/bun#34478.
*/
export async function withWindowsSubprocessRef<T>(operation: () => Promise<T>): Promise<T> {
if (process.platform !== 'win32') return await operation()
const eventLoopRef = setInterval(() => {}, 1000)
try {
return await operation()
} finally {
clearInterval(eventLoopRef)
}
}
7 changes: 4 additions & 3 deletions packages/supacloud-lite/test/cli-shutdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { access, mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { waitForShutdown } from '../src/shutdown.js'
import { withWindowsSubprocessRef } from '../scripts/subprocess.js'

const cliPath = resolve(import.meta.dir, '../src/cli.ts')

Expand Down Expand Up @@ -109,11 +110,11 @@ async function runCli(projectDir: string, command: string[]) {
stdout: 'pipe',
stderr: 'pipe',
})
const [exitCode, stdout, stderr] = await Promise.all([
const [exitCode, stdout, stderr] = await withWindowsSubprocessRef(() => Promise.all([
processHandle.exited,
new Response(processHandle.stdout).text(),
new Response(processHandle.stderr).text(),
])
]))
return { exitCode, stdout, stderr, durationMs: performance.now() - startedAt }
}

Expand All @@ -123,7 +124,7 @@ async function stopCli(cliRun: ReturnType<typeof startCli>, signal: NodeJS.Signa
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ESRCH') throw error
}
return await cliRun.processHandle.exited
return await withWindowsSubprocessRef(() => cliRun.processHandle.exited)
}

async function assertShutdownHandlerClosesProject(signal: NodeJS.Signals): Promise<void> {
Expand Down
23 changes: 15 additions & 8 deletions packages/supacloud-lite/test/snapshot.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { afterEach, describe, expect, test } from 'bun:test'
import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'
import { access, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { ensureProjectSecrets, resolveProjectPaths } from '../src/project-runtime.js'
import { createSnapshot, restoreSnapshot } from '../src/snapshot.js'
import { withWindowsSubprocessRef } from '../scripts/subprocess.js'
import { createSymlinkIfPermitted } from './support/symlink.js'

const temporaryDirectories: string[] = []
Expand Down Expand Up @@ -155,6 +156,10 @@ describe('Lite snapshots', () => {

const status = await runCli(['status', '--project-dir', projectDir])
expect(status).toContain('20260728000000')

const lockPath = `${resolveProjectPaths({ projectDir }).dataDir!}.supacloud-lite.lock`
await expect(access(lockPath)).rejects.toMatchObject({ code: 'ENOENT' })
expect(await runCli(['status', '--project-dir', projectDir])).toContain('20260728000000')
})
})

Expand All @@ -172,11 +177,13 @@ async function runCli(args: string[]): Promise<string> {
stderr: 'pipe',
env: process.env,
})
const [exitCode, stdout, stderr] = await Promise.all([
processHandle.exited,
new Response(processHandle.stdout).text(),
new Response(processHandle.stderr).text(),
])
if (exitCode !== 0) throw new Error(`CLI failed (${exitCode}): ${stderr || stdout}`)
return stdout
return await withWindowsSubprocessRef(async () => {
const [exitCode, stdout, stderr] = await Promise.all([
processHandle.exited,
new Response(processHandle.stdout).text(),
new Response(processHandle.stderr).text(),
])
if (exitCode !== 0) throw new Error(`CLI failed (${exitCode}): ${stderr || stdout}`)
return stdout
})
}
Loading