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
5 changes: 5 additions & 0 deletions .changeset/update-ink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopify/cli-kit": patch
---

Update ink from 4.4.1 to 5.2.1
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
"octokit-plugin-create-pull-request": "^3.12.2",
"pathe": "1.1.1",
"pin-github-action": "^3.3.1",
"react": "17.0.2",
"rimraf": "^3.0.2",
"tmp": "^0.2.5",
"ts-node": "^10.9.1",
Expand All @@ -98,7 +97,7 @@
},
"version": "0.0.0",
"resolutions": {
"@types/react": "17.0.2",
"@types/react": "18.3.12",
"vite": "6.4.1",
"whatwg-url": "14.0.0",
"supports-hyperlinks": "3.1.0",
Expand Down Expand Up @@ -162,7 +161,6 @@
"@nx/workspace",
"graphql-tag",
"pin-github-action",
"react",
"esbuild"
],
"ignore": [
Expand Down
4 changes: 2 additions & 2 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@
"@types/diff": "^5.0.3",
"@types/express": "^4.17.17",
"@types/proper-lockfile": "4.1.4",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/which": "3.0.4",
"@types/ws": "^8.5.13",
"@vitest/coverage-istanbul": "^3.1.4"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ const Logs: FunctionComponent<LogsProps> = ({
</Box>
<Box flexDirection="column" marginLeft={4}>
{appLog instanceof FunctionRunLog && (
<>
<Box flexDirection="column">
<Text>{appLog.logs}</Text>
{appLog.inputQueryVariablesMetafieldKey && appLog.inputQueryVariablesMetafieldNamespace && (
{appLog.inputQueryVariablesMetafieldKey && appLog.inputQueryVariablesMetafieldNamespace ? (
<Box flexDirection="column" marginTop={1}>
<Text bold>Input Query Variables:</Text>
<Box flexDirection="row" marginLeft={1} marginTop={1}>
Expand All @@ -98,8 +98,8 @@ const Logs: FunctionComponent<LogsProps> = ({
</Text>
</Box>
</Box>
)}
{appLog.input && (
) : null}
{appLog.input ? (
<Box flexDirection="column" marginTop={1}>
<Text bold>
Input <Text dimColor>({appLog.inputBytes} bytes):</Text>
Expand All @@ -108,8 +108,8 @@ const Logs: FunctionComponent<LogsProps> = ({
<Text>{prettyPrintJsonIfPossible(appLog.input)}</Text>
</Box>
</Box>
)}
{appLog.output && (
) : null}
{appLog.output ? (
<Box flexDirection="column" marginTop={1}>
<Text bold>
Output <Text dimColor>({appLog.outputBytes} bytes):</Text>
Expand All @@ -118,41 +118,41 @@ const Logs: FunctionComponent<LogsProps> = ({
<Text>{prettyPrintJsonIfPossible(appLog.output)}</Text>
</Box>
</Box>
)}
</>
) : null}
</Box>
)}
{appLog instanceof NetworkAccessResponseFromCacheLog && (
<>
<Box flexDirection="column">
<Text>Cache write time: {new Date(appLog.cacheEntryEpochMs).toISOString()}</Text>
<Text>Cache TTL: {appLog.cacheTtlMs / 1000} s</Text>
<Text>HTTP request:</Text>
<Text>{prettyPrintJsonIfPossible(appLog.httpRequest)}</Text>
<Text>HTTP response:</Text>
<Text>{prettyPrintJsonIfPossible(appLog.httpResponse)}</Text>
</>
</Box>
)}
{appLog instanceof NetworkAccessRequestExecutionInBackgroundLog && (
<>
<Box flexDirection="column">
<Text>Reason: {getBackgroundExecutionReasonMessage(appLog.reason)}</Text>
<Text>HTTP request:</Text>
<Text>{prettyPrintJsonIfPossible(appLog.httpRequest)}</Text>
</>
</Box>
)}
{appLog instanceof NetworkAccessRequestExecutedLog && (
<>
<Box flexDirection="column">
<Text>Attempt: {appLog.attempt}</Text>
{appLog.connectTimeMs && <Text>Connect time: {appLog.connectTimeMs} ms</Text>}
{appLog.writeReadTimeMs && <Text>Write read time: {appLog.writeReadTimeMs} ms</Text>}
{appLog.connectTimeMs ? <Text>Connect time: {appLog.connectTimeMs} ms</Text> : null}
{appLog.writeReadTimeMs ? <Text>Write read time: {appLog.writeReadTimeMs} ms</Text> : null}
<Text>HTTP request:</Text>
<Text>{prettyPrintJsonIfPossible(appLog.httpRequest)}</Text>
{appLog.httpResponse && (
<>
{appLog.httpResponse ? (
<Box flexDirection="column">
<Text>HTTP response:</Text>
<Text>{prettyPrintJsonIfPossible(appLog.httpResponse)}</Text>
</>
)}
{appLog.error && <Text>Error: {appLog.error}</Text>}
</>
</Box>
) : null}
{appLog.error ? <Text>Error: {appLog.error}</Text> : null}
</Box>
)}
</Box>
</Box>
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/cli/services/app-logs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,15 +229,15 @@ export const subscribeToAppLogs = async (
return jwtToken
}

export function prettyPrintJsonIfPossible(json: unknown) {
export function prettyPrintJsonIfPossible(json: unknown): string | undefined {
try {
if (typeof json === 'string') {
const jsonObject = JSON.parse(json)
return JSON.stringify(jsonObject, null, 2)
} else if (typeof json === 'object' && json !== null) {
return JSON.stringify(json, null, 2)
} else {
return json
return undefined
}
} catch (error) {
throw new Error(`Error parsing JSON: ${error as string}`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ async function reload(app: AppLinkedInterface): Promise<AppLinkedInterface> {
return newApp
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
throw new Error(`Error reloading app: ${error.message}`, {cause: 'validation-error'})
const err = new Error(`Error reloading app: ${error.message}`) as Error & {cause: string}
err.cause = 'validation-error'
throw err
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ describe('pushUpdatesForDevSession', () => {
test('sets validation error status message when error cause is validation-error', async () => {
// Given
const validationError = new Error('Validation failed')
validationError.cause = 'validation-error'
;(validationError as Error & {cause: string}).cause = 'validation-error'

// When
await pushUpdatesForDevSession({stderr, stdout, abortSignal: abortController.signal}, options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export class DevSession {
await this.logger.debug('❌ Dev preview update aborted (new change detected or error during update)')
} else if (result.status === 'remote-error' || result.status === 'unknown-error') {
await this.logger.logUserErrors(result.error, event?.app.allExtensions ?? [])
if (result.error instanceof Error && result.error.cause === 'validation-error') {
if (result.error instanceof Error && (result.error as Error & {cause?: string}).cause === 'validation-error') {
this.statusManager.setMessage('VALIDATION_ERROR')
} else {
if (event) this.failedEvents.push(event)
Expand Down
10 changes: 8 additions & 2 deletions packages/app/src/cli/utilities/app/http-reverse-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import * as https from 'https'
import {Writable} from 'stream'
import type Server from 'http-proxy-node16'

function isAggregateError(err: Error): err is Error & {errors: Error[]} {
return 'errors' in err && Array.isArray((err as {errors?: unknown}).errors)
}

export interface LocalhostCert {
key: string
cert: string
Expand Down Expand Up @@ -47,7 +51,8 @@ function getProxyServerWebsocketUpgradeListener(
if (target) {
return proxy.ws(req, socket, head, {target}, (err) => {
useConcurrentOutputContext({outputPrefix: 'proxy', stripAnsi: false}, () => {
const error = err instanceof AggregateError && err.errors.length > 0 ? err.errors[err.errors.length - 1] : err
const lastError = isAggregateError(err) ? err.errors[err.errors.length - 1] : undefined
const error = lastError ?? err
outputWarn(`Error forwarding websocket request: ${error.message}`, stdout)
outputWarn(`└ Unreachable target "${target}" for path: "${req.url}"`, stdout)
})
Expand All @@ -67,7 +72,8 @@ function getProxyServerRequestListener(
if (target) {
return proxy.web(req, res, {target}, (err) => {
useConcurrentOutputContext({outputPrefix: 'proxy', stripAnsi: false}, () => {
const error = err instanceof AggregateError && err.errors.length > 0 ? err.errors[err.errors.length - 1] : err
const lastError = isAggregateError(err) ? err.errors[err.errors.length - 1] : undefined
const error = lastError ?? err
outputWarn(`Error forwarding web request: ${error.message}`, stdout)
outputWarn(`└ Unreachable target "${target}" for path: "${req.url}"`, stdout)
})
Expand Down
83 changes: 51 additions & 32 deletions packages/cli-kit/bin/documentation/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,45 +565,64 @@ export const examples: {[key in string]: Example} = {
renderTasks: {
type: 'async',
basic: async () => {
const stdout = new Stdout({columns: TERMINAL_WIDTH})
// Force colors for deterministic output (solid blocks vs animated wave)
const originalForceColor = process.env.FORCE_COLOR
process.env.FORCE_COLOR = '1'
try {
const stdout = new Stdout({columns: TERMINAL_WIDTH})

const tasks = [
{
title: 'Installing dependencies',
task: async () => {
await new Promise((resolve) => setTimeout(resolve, 2000))
const tasks = [
{
title: 'Installing dependencies',
task: async () => {
await new Promise((resolve) => setTimeout(resolve, 2000))
},
},
},
]

renderTasks(tasks, {renderOptions: {stdout: stdout as any, debug: true}})

await waitFor(
() => {},
() => Boolean(stdout.lastFrame()?.includes('Installing dependencies')),
)

return stdout.lastFrame()!
]

renderTasks(tasks, {renderOptions: {stdout: stdout as any, debug: true}})

await waitFor(
() => {},
() => Boolean(stdout.lastFrame()?.includes('Installing dependencies')),
)

return stdout.lastFrame()!
} finally {
if (originalForceColor === undefined) {
delete process.env.FORCE_COLOR
} else {
process.env.FORCE_COLOR = originalForceColor
}
}
},
},
renderSingleTask: {
type: 'async',
basic: async () => {
const stdout = new Stdout({columns: TERMINAL_WIDTH})

await renderSingleTask({
title: outputContent`Loading app`,
task: async () => {
await sleep(1)
},
renderOptions: {stdout: stdout as any, debug: true}
})

// Find the last frame that includes mention of "Loading"
const loadingFrame = stdout.frames.findLast(frame => frame.includes('Loading'))

// Gives a frame where the loading bar is visible
return loadingFrame ?? stdout.lastFrame()!
// Force colors for deterministic output (solid blocks vs animated wave)
const originalForceColor = process.env.FORCE_COLOR
process.env.FORCE_COLOR = '1'
try {
const stdout = new Stdout({columns: TERMINAL_WIDTH})

await renderSingleTask({
title: outputContent`Loading app`,
task: async () => {
await sleep(1)
},
renderOptions: {stdout: stdout as any, debug: true},
})

// Return first frame with the title for deterministic output
return stdout.frames.find((frame) => frame.includes('Loading'))!
} finally {
if (originalForceColor === undefined) {
delete process.env.FORCE_COLOR
} else {
process.env.FORCE_COLOR = originalForceColor
}
}
},
},
renderTextPrompt: {
Expand Down
14 changes: 7 additions & 7 deletions packages/cli-kit/bin/refresh-code-documentation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import {examples} from './documentation/examples.js'
import {unstyled} from '../src/public/node/output.js'
import {renderFatalError} from '../src/public/node/ui.js'
import {AbortError} from '../src/public/node/error.js'
import {FunctionDeclaration, JSDocTag, Project} from 'ts-morph'
import difference from 'lodash/difference.js'

Expand All @@ -17,11 +15,13 @@ async function refreshDocumentation(): Promise<void> {
validateMissingDocs(renderFunctions, validationErrors)

if (validationErrors.length > 0) {
renderFatalError(
new AbortError('Refreshing the documentation failed', {
list: {items: validationErrors.map((error) => error.message)},
}),
)
// eslint-disable-next-line no-console
console.error('Refreshing the documentation failed:')
validationErrors.forEach((error) => {
// eslint-disable-next-line no-console
console.error(` - ${error.message}`)
})
process.exit(1)
}

const exampleTags: {[key: string]: JSDocTag[]} = renderFunctions.reduce((acc, func) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"graphql": "16.10.0",
"graphql-request": "6.1.0",
"ignore": "6.0.2",
"ink": "4.4.1",
"ink": "5.2.1",
"is-executable": "2.0.1",
"is-interactive": "2.0.0",
"is-wsl": "3.1.0",
Expand Down Expand Up @@ -171,7 +171,7 @@
"@types/fs-extra": "9.0.13",
"@types/gradient-string": "^1.1.2",
"@types/lodash": "4.17.19",
"@types/react": "18.2.0",
"@types/react": "^18.2.0",
"@types/semver": "^7.5.2",
"@types/which": "3.0.4",
"@vitest/coverage-istanbul": "^3.1.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type BannerType = 'success' | 'error' | 'warning' | 'info' | 'external_er

interface BannerProps {
type: BannerType
children?: React.ReactNode
}

function typeToColor(type: BannerProps['type']) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ interface CompletedPromptProps {

const CompletedPrompt: FunctionComponent<CompletedPromptProps> = ({cancelled}) => (
<Box>
<Box marginRight={2}>
<Box width={3}>
{cancelled ? <Text color="red">{figures.cross}</Text> : <Text color="cyan">{figures.tick}</Text>}
</Box>

Expand Down
Loading
Loading