Skip to content
Draft
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
4 changes: 2 additions & 2 deletions bin/bundling/esbuild-plugin-graphiql-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const GraphiQLImportsPlugin = {
const contents = await readFile(args.path, 'utf8')
return {
contents: contents
.replace('@shopify/app/assets/graphiql/favicon.ico', './assets/graphiql/favicon.ico')
.replace('@shopify/app/assets/graphiql/style.css', './assets/graphiql/style.css'),
.replace('@shopify/cli-kit/assets/graphiql/favicon.ico', './assets/graphiql/favicon.ico')
.replace('@shopify/cli-kit/assets/graphiql/style.css', './assets/graphiql/style.css'),
}
})
},
Expand Down
4 changes: 0 additions & 4 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@
"@oclif/core": "4.5.3",
"@shopify/cli-kit": "3.93.0",
"@shopify/plugin-cloudflare": "3.93.0",
"@shopify/polaris": "12.27.0",
"@shopify/polaris-icons": "8.11.1",
"@shopify/theme": "3.93.0",
"@shopify/theme-check-node": "3.25.0",
"@shopify/toml-patch": "0.3.0",
Expand All @@ -68,15 +66,13 @@
"prettier": "3.8.1",
"proper-lockfile": "4.1.2",
"react": "19.2.4",
"react-dom": "19.2.4",
"which": "4.0.0",
"ws": "8.18.0"
},
"devDependencies": {
"@types/diff": "^5.0.3",
"@types/proper-lockfile": "4.1.4",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/which": "3.0.4",
"@types/ws": "^8.5.13",
"@vitest/coverage-istanbul": "^3.1.4"
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/cli/services/dev/processes/graphiql.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {BaseProcess, DevProcessFunction} from './types.js'
import {setupGraphiQLServer} from '../graphiql/server.js'
import {setupGraphiQLServer} from '@shopify/cli-kit/node/graphiql/server'

interface GraphiQLServerProcessOptions {
appName: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {pushUpdatesForDraftableExtensions} from './draftable-extension.js'
import {pushUpdatesForDevSession} from './dev-session/dev-session-process.js'
import {runThemeAppExtensionsServer} from './theme-app-extension.js'
import {launchAppWatcher} from './app-watcher-process.js'
import {resolveGraphiQLKey} from '../graphiql/server.js'
import {
testAppAccessConfigExtension,
testAppConfigExtensions,
Expand All @@ -31,6 +30,7 @@ import {ensureDeploymentIdsPresence} from '../../context/identifiers.js'
import {DeveloperPlatformClient} from '../../../utilities/developer-platform-client.js'
import {AppEventWatcher} from '../app-events/app-event-watcher.js'
import * as loader from '../../../models/app/loader.js'
import {resolveGraphiQLKey} from '@shopify/cli-kit/node/graphiql/server'
import {describe, test, expect, beforeEach, vi} from 'vitest'
import {ensureAuthenticatedAdmin, ensureAuthenticatedStorefront} from '@shopify/cli-kit/node/session'
import {Config} from '@oclif/core'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {DevSessionProcess, setupDevSessionProcess} from './dev-session/dev-sessi
import {AppLogsSubscribeProcess, setupAppLogsPollingProcess} from './app-logs-polling.js'
import {AppWatcherProcess, setupAppWatcherProcess} from './app-watcher-process.js'
import {DevSessionStatusManager} from './dev-session/dev-session-status-manager.js'
import {resolveGraphiQLKey} from '../graphiql/server.js'
import {environmentVariableNames} from '../../../constants.js'
import {AppLinkedInterface, getAppScopes, WebType} from '../../../models/app/app.js'

Expand All @@ -21,6 +20,7 @@ import {ApplicationURLs} from '../urls.js'
import {DeveloperPlatformClient} from '../../../utilities/developer-platform-client.js'
import {AppEventWatcher} from '../app-events/app-event-watcher.js'
import {reloadApp} from '../../../models/app/loader.js'
import {resolveGraphiQLKey} from '@shopify/cli-kit/node/graphiql/server'
import {getAvailableTCPPort} from '@shopify/cli-kit/node/tcp'
import {isTruthy} from '@shopify/cli-kit/node/context/utilities'
import {firstPartyDev} from '@shopify/cli-kit/node/context/local'
Expand Down
5 changes: 5 additions & 0 deletions packages/cli-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
"@graphql-typed-document-node/core": "3.2.0",
"@iarna/toml": "2.2.5",
"@oclif/core": "4.5.3",
"@shopify/polaris": "12.27.0",
"@shopify/polaris-icons": "8.11.1",
"@shopify/toml-patch": "0.3.0",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/core": "1.30.0",
Expand Down Expand Up @@ -134,6 +136,7 @@
"gradient-string": "2.0.2",
"graphql": "16.10.0",
"graphql-request": "6.1.0",
"h3": "1.15.9",
"ignore": "6.0.2",
"ink": "6.8.0",
"is-executable": "2.0.1",
Expand All @@ -151,6 +154,7 @@
"open": "8.4.2",
"pathe": "1.1.2",
"react": "19.2.4",
"react-dom": "19.2.4",
"semver": "7.6.3",
"stacktracey": "2.1.8",
"strip-ansi": "7.1.0",
Expand All @@ -165,6 +169,7 @@
"@types/gradient-string": "^1.1.2",
"@types/lodash": "4.17.19",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.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
@@ -1,6 +1,13 @@
import {defaultQuery, graphiqlTemplate} from './templates/graphiql.js'
import {unauthorizedTemplate} from './templates/unauthorized.js'
import {filterCustomHeaders} from './utilities.js'
import {performActionWithRetryAfterRecovery} from '../../common/retry.js'
import {CLI_KIT_VERSION} from '../../common/version.js'
import {AbortError} from '../error.js'
import {adminUrl, supportedApiVersions} from '../api/admin.js'
import {fetch} from '../http.js'
import {renderLiquidTemplate} from '../liquid.js'
import {outputDebug} from '../output.js'
import {
createApp,
createRouter,
Expand All @@ -13,13 +20,6 @@ import {
setResponseStatus,
toNodeListener,
} from 'h3'
import {performActionWithRetryAfterRecovery} from '@shopify/cli-kit/common/retry'
import {CLI_KIT_VERSION} from '@shopify/cli-kit/common/version'
import {AbortError} from '@shopify/cli-kit/node/error'
import {adminUrl, supportedApiVersions} from '@shopify/cli-kit/node/api/admin'
import {fetch} from '@shopify/cli-kit/node/http'
import {renderLiquidTemplate} from '@shopify/cli-kit/node/liquid'
import {outputDebug} from '@shopify/cli-kit/node/output'
import {createHmac} from 'crypto'
import {createServer, Server} from 'http'
import {readFileSync} from 'fs'
Expand All @@ -30,6 +30,10 @@ import {createRequire} from 'module'
* Derives a deterministic GraphiQL authentication key from the app's API secret and store FQDN.
* The key is stable across dev server restarts (so browser tabs survive restarts)
* but is not guessable without the app secret.
*
* @param apiSecret - The Partners app's client secret used as the HMAC key.
* @param storeFqdn - The myshopify.com domain the GraphiQL session targets.
* @returns A 64-character hex string suitable for use as the `?key=` query param.
*/
export function deriveGraphiQLKey(apiSecret: string, storeFqdn: string): string {
return createHmac('sha256', apiSecret).update(`graphiql:${storeFqdn}`).digest('hex')
Expand All @@ -38,6 +42,11 @@ export function deriveGraphiQLKey(apiSecret: string, storeFqdn: string): string
/**
* Resolves the GraphiQL authentication key. Uses the explicitly provided key
* if non-empty, otherwise derives one deterministically from the app secret.
*
* @param providedKey - An explicit key supplied by the caller; takes precedence when non-empty.
* @param apiSecret - The Partners app's client secret, used to derive a stable key as a fallback.
* @param storeFqdn - The myshopify.com domain the GraphiQL session targets.
* @returns The resolved key.
*/
export function resolveGraphiQLKey(providedKey: string | undefined, apiSecret: string, storeFqdn: string): string {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional: empty string after trim should fall through to deriveGraphiQLKey
Expand All @@ -63,16 +72,18 @@ interface SetupGraphiQLServerOptions {
storeFqdn: string
}

export function setupGraphiQLServer({
stdout,
port,
appName,
appUrl,
apiKey,
apiSecret,
key: providedKey,
storeFqdn,
}: SetupGraphiQLServerOptions): Server {
/**
* Starts a local HTTP server that hosts the GraphiQL UI and proxies requests to the
* Admin API for the configured store. The server uses the OAuth `client_credentials`
* grant with the supplied `apiKey` / `apiSecret` to mint and refresh access tokens
* on the fly.
*
* @param options - Configuration for the server, including the target store, the
* Partners app credentials, and the local port to bind to.
* @returns The underlying Node `http.Server` instance, already listening on `options.port`.
*/
export function setupGraphiQLServer(options: SetupGraphiQLServerOptions): Server {
const {stdout, port, appName, appUrl, apiKey, apiSecret, key: providedKey, storeFqdn} = options
// Always require an authentication key. If not explicitly provided, derive one
// deterministically from apiSecret + storeFqdn so the key is stable across restarts
// (browser tabs survive dev server restarts) but not guessable without the app secret.
Expand Down Expand Up @@ -120,9 +131,9 @@ export function setupGraphiQLServer({
)
}

const faviconPath = require.resolve('@shopify/app/assets/graphiql/favicon.ico')
const faviconPath = require.resolve('@shopify/cli-kit/assets/graphiql/favicon.ico')
const faviconContent = readFileSync(faviconPath)
const stylePath = require.resolve('@shopify/app/assets/graphiql/style.css')
const stylePath = require.resolve('@shopify/cli-kit/assets/graphiql/style.css')
const styleContent = readFileSync(stylePath, 'utf8')

app.use(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {platformAndArch} from '@shopify/cli-kit/node/os'
import {platformAndArch} from '../../os.js'
import React from 'react'
import {renderToStaticMarkup} from 'react-dom/server'
import {AppProvider, Badge, Banner, BlockStack, Box, Grid, InlineStack, Link, Select, Text} from '@shopify/polaris'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/**
* Headers that should NOT be forwarded from the GraphiQL client to the Admin API.
* These include:
* - Hop-by-hop headers (RFC 7230) that are connection-specific
* - Browser-specific headers that are not relevant to API requests
* - Headers the proxy sets itself (auth, content-type, etc.)
* - Hop-by-hop headers (RFC 7230) that are connection-specific.
* - Browser-specific headers that are not relevant to API requests.
* - Headers the proxy sets itself (auth, content-type, etc.).
*/
const BLOCKED_HEADERS = new Set([
// Hop-by-hop headers (RFC 7230 Section 6.1)
Expand All @@ -30,6 +30,9 @@ const BLOCKED_HEADERS = new Set([
/**
* Filters request headers to extract only custom headers that are safe to forward.
* Blocked headers and non-string values are excluded.
*
* @param headers - The raw incoming request headers.
* @returns The subset of headers that are safe to forward to the Admin API.
*/
export function filterCustomHeaders(headers: {[key: string]: string | string[] | undefined}): {[key: string]: string} {
const customHeaders: {[key: string]: string} = {}
Expand Down
27 changes: 15 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading