Skip to content

Commit

Permalink
fix: use "spawnAsync" to stream publish script
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Sep 2, 2023
1 parent b11b9cf commit dd48334
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 25 deletions.
11 changes: 5 additions & 6 deletions src/commands/__test__/publish.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,8 @@ setTimeout(() => process.exit(0), 150)
await publish.run()

// Must log the release script stdout.
expect(log.info).toHaveBeenCalledWith(
'publishing script done, see the process output below:\n\n--- stdout ---\nhello\nworld\n\n',
)
expect(log.info).toHaveBeenCalledWith('hello\n')
expect(log.info).toHaveBeenCalledWith('world\n')

// Must report a successful release.
expect(log.info).toHaveBeenCalledWith('release type "minor": 0.0.0 -> 0.1.0')
Expand Down Expand Up @@ -517,11 +516,11 @@ setTimeout(() => process.exit(0), 150)
await publish.run()

// Must log the release script stdout.
expect(log.info).toHaveBeenCalledWith(
'publishing script done, see the process output below:\n\n--- stderr ---\nsomething\nwent wrong\n\n',
)
expect(log.error).toHaveBeenCalledWith('something\n')
expect(log.error).toHaveBeenCalledWith('went wrong\n')

// Must report a successful release.
// As long as the publish script doesn't exit, it is successful.
expect(log.info).toHaveBeenCalledWith('release type "minor": 0.0.0 -> 0.1.0')
expect(log.info).toHaveBeenCalledWith('release "v0.1.0" completed!')
})
Expand Down
39 changes: 20 additions & 19 deletions src/commands/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getLatestRelease } from '../utils/git/getLatestRelease'
import { bumpPackageJson } from '../utils/bumpPackageJson'
import { getTags } from '../utils/git/getTags'
import { execAsync } from '../utils/execAsync'
import { spawnAsync } from '../utils/spawnAsync'
import { commit } from '../utils/git/commit'
import { createTag } from '../utils/git/createTag'
import { push } from '../utils/git/push'
Expand All @@ -21,7 +22,7 @@ import { createComment } from '../utils/github/createComment'
import { createReleaseComment } from '../utils/createReleaseComment'
import { demandGitHubToken, demandNpmToken } from '../utils/env'
import { Notes } from './notes'
import { ReleaseProfile } from 'utils/getConfig'
import { ReleaseProfile } from '../utils/getConfig'

interface PublishArgv {
profile: string
Expand Down Expand Up @@ -260,31 +261,31 @@ export class Publish extends Command<PublishArgv> {
this.profile.use,
)

const publishResult = await until(async () => {
const releaseScriptStd = await execAsync(this.profile.use, {
const [releaseScriptProcess, releaseScriptPromise] = spawnAsync(
this.profile.use,
{
env: {
...process.env,
...env,
},
})

this.log.info(`publishing script done, see the process output below:
},
)

${[
['--- stdout ---', releaseScriptStd.stdout],
['--- stderr ---', releaseScriptStd.stderr],
]
.filter(([, data]) => !!data)
.map(([header, data]) => `${header}\n${data}`)
.join('\n\n')}
`)
// Forward the publish script's stdio to the logger.
releaseScriptProcess.stdout?.on('data', (chunk) => {
this.log.info(Buffer.from(chunk).toString('utf8'))
})
releaseScriptProcess.stderr?.on('data', (chunk) => {
this.log.error(Buffer.from(chunk).toString('utf8'))
})

invariant(
publishResult.error == null,
'Failed to publish: the publish script exited.\n%s',
publishResult.error?.message,
)
await releaseScriptPromise.catch((error) => {
this.log.error(error)
this.log.error(
'Failed to publish: the publih script errored. See the original error above.',
)
process.exit(releaseScriptProcess.exitCode || 1)
})

this.log.info('published successfully!')
}
Expand Down
1 change: 1 addition & 0 deletions src/utils/execAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type ExecAsyncFn = {
command: string,
options?: ExecOptions,
): DeferredPromise<ExecAsyncPromisePayload>

mockContext(options: ExecOptions): void
restoreContext(): void
contextOptions: ExecOptions
Expand Down
59 changes: 59 additions & 0 deletions src/utils/spawnAsync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { ChildProcess, SpawnOptions, spawn } from 'node:child_process'
import { format } from 'outvariant'
import { DeferredPromise } from '@open-draft/deferred-promise'

type SpawnAsyncFunction = {
(command: string, options?: SpawnOptions): [
ChildProcess,
DeferredPromise<void>,
]

contextOptions: SpawnOptions
mockContext(options: SpawnOptions): void
restoreContext(): void
}

const DEFAULT_SPAWN_CONTEXT: Partial<SpawnOptions> = {
cwd: process.cwd(),
}

/**
* Spawns the given `command` in a new process and gives that
* child process' reference as well as the command exit promise.
*/
export const spawnAsync = <SpawnAsyncFunction>((command, options) => {
const commandPromise = new DeferredPromise<void>()
const [commandName, ...args] = command.split(' ')
const io = spawn(commandName, args, {
...spawnAsync.contextOptions,
...options,
})

io.once('exit', (exitCode) => {
if (exitCode !== 0) {
return commandPromise.reject(
new Error(
format(
'Running "%s" failed: process exited with code %d',
command,
exitCode,
),
),
)
}

commandPromise.resolve()
})

return [io, commandPromise]
})

spawnAsync.mockContext = (options) => {
spawnAsync.contextOptions = options
}

spawnAsync.restoreContext = () => {
spawnAsync.contextOptions = DEFAULT_SPAWN_CONTEXT
}

spawnAsync.restoreContext()
9 changes: 9 additions & 0 deletions test/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createTeardown, TeardownApi } from 'fs-teardown'
import { log } from '../src/logger'
import { initGit, createGitProvider } from './utils'
import { execAsync } from '../src/utils/execAsync'
import { spawnAsync } from '../src/utils/spawnAsync'
import { requiredGitHubTokenScopes } from '../src/utils/github/validateAccessToken'

export const api = setupServer(
Expand Down Expand Up @@ -66,6 +67,9 @@ export function testEnvironment(
jest.spyOn(log, 'warn').mockImplementation()
jest.spyOn(log, 'error').mockImplementation()

spawnAsync.mockContext({
cwd: fs.resolve(),
})
execAsync.mockContext({
cwd: fs.resolve(),
})
Expand All @@ -90,6 +94,11 @@ export function testEnvironment(
await repoFs.prepare()
subscriptions.push(() => repoFs.cleanup())

spawnAsync.mockContext({
cwd: absoluteRootDir,
})
subscriptions.push(() => spawnAsync.restoreContext())

execAsync.mockContext({
cwd: absoluteRootDir,
})
Expand Down

0 comments on commit dd48334

Please sign in to comment.