Skip to content

Commit 7681be7

Browse files
committed
Render extension generation tasks to stderr to reduce output noise
Render the loading bar to stderr instead of stdout for extension generation tasks, following the pattern already used by the theme package's renderTasksToStdErr. This keeps the animated progress bar out of stdout, which prevents output flooding in non-TTY environments (CI, AI coding agents) where Ink's ANSI escape codes can't overwrite previous frames. stderr is typically still a TTY, so the animation remains visible to interactive users. Piped commands (e.g. | cat) are also unaffected since only stdout is piped. Reverts the previous approach of suppressing the animation at the LoadingBar component level, which couldn't distinguish between 'pipe to terminal' (animation works fine) and 'output captured as text' (animation floods).
1 parent e2ae2ca commit 7681be7

13 files changed

Lines changed: 64 additions & 91 deletions

File tree

packages/app/src/cli/services/dependencies.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Project} from '../models/project/project.js'
22
import {installNPMDependenciesRecursively} from '@shopify/cli-kit/node/node-package-manager'
3-
import {renderTasks} from '@shopify/cli-kit/node/ui'
3+
import {renderTasksToStdErr} from '../utilities/app-ui.js'
44

55
/**
66
* Given a project, it installs its NPM dependencies by traversing
@@ -21,5 +21,5 @@ export async function installAppDependencies(project: Project) {
2121
},
2222
},
2323
]
24-
await renderTasks(tasks)
24+
await renderTasksToStdErr(tasks)
2525
}

packages/app/src/cli/services/deploy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {Organization, OrganizationApp} from '../models/organization.js'
1212
import {reloadApp} from '../models/app/loader.js'
1313
import {ExtensionRegistration} from '../api/graphql/all_app_extension_registrations.js'
1414
import {getTomls} from '../utilities/app/config/getTomls.js'
15-
import {renderInfo, renderSuccess, renderTasks, renderConfirmationPrompt, isTTY} from '@shopify/cli-kit/node/ui'
15+
import {renderInfo, renderSuccess, renderConfirmationPrompt, isTTY} from '@shopify/cli-kit/node/ui'
16+
import {renderTasksToStdErr} from '../utilities/app-ui.js'
1617
import {mkdir} from '@shopify/cli-kit/node/fs'
1718
import {joinPath, dirname} from '@shopify/cli-kit/node/path'
1819
import {outputNewline, outputInfo, formatPackageManagerCommand} from '@shopify/cli-kit/node/output'
@@ -277,7 +278,7 @@ export async function deploy(options: DeployOptions) {
277278
},
278279
]
279280

280-
await renderTasks(tasks)
281+
await renderTasksToStdErr(tasks)
281282

282283
await outputCompletionMessage({
283284
app,

packages/app/src/cli/services/dev/processes/theme-app-extension.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {AdminSession, ensureAuthenticatedAdmin} from '@shopify/cli-kit/node/sess
88
import {fetchTheme} from '@shopify/cli-kit/node/themes/api'
99
import {AbortError} from '@shopify/cli-kit/node/error'
1010
import {Theme} from '@shopify/cli-kit/node/themes/types'
11-
import {renderInfo, renderTasks, Task} from '@shopify/cli-kit/node/ui'
11+
import {renderInfo, Task} from '@shopify/cli-kit/node/ui'
12+
import {renderTasksToStdErr} from '../../../utilities/app-ui.js'
1213
import {initializeDevelopmentExtensionServer, ensureValidPassword, isStorefrontPasswordProtected} from '@shopify/theme'
1314
import {partnersFqdn, adminFqdn} from '@shopify/cli-kit/node/context/fqdn'
1415

@@ -135,7 +136,7 @@ export async function findOrCreateHostTheme(adminSession: AdminSession, theme?:
135136
},
136137
},
137138
]
138-
await renderTasks(tasks)
139+
await renderTasksToStdErr(tasks)
139140
}
140141

141142
if (!hostTheme) {

packages/app/src/cli/services/dev/select-store.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
} from '../../api/graphql/convert_dev_to_transfer_disabled_store.js'
1111
import {ClientName, DeveloperPlatformClient, Paginateable} from '../../utilities/developer-platform-client.js'
1212
import {sleep} from '@shopify/cli-kit/node/system'
13-
import {renderInfo, renderTasks} from '@shopify/cli-kit/node/ui'
13+
import {renderInfo} from '@shopify/cli-kit/node/ui'
14+
import {renderTasksToStdErr} from '../../utilities/app-ui.js'
1415
import {firstPartyDev} from '@shopify/cli-kit/node/context/local'
1516
import {AbortError, BugError, CancelExecution} from '@shopify/cli-kit/node/error'
1617
import {outputSuccess} from '@shopify/cli-kit/node/output'
@@ -107,7 +108,7 @@ async function waitForCreatedStore(
107108
},
108109
},
109110
]
110-
await renderTasks(tasks)
111+
await renderTasksToStdErr(tasks)
111112

112113
return data
113114
}

packages/app/src/cli/services/function/build.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {dirname, joinPath} from '@shopify/cli-kit/node/path'
2020
import {build as esBuild, BuildResult} from 'esbuild'
2121
import {findPathUp, inTemporaryDirectory, readFile, readFileSync, writeFile} from '@shopify/cli-kit/node/fs'
2222
import {AbortSignal} from '@shopify/cli-kit/node/abort'
23-
import {renderTasks} from '@shopify/cli-kit/node/ui'
23+
import {renderTasksToStdErr} from '../../utilities/app-ui.js'
2424
import {pickBy} from '@shopify/cli-kit/common/object'
2525
import {runWithTimer} from '@shopify/cli-kit/node/metadata'
2626
import {AbortError} from '@shopify/cli-kit/node/error'
@@ -100,7 +100,7 @@ async function buildJSFunctionWithTasks(
100100
builder: JavyBuilder,
101101
deps: BinaryDependencies,
102102
) {
103-
await renderTasks([
103+
await renderTasksToStdErr([
104104
{
105105
title: 'Building GraphQL types',
106106
task: async () => {

packages/app/src/cli/services/generate/extension.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
readAndParsePackageJson,
1616
} from '@shopify/cli-kit/node/node-package-manager'
1717
import {recursiveLiquidTemplateCopy} from '@shopify/cli-kit/node/liquid'
18-
import {renderTasks} from '@shopify/cli-kit/node/ui'
18+
import {renderTasksToStdErr} from '../../utilities/app-ui.js'
1919
import {downloadGitRepository} from '@shopify/cli-kit/node/git'
2020
import {fileExists, inTemporaryDirectory, mkdir, moveFile, removeFile, glob} from '@shopify/cli-kit/node/fs'
2121
import {joinPath, relativizePath} from '@shopify/cli-kit/node/path'
@@ -214,7 +214,7 @@ async function functionExtensionInit({
214214
})
215215
}
216216

217-
await renderTasks(taskList)
217+
await renderTasksToStdErr(taskList)
218218
}
219219

220220
async function uiExtensionInit({
@@ -289,7 +289,7 @@ async function uiExtensionInit({
289289
},
290290
})
291291

292-
await renderTasks(tasks)
292+
await renderTasksToStdErr(tasks)
293293
}
294294

295295
type SrcFileExtension = 'ts' | 'tsx' | 'js' | 'jsx' | 'rs' | 'wasm' | 'liquid' | ''

packages/app/src/cli/services/generate/shop-import/declarative-definitions.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import {outputContent, outputInfo, outputToken} from '@shopify/cli-kit/node/outp
2525
import {TypedDocumentNode} from '@graphql-typed-document-node/core'
2626
import {Variables} from 'graphql-request'
2727
import {updateTomlValues} from '@shopify/toml-patch'
28-
import {renderInfo, renderSingleTask, renderTasks} from '@shopify/cli-kit/node/ui'
28+
import {renderInfo, renderSingleTask} from '@shopify/cli-kit/node/ui'
29+
import {renderTasksToStdErr} from '../../../utilities/app-ui.js'
2930
import {isEmpty} from '@shopify/cli-kit/common/object'
3031

3132
interface ImportDeclarativeDefinitionsOptions {
@@ -282,7 +283,7 @@ async function loadMetafieldNodes(adminSession: AdminSession): Promise<Metafield
282283
graphQLOwner: MetafieldOwnerType
283284
}[] = []
284285

285-
await renderTasks(
286+
await renderTasksToStdErr(
286287
Object.entries(DCDD_OWNER_TO_GRAPHQL_MAPPING).map(([dcddOwner, graphQLOwner]) => ({
287288
title: outputContent`Loading ${outputToken.green(dcddOwner)} metafields`,
288289
task: async () => {

packages/app/src/cli/services/init/init.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
UnknownPackageManagerError,
1414
writePackageJSON,
1515
} from '@shopify/cli-kit/node/node-package-manager'
16-
import {renderInfo, renderSuccess, renderTasks, Task} from '@shopify/cli-kit/node/ui'
16+
import {renderInfo, renderSuccess, Task} from '@shopify/cli-kit/node/ui'
17+
import {renderTasksToStdErr} from '../../utilities/app-ui.js'
1718
import {parseGitHubRepositoryReference} from '@shopify/cli-kit/node/github'
1819
import {hyphenate} from '@shopify/cli-kit/common/string'
1920
import {recursiveLiquidTemplateCopy} from '@shopify/cli-kit/node/liquid'
@@ -192,7 +193,7 @@ async function init(options: InitOptions) {
192193
},
193194
)
194195

195-
await renderTasks(tasks)
196+
await renderTasksToStdErr(tasks)
196197

197198
// Ensure the app directory is available before moving the template scaffold
198199
await ensureAppDirectoryIsAvailable(outputDirectory, hyphenizedName)

packages/app/src/cli/services/release.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {AppReleaseSchema} from '../api/graphql/app_release.js'
77
import {deployOrReleaseConfirmationPrompt} from '../prompts/deploy-release.js'
88
import {OrganizationApp} from '../models/organization.js'
99
import {DeveloperPlatformClient} from '../utilities/developer-platform-client.js'
10-
import {renderError, renderSuccess, renderTasks, TokenItem} from '@shopify/cli-kit/node/ui'
10+
import {renderError, renderSuccess, TokenItem} from '@shopify/cli-kit/node/ui'
11+
import {renderTasksToStdErr} from '../utilities/app-ui.js'
1112
import {AbortSilentError} from '@shopify/cli-kit/node/error'
1213

1314
interface ReleaseOptions {
@@ -82,7 +83,7 @@ export async function release(options: ReleaseOptions) {
8283

8384
const {
8485
appRelease: {appRelease: release},
85-
} = await renderTasks<Context>(tasks)
86+
} = await renderTasksToStdErr<Context>(tasks)
8687

8788
const linkAndMessage: TokenItem = [
8889
{link: {label: versionDetails.versionTag ?? undefined, url: versionDetails.location}},
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {Task, renderTasks} from '@shopify/cli-kit/node/ui'
2+
3+
/**
4+
* Renders tasks with the progress bar on stderr instead of stdout.
5+
* This prevents the animated loading bar from polluting stdout, which is
6+
* important for pipe operations and non-TTY environments (CI, AI agents).
7+
* stderr is typically still connected to the terminal, so the animation
8+
* remains visible to interactive users.
9+
*
10+
* When stderr is not a TTY (e.g. AI agents that redirect stderr via
11+
* `2>&1`), the LoadingBar component automatically shows static output
12+
* instead of the animated progress bar.
13+
*/
14+
export async function renderTasksToStdErr<TContext>(tasks: Task<TContext>[]): Promise<TContext> {
15+
return renderTasks(tasks, {renderOptions: {stdout: process.stderr as unknown as NodeJS.WriteStream}})
16+
}

0 commit comments

Comments
 (0)