diff --git a/bin/bundling/esbuild-plugin-graphiql-imports.js b/bin/bundling/esbuild-plugin-graphiql-imports.js index 538bf7d95de..2c5a31f8a2b 100644 --- a/bin/bundling/esbuild-plugin-graphiql-imports.js +++ b/bin/bundling/esbuild-plugin-graphiql-imports.js @@ -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'), } }) }, diff --git a/packages/app/package.json b/packages/app/package.json index 537ba164561..c0566e69b9f 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -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", @@ -68,7 +66,6 @@ "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" }, @@ -76,7 +73,6 @@ "@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" diff --git a/packages/app/src/cli/services/dev/processes/graphiql.ts b/packages/app/src/cli/services/dev/processes/graphiql.ts index 6b00d808c55..06776b27e78 100644 --- a/packages/app/src/cli/services/dev/processes/graphiql.ts +++ b/packages/app/src/cli/services/dev/processes/graphiql.ts @@ -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 diff --git a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts index aeb6702a4ea..06ad0ad0f00 100644 --- a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts +++ b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts @@ -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, @@ -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' diff --git a/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts b/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts index 0c6ca0b2f6d..9feb2b29f07 100644 --- a/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts +++ b/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts @@ -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' @@ -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' diff --git a/packages/app/assets/graphiql/favicon.ico b/packages/cli-kit/assets/graphiql/favicon.ico similarity index 100% rename from packages/app/assets/graphiql/favicon.ico rename to packages/cli-kit/assets/graphiql/favicon.ico diff --git a/packages/app/assets/graphiql/style.css b/packages/cli-kit/assets/graphiql/style.css similarity index 100% rename from packages/app/assets/graphiql/style.css rename to packages/cli-kit/assets/graphiql/style.css diff --git a/packages/cli-kit/package.json b/packages/cli-kit/package.json index 13d9f4af833..5fa54c62837 100644 --- a/packages/cli-kit/package.json +++ b/packages/cli-kit/package.json @@ -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", @@ -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", @@ -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", @@ -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", diff --git a/packages/app/src/cli/services/dev/graphiql/server.test.ts b/packages/cli-kit/src/public/node/graphiql/server.test.ts similarity index 100% rename from packages/app/src/cli/services/dev/graphiql/server.test.ts rename to packages/cli-kit/src/public/node/graphiql/server.test.ts diff --git a/packages/app/src/cli/services/dev/graphiql/server.ts b/packages/cli-kit/src/public/node/graphiql/server.ts similarity index 82% rename from packages/app/src/cli/services/dev/graphiql/server.ts rename to packages/cli-kit/src/public/node/graphiql/server.ts index 4ee8646d38a..4b83473910d 100644 --- a/packages/app/src/cli/services/dev/graphiql/server.ts +++ b/packages/cli-kit/src/public/node/graphiql/server.ts @@ -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, @@ -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' @@ -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') @@ -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 @@ -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. @@ -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( diff --git a/packages/app/src/cli/services/dev/graphiql/templates/graphiql.tsx b/packages/cli-kit/src/public/node/graphiql/templates/graphiql.tsx similarity index 99% rename from packages/app/src/cli/services/dev/graphiql/templates/graphiql.tsx rename to packages/cli-kit/src/public/node/graphiql/templates/graphiql.tsx index a14e1e0c9d6..edc3638df34 100644 --- a/packages/app/src/cli/services/dev/graphiql/templates/graphiql.tsx +++ b/packages/cli-kit/src/public/node/graphiql/templates/graphiql.tsx @@ -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' diff --git a/packages/app/src/cli/services/dev/graphiql/templates/unauthorized.tsx b/packages/cli-kit/src/public/node/graphiql/templates/unauthorized.tsx similarity index 100% rename from packages/app/src/cli/services/dev/graphiql/templates/unauthorized.tsx rename to packages/cli-kit/src/public/node/graphiql/templates/unauthorized.tsx diff --git a/packages/app/src/cli/services/dev/graphiql/utilities.test.ts b/packages/cli-kit/src/public/node/graphiql/utilities.test.ts similarity index 100% rename from packages/app/src/cli/services/dev/graphiql/utilities.test.ts rename to packages/cli-kit/src/public/node/graphiql/utilities.test.ts diff --git a/packages/app/src/cli/services/dev/graphiql/utilities.ts b/packages/cli-kit/src/public/node/graphiql/utilities.ts similarity index 80% rename from packages/app/src/cli/services/dev/graphiql/utilities.ts rename to packages/cli-kit/src/public/node/graphiql/utilities.ts index d900e443093..85573897f08 100644 --- a/packages/app/src/cli/services/dev/graphiql/utilities.ts +++ b/packages/cli-kit/src/public/node/graphiql/utilities.ts @@ -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) @@ -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} = {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c5ed417d78..adda6486305 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,12 +160,6 @@ importers: '@shopify/plugin-cloudflare': specifier: 3.93.0 version: link:../plugin-cloudflare - '@shopify/polaris': - specifier: 12.27.0 - version: 12.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@shopify/polaris-icons': - specifier: 8.11.1 - version: 8.11.1(react@19.2.4) '@shopify/theme': specifier: 3.93.0 version: link:../theme @@ -208,9 +202,6 @@ importers: react: specifier: 19.2.4 version: 19.2.4 - react-dom: - specifier: 19.2.4 - version: 19.2.4(react@19.2.4) which: specifier: 4.0.0 version: 4.0.0 @@ -227,9 +218,6 @@ importers: '@types/react': specifier: 18.3.12 version: 18.3.12 - '@types/react-dom': - specifier: ^19.0.0 - version: 19.2.3(@types/react@18.3.12) '@types/which': specifier: 3.0.4 version: 3.0.4 @@ -324,6 +312,12 @@ importers: '@opentelemetry/sdk-metrics': specifier: 1.30.0 version: 1.30.0(@opentelemetry/api@1.9.0) + '@shopify/polaris': + specifier: 12.27.0 + version: 12.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@shopify/polaris-icons': + specifier: 8.11.1 + version: 8.11.1(react@19.2.4) '@shopify/toml-patch': specifier: 0.3.0 version: 0.3.0 @@ -390,6 +384,9 @@ importers: graphql-request: specifier: 6.1.0 version: 6.1.0(graphql@16.10.0) + h3: + specifier: 1.15.9 + version: 1.15.9 ignore: specifier: 6.0.2 version: 6.0.2 @@ -441,6 +438,9 @@ importers: react: specifier: 19.2.4 version: 19.2.4 + react-dom: + specifier: 19.2.4 + version: 19.2.4(react@19.2.4) semver: specifier: 7.6.3 version: 7.6.3 @@ -478,6 +478,9 @@ importers: '@types/react': specifier: 18.3.12 version: 18.3.12 + '@types/react-dom': + specifier: ^19.0.0 + version: 19.2.3(@types/react@18.3.12) '@types/semver': specifier: ^7.5.2 version: 7.7.1