cli
cli copied to clipboard
feat(graphiql): integrate console with app server
WHY are these changes introduced?
This is the final PR in the 8-PR stack that completes the GraphiQL migration by integrating the new React 18 console with the app server.
Context: All previous PRs built the standalone React console. Now we need to:
- Update the server to serve the built React app instead of using templates
- Inject runtime configuration securely into the page
- Remove the old template-based implementation
- Ensure all existing functionality continues to work
This completes the migration from template-based GraphiQL to a modern React 18 standalone package.
WHAT is this pull request doing?
This PR updates the Express server to serve the new GraphiQL console and removes the old template implementation.
Key Changes:
1. Server Integration (packages/app/src/cli/services/dev/graphiql/server.ts):
Serve Built React App:
// Find the built React app
const graphiqlAssetsDir = await findPathUp(joinPath('assets', 'graphiql'), {
type: 'directory',
cwd: moduleDirectory(import.meta.url),
})
// Serve static assets (JS, CSS, Monaco workers)
app.use('/extensions/graphiql/assets', express.static(...))
app.use('/monacoeditorwork', express.static(...))
Secure Config Injection:
const config = {
apiVersion, // Current API version
apiVersions, // Available versions
appName, // App name from dev config
appUrl, // App preview URL
storeFqdn, // Store domain
baseUrl, // Server base URL
key, // Optional security key
query, // Optional initial query from URL param
}
// SECURITY: Escape < > & using Unicode escapes to prevent XSS
// This prevents script tag breakout: </script><script>alert('xss')</script>
const safeJson = JSON.stringify(config)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026')
const configScript = `<script>window.__GRAPHIQL_CONFIG__ = ${safeJson};</script>`
indexHtml = indexHtml.replace('</head>', `${configScript}\n </head>`)
Why Unicode Escapes?
- HTML entities (
<,>) would break JavaScript parsing - Unicode escapes (
\u003c) are decoded correctly by JavaScript - Prevents XSS attacks through config injection
- Works with the client-side
validateConfig(PR #6579) for defense-in-depth
Attack Prevention Example:
// Without escaping (VULNERABLE):
const config = {query: "</script><script>alert('xss')</script><script>"}
// Result: <script>window.__GRAPHIQL_CONFIG__ = {..."</script><script>alert('xss')...
// Browser executes the injected script!
// With Unicode escaping (SAFE):
const safeJson = JSON.stringify(config).replace(/</g, '\\u003c').replace(/>/g, '\\u003e')
// Result: <script>window.__GRAPHIQL_CONFIG__ = {..."\u003c/script\u003e\u003cscript\u003ealert('xss')...
// Browser treats it as data, not executable code!
Query Parameter Support:
// Support passing initial query via URL
// GET /graphiql?query=...
const query = decodeQueryString(req.query.query as string)
2. New Server Endpoints:
/graphiql/ping (GET):
- Returns "pong" to indicate server is alive
- Used by
useServerStatushook for health checks - Polls every 2 seconds by default
/graphiql/status (GET):
- Returns app installation status and store info
- Checks if token refresh succeeds (app is installed)
- Returns:
{status: 'OK', storeFqdn, appName, appUrl} - Used by
useServerStatushook for app status - Polls every 5 seconds by default
3. Cleanup - Remove Old Template Implementation:
- ❌ Deleted
graphiql.tsxtemplate (365 lines) - ❌ Deleted old
style.css(58 lines) - ✅ Total: 423 lines of legacy code removed
4. Build Integration:
// packages/app/project.json
{
"targets": {
"build": {
"dependsOn": ["^build"], // Build graphiql-console first
}
}
}
- App build now depends on graphiql-console build
- Ensures built assets are available before app builds
5. .gitignore Update:
packages/app/assets/graphiql
- Ignore built assets (generated by Vite)
- Assets are built during CI and local development
Files Modified:
packages/app/src/cli/services/dev/graphiql/server.ts- Server integration (57 additions, 34 deletions)packages/app/project.json- Build dependency.gitignore- Ignore built assets
Files Deleted:
packages/app/src/cli/services/dev/graphiql/templates/graphiql.tsx- Old template (365 lines)packages/app/assets/graphiql/style.css- Old styles (58 lines)
Dependencies
Builds on ALL 7 previous PRs:
- PR #6578 - Package foundation
- PR #6579 - Types, validation (validateConfig)
- PR #6580 - UI components
- PR #6581 - API version selector
- PR #6582 - GraphiQL editor
- PR #6583 - Server status hooks
- PR #6584 - Complete GraphiQL section
Result: 🎉 Migration Complete! The GraphiQL console is now a standalone React 18 package integrated with the app server.
How to test your changes?
Build and Run:
# Build the graphiql-console package
pnpm --filter @shopify/graphiql-console build
# Verify built assets exist
ls -la packages/app/assets/graphiql
# Run the dev server
cd /path/to/shopify-app
dev server
# Open GraphiQL
# The server will log the URL, typically: http://localhost:3457/graphiql
Verify Functionality:
- ✅ GraphiQL loads with React UI
- ✅ Status badge shows "Running" (green)
- ✅ API version selector shows available versions
- ✅ Store and app link pills appear
- ✅ Monaco syntax highlighting works
- ✅ GraphQL queries execute successfully
- ✅ Switching API versions re-fetches schema
Test Server Health Monitoring:
- While GraphiQL is open, stop the dev server (
Ctrl+C) - ✅ Status badge changes to "Disconnected" (red)
- ✅ Error banner appears: "The server has been stopped"
- Restart the dev server
- ✅ Status badge changes back to "Running" (green)
- ✅ Error banner disappears
Test Config Injection: Open browser DevTools console:
console.log(window.__GRAPHIQL_CONFIG__)
// Should show: {apiVersion, apiVersions, appName, appUrl, storeFqdn, baseUrl, ...}
Test Query Parameter:
# Pass initial query via URL
http://localhost:3457/graphiql?query=%7Bshop%7Bname%7D%7D
# GraphiQL should open with "{shop{name}}" query pre-loaded
Security Verification: Verify config escaping in page source (View Page Source):
<script>window.__GRAPHIQL_CONFIG__ = {..."query":"\u003cscript\u003ealert('test')\u003c/script\u003e"...};</script>
- Should see
\u003cand\u003einstead of<and> - No executable script tags should be present
Measuring impact
- [x] n/a - this completes the GraphiQL migration to React 18
Post-release steps
None - this is a drop-in replacement for the existing GraphiQL implementation.
Checklist
- [x] I've considered possible cross-platform impacts (Mac, Linux, Windows)
- [x] I've considered possible documentation changes
Migration Summary
This stack successfully migrates GraphiQL from template-based implementation to a modern React 18 standalone package:
Before:
- 365 lines of Rails-like template code
- Direct DOM manipulation with vanilla JavaScript
- No type safety
- Difficult to test and maintain
- Limited features
After:
- Standalone React 18 package with 1,500+ lines of tested code
- Monaco editor integration for advanced syntax highlighting
- Full TypeScript type safety
- Comprehensive test coverage (89 tests, 1,000+ test lines)
- Security-first design (XSS prevention at multiple layers)
- Modern development workflow with Vite
- Responsive design for different screen sizes
- Real-time server health monitoring
- Maintainable, testable, and extensible codebase
Security Improvements:
- Server-side config escaping (Unicode escapes)
- Client-side config validation and sanitization (PR #6579)
- URL allowlist for localhost and Shopify domains
- Defense-in-depth approach to XSS prevention
Developer Experience:
- Hot reload during development
- Type-checked at compile time
- Component isolation for easier testing
- Reusable hooks for common patterns
- Modern React patterns (hooks, functional components)
[!WARNING] This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite. Learn more
- #6585
👈 (View in Graphite) - #6584

- #6583

- #6582

- #6581

- #6580

- #6579

- #6578

main
This stack of pull requests is managed by Graphite. Learn more about stacking.
Coverage report
St.:grey_question: |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟡 | Statements | 79.35% | 13745/17323 |
| 🟡 | Branches | 73.27% | 6728/9183 |
| 🟡 | Functions | 79.5% | 3549/4464 |
| 🟡 | Lines | 79.71% | 12976/16280 |
Test suite run success
3447 tests passing in 1401 suites.
Report generated by 🧪jest coverage report action from 8dc20f9bcb7631a4954472d05f55898f694d6747