Skip to content

[Feature] Render error page when theme dev encounters asset upload error #5221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 7, 2025
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/bright-impalas-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/theme': minor
---

Render error overlay when `theme dev` encounters asset upload errors. A 500 status code is returned when the error overlay is rendered.
9 changes: 9 additions & 0 deletions docs-shopify.dev/commands/interfaces/theme-dev.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ export interface themedev {
*/
'-e, --environment <value>'?: string

/**
* Controls the visibility of the error overlay when an theme asset upload fails:
- silent Prevents the error overlay from appearing.
- default Displays the error overlay.

* @environment SHOPIFY_FLAG_ERROR_OVERLAY
*/
'--error-overlay <value>'?: string

/**
* Set which network interface the web server listens on. The default value is 127.0.0.1.
* @environment SHOPIFY_FLAG_HOST
Expand Down
11 changes: 10 additions & 1 deletion docs-shopify.dev/generated/generated_docs_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -4960,6 +4960,15 @@
"name": "themedev",
"description": "",
"members": [
{
"filePath": "docs-shopify.dev/commands/interfaces/theme-dev.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--error-overlay <value>",
"value": "string",
"description": "Controls the visibility of the error overlay when an theme asset upload fails: - silent Prevents the error overlay from appearing. - default Displays the error overlay.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_ERROR_OVERLAY"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/theme-dev.interface.ts",
"syntaxKind": "PropertySignature",
Expand Down Expand Up @@ -5114,7 +5123,7 @@
"environmentValue": "SHOPIFY_FLAG_IGNORE"
}
],
"value": "export interface themedev {\n /**\n * The environment to apply to the current command.\n * @environment SHOPIFY_FLAG_ENVIRONMENT\n */\n '-e, --environment <value>'?: string\n\n /**\n * Set which network interface the web server listens on. The default value is 127.0.0.1.\n * @environment SHOPIFY_FLAG_HOST\n */\n '--host <value>'?: string\n\n /**\n * Skip hot reloading any files that match the specified pattern.\n * @environment SHOPIFY_FLAG_IGNORE\n */\n '-x, --ignore <value>'?: string\n\n /**\n * The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload\n * @environment SHOPIFY_FLAG_LIVE_RELOAD\n */\n '--live-reload <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Prevents files from being deleted in the remote theme when a file has been deleted locally. This applies to files that are deleted while the command is running, and files that have been deleted locally before the command is run.\n * @environment SHOPIFY_FLAG_NODELETE\n */\n '-n, --nodelete'?: ''\n\n /**\n * The file path or URL. The file path is to a file that you want updated on idle. The URL path is where you want a webhook posted to report on file changes.\n * @environment SHOPIFY_FLAG_NOTIFY\n */\n '--notify <value>'?: string\n\n /**\n * Hot reload only files that match the specified pattern.\n * @environment SHOPIFY_FLAG_ONLY\n */\n '-o, --only <value>'?: string\n\n /**\n * Automatically launch the theme preview in your default web browser.\n * @environment SHOPIFY_FLAG_OPEN\n */\n '--open'?: ''\n\n /**\n * Password generated from the Theme Access app.\n * @environment SHOPIFY_CLI_THEME_TOKEN\n */\n '--password <value>'?: string\n\n /**\n * The path to your theme directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * Local port to serve theme preview from.\n * @environment SHOPIFY_FLAG_PORT\n */\n '--port <value>'?: string\n\n /**\n * Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com, https://example.myshopify.com).\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>'?: string\n\n /**\n * The password for storefronts with password protection.\n * @environment SHOPIFY_FLAG_STORE_PASSWORD\n */\n '--store-password <value>'?: string\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme <value>'?: string\n\n /**\n * Synchronize Theme Editor updates in the local theme files.\n * @environment SHOPIFY_FLAG_THEME_EDITOR_SYNC\n */\n '--theme-editor-sync'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
"value": "export interface themedev {\n /**\n * The environment to apply to the current command.\n * @environment SHOPIFY_FLAG_ENVIRONMENT\n */\n '-e, --environment <value>'?: string\n\n /**\n * Controls the visibility of the error overlay when an theme asset upload fails:\n- silent Prevents the error overlay from appearing.\n- default Displays the error overlay.\n \n * @environment SHOPIFY_FLAG_ERROR_OVERLAY\n */\n '--error-overlay <value>'?: string\n\n /**\n * Set which network interface the web server listens on. The default value is 127.0.0.1.\n * @environment SHOPIFY_FLAG_HOST\n */\n '--host <value>'?: string\n\n /**\n * Skip hot reloading any files that match the specified pattern.\n * @environment SHOPIFY_FLAG_IGNORE\n */\n '-x, --ignore <value>'?: string\n\n /**\n * The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload\n * @environment SHOPIFY_FLAG_LIVE_RELOAD\n */\n '--live-reload <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Prevents files from being deleted in the remote theme when a file has been deleted locally. This applies to files that are deleted while the command is running, and files that have been deleted locally before the command is run.\n * @environment SHOPIFY_FLAG_NODELETE\n */\n '-n, --nodelete'?: ''\n\n /**\n * The file path or URL. The file path is to a file that you want updated on idle. The URL path is where you want a webhook posted to report on file changes.\n * @environment SHOPIFY_FLAG_NOTIFY\n */\n '--notify <value>'?: string\n\n /**\n * Hot reload only files that match the specified pattern.\n * @environment SHOPIFY_FLAG_ONLY\n */\n '-o, --only <value>'?: string\n\n /**\n * Automatically launch the theme preview in your default web browser.\n * @environment SHOPIFY_FLAG_OPEN\n */\n '--open'?: ''\n\n /**\n * Password generated from the Theme Access app.\n * @environment SHOPIFY_CLI_THEME_TOKEN\n */\n '--password <value>'?: string\n\n /**\n * The path to your theme directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * Local port to serve theme preview from.\n * @environment SHOPIFY_FLAG_PORT\n */\n '--port <value>'?: string\n\n /**\n * Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com, https://example.myshopify.com).\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>'?: string\n\n /**\n * The password for storefronts with password protection.\n * @environment SHOPIFY_FLAG_STORE_PASSWORD\n */\n '--store-password <value>'?: string\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme <value>'?: string\n\n /**\n * Synchronize Theme Editor updates in the local theme files.\n * @environment SHOPIFY_FLAG_THEME_EDITOR_SYNC\n */\n '--theme-editor-sync'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
}
}
}
Expand Down
14 changes: 11 additions & 3 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1796,9 +1796,10 @@ Uploads the current theme as a development theme to the connected store, then pr

```
USAGE
$ shopify theme dev [-e <value>] [--host <value>] [-x <value>] [--live-reload hot-reload|full-page|off]
[--no-color] [-n] [--notify <value>] [-o <value>] [--open] [--password <value>] [--path <value>] [--port <value>]
[-s <value>] [--store-password <value>] [-t <value>] [--theme-editor-sync] [--verbose]
$ shopify theme dev [-e <value>] [--error-overlay silent|default] [--host <value>] [-x <value>]
[--live-reload hot-reload|full-page|off] [--no-color] [-n] [--notify <value>] [-o <value>] [--open] [--password
<value>] [--path <value>] [--port <value>] [-s <value>] [--store-password <value>] [-t <value>]
[--theme-editor-sync] [--verbose]

FLAGS
-e, --environment=<value>
Expand All @@ -1821,6 +1822,13 @@ FLAGS
-x, --ignore=<value>...
Skip hot reloading any files that match the specified pattern.

--error-overlay=<option>
[default: default] Controls the visibility of the error overlay when an theme asset upload fails:
- silent Prevents the error overlay from appearing.
- default Displays the error overlay.

<options: silent|default>

--host=<value>
Set which network interface the web server listens on. The default value is 127.0.0.1.

Expand Down
26 changes: 26 additions & 0 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5123,6 +5123,19 @@
"name": "environment",
"type": "option"
},
"error-overlay": {
"default": "default",
"description": "Controls the visibility of the error overlay when an theme asset upload fails:\n- silent Prevents the error overlay from appearing.\n- default Displays the error overlay.\n ",
"env": "SHOPIFY_FLAG_ERROR_OVERLAY",
"hasDynamicHelp": false,
"multiple": false,
"name": "error-overlay",
"options": [
"silent",
"default"
],
"type": "option"
},
"force": {
"allowNo": false,
"char": "f",
Expand Down Expand Up @@ -6398,6 +6411,19 @@
"name": "environment",
"type": "option"
},
"error-overlay": {
"default": "default",
"description": "Controls the visibility of the error overlay when an theme asset upload fails:\n- silent Prevents the error overlay from appearing.\n- default Displays the error overlay.\n ",
"env": "SHOPIFY_FLAG_ERROR_OVERLAY",
"hasDynamicHelp": false,
"multiple": false,
"name": "error-overlay",
"options": [
"silent",
"default"
],
"type": "option"
},
"force": {
"allowNo": false,
"char": "f",
Expand Down
12 changes: 11 additions & 1 deletion packages/theme/src/cli/commands/theme/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {Flags} from '@oclif/core'
import {globalFlags} from '@shopify/cli-kit/node/cli'
import {Theme} from '@shopify/cli-kit/node/themes/types'
import {ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session'
import type {LiveReload} from '../../utilities/theme-environment/types.js'
import type {ErrorOverlayMode, LiveReload} from '../../utilities/theme-environment/types.js'

export default class Dev extends ThemeCommand {
static summary =
Expand Down Expand Up @@ -54,6 +54,15 @@ You can run this command only in a directory that matches the [default Shopify t
options: ['hot-reload', 'full-page', 'off'],
env: 'SHOPIFY_FLAG_LIVE_RELOAD',
}),
'error-overlay': Flags.string({
description: `Controls the visibility of the error overlay when an theme asset upload fails:
- silent Prevents the error overlay from appearing.
- default Displays the error overlay.
`,
options: ['silent', 'default'],
Comment on lines +57 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing values, should we pass allowNo: true so that devs can use the flag as --no-error-overlay to disable the behavior? Or do we expect to have more options in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, the idea is to leave room for expansion moving forward, though there's no clear timeline for that so perhaps something we could call into question

default: 'default',
env: 'SHOPIFY_FLAG_ERROR_OVERLAY',
}),
poll: Flags.boolean({
hidden: true,
description: 'Force polling to detect file changes.',
Expand Down Expand Up @@ -147,6 +156,7 @@ You can run this command only in a directory that matches the [default Shopify t
host: flags.host,
port: flags.port,
'live-reload': flags['live-reload'] as LiveReload,
'error-overlay': flags['error-overlay'] as ErrorOverlayMode,
force: flags.force,
open: flags.open,
'theme-editor-sync': flags['theme-editor-sync'],
Expand Down
2 changes: 2 additions & 0 deletions packages/theme/src/cli/services/dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('dev', () => {
noDelete: false,
ignore: [],
only: [],
'error-overlay': 'default',
}

const session: DevServerSession = {
Expand Down Expand Up @@ -101,6 +102,7 @@ describe('dev', () => {
ignore: [],
noDelete: false,
only: [],
errorOverlay: 'default',
},
})
})
Expand Down
4 changes: 3 additions & 1 deletion packages/theme/src/cli/services/dev.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {hasRequiredThemeDirectories, mountThemeFileSystem} from '../utilities/theme-fs.js'
import {ensureDirectoryConfirmed} from '../utilities/theme-ui.js'
import {setupDevServer} from '../utilities/theme-environment/theme-environment.js'
import {DevServerContext, LiveReload} from '../utilities/theme-environment/types.js'
import {DevServerContext, ErrorOverlayMode, LiveReload} from '../utilities/theme-environment/types.js'
import {isStorefrontPasswordProtected} from '../utilities/theme-environment/storefront-session.js'
import {ensureValidPassword} from '../utilities/theme-environment/storefront-password-prompt.js'
import {emptyThemeExtFileSystem} from '../utilities/theme-fs-empty.js'
Expand Down Expand Up @@ -31,6 +31,7 @@ export interface DevOptions {
force: boolean
'theme-editor-sync': boolean
'live-reload': LiveReload
'error-overlay': ErrorOverlayMode
noDelete: boolean
ignore: string[]
only: string[]
Expand Down Expand Up @@ -90,6 +91,7 @@ export async function dev(options: DevOptions) {
noDelete: options.noDelete,
ignore: options.ignore,
only: options.only,
errorOverlay: options['error-overlay'],
},
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
interface Error {
message: string
code: string
}

const POLARIS_STYLESHEET_URL = 'https://unpkg.com/@shopify/[email protected]/build/esm/styles.css'

function escapeHtml(unsafe: string) {
return unsafe
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}

export function getErrorPage(options: {title: string; header: string; errors: Error[]}) {
const html = String.raw
return html`<!DOCTYPE html>
<html>
<head>
<title>${options.title ?? 'Upload Errors'}</title>
<link rel="stylesheet" href="${POLARIS_STYLESHEET_URL}" />
</head>
<body>
<div style="display: flex; justify-content: center; padding-top: 2rem;">
<div style="width: 80%;">
<div class="Polaris-Banner Polaris-Banner--withinPage" tabindex="0" role="alert" aria-live="polite">
<div class="Polaris-Box" style="--pc-box-width:100%">
<div
class="Polaris-BlockStack"
style="--pc-block-stack-align:space-between;--pc-block-stack-order:column"
>
<div
class="Polaris-Box"
style="--pc-box-color: var(--p-color-text-critical-on-bg-fill); --pc-box-background: var(--p-color-bg-fill-critical); --pc-box-padding-block-start-xs: var(--p-space-300); --pc-box-padding-block-end-xs: var(--p-space-300); --pc-box-padding-inline-start-xs: var(--p-space-300); --pc-box-padding-inline-end-xs: var(--p-space-300); --pc-box-border-start-start-radius: var(--p-border-radius-300); --pc-box-border-start-end-radius: var(--p-border-radius-300);"
>
<div
class="Polaris-InlineStack"
style="--pc-inline-stack-align:space-between;--pc-inline-stack-block-align:center;--pc-inline-stack-wrap:nowrap;--pc-inline-stack-gap-xs:var(--p-space-200);--pc-inline-stack-flex-direction-xs:row"
>
<div
class="Polaris-InlineStack"
style="--pc-inline-stack-wrap:nowrap;--pc-inline-stack-gap-xs:var(--p-space-100);--pc-inline-stack-flex-direction-xs:row"
>
<span class="Polaris-Banner--textCriticalOnBgFill">
<span class="Polaris-Icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M10 6a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5a.75.75 0 0 1 .75-.75Z" />
<path d="M11 13a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path
fill-rule="evenodd"
d="M17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0Zm-1.5 0a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0Z"
/>
</svg>
</span>
</span>
<h2 class="Polaris-Text--root Polaris-Text--headingSm Polaris-Text--break">
${options.header}
</h2>
</div>
</div>
</div>
<div
class="Polaris-Box"
style="--pc-box-padding-block-start-xs:var(--p-space-300);--pc-box-padding-block-end-xs:var(--p-space-300);--pc-box-padding-block-end-md:var(--p-space-400);--pc-box-padding-inline-start-xs:var(--p-space-300);--pc-box-padding-inline-start-md:var(--p-space-400);--pc-box-padding-inline-end-xs:var(--p-space-300);--pc-box-padding-inline-end-md:var(--p-space-400)"
>
<div
class="Polaris-BlockStack"
style="--pc-block-stack-order:column;--pc-block-stack-gap-xs:var(--p-space-300)"
>
${options.errors
.map(
(error) => `
<div>
<span class="Polaris-Text--root Polaris-Text--headingSm">${escapeHtml(error.message)}</span>
<ul class="Polaris-List">
<li class="Polaris-List__Item">${escapeHtml(error.code)}</li>
</ul>
</div>`,
)
.join('')}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>`
}
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ function createTestContext(options?: {files?: [string, string][]}) {
liveReload: 'hot-reload',
open: false,
themeEditorSync: false,
errorOverlay: 'default',
},
}

Expand Down
Loading