diff --git a/lib/stencil-push.js b/lib/stencil-push.js index 63071eb4..09a10001 100644 --- a/lib/stencil-push.js +++ b/lib/stencil-push.js @@ -8,6 +8,7 @@ function stencilPush(options = {}, callback) { utils.readStencilConfigFile, utils.getStoreHash, utils.getThemes, + utils.validateGqlQueries, utils.generateBundle, utils.uploadBundle, utils.notifyUserOfThemeLimitReachedIfNecessary, diff --git a/lib/stencil-push.utils.js b/lib/stencil-push.utils.js index 756a5073..edc77b68 100644 --- a/lib/stencil-push.utils.js +++ b/lib/stencil-push.utils.js @@ -5,6 +5,9 @@ const ProgressBar = require('progress'); const uuid = require('uuid4'); const os = require('os'); +const fs = require('fs'); +const path = require('path'); +const { parse } = require('graphql/index'); const { THEME_PATH } = require('../constants'); const Bundle = require('./stencil-bundle'); const themeApiClient = require('./theme-api-client'); @@ -518,4 +521,61 @@ utils.notifyUserOfCompletion = (options, callback) => { callback(null, `Stencil Push Finished. Variation ID: ${options.variationId}`); }; +function validateQuery(query) { + try { + parse(query); + return true; + } catch (error) { + console.error('Error parsing GraphQL query:', error); + return false; + } +} + +const gqlAssetDir = path.join(process.cwd(), 'assets', 'js'); + +const gqlRegex = /^(query|mutation)\s+([a-zA-Z]+|\{)/; +const stringLiteralRegex = /(['"`])(\\?.)*?\1|(`(?:\\.|[^`])*`)/g; + +utils.validateGqlQueries = async (options) => { + let invalidQueriesFound = false; + + function scanFiles(dir) { + fs.readdirSync(dir).forEach((file) => { + const filePath = path.join(dir, file); + + // If it's a directory, scan recursively + if (fs.lstatSync(filePath).isDirectory()) { + scanFiles(filePath); + } else if (path.extname(file) === '.js' && !filePath.includes('test')) { + const content = fs.readFileSync(filePath, 'utf8'); + + const stringLiterals = content.match(stringLiteralRegex); + + if (stringLiterals) { + stringLiterals.forEach((stringLiteral) => { + // Remove surrounding quotes or backticks for further processing + const cleanedString = stringLiteral.slice(1, -1); + + const matches = cleanedString.match(gqlRegex); + if (matches) { + if (!validateQuery(cleanedString)) { + console.error(`Invalid GraphQL query found in file: ${filePath}`); + invalidQueriesFound = true; + } + } + }); + } + } + }); + } + + scanFiles(gqlAssetDir); + + if (invalidQueriesFound) { + throw new Error('GraphQL query validation failed. Fix it before proceeding.'); + } + + return options; +}; + module.exports = utils; diff --git a/package-lock.json b/package-lock.json index f36e1a0b..ea6749b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bigcommerce/stencil-cli", - "version": "8.0.0", + "version": "8.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@bigcommerce/stencil-cli", - "version": "8.0.0", + "version": "8.2.0", "license": "BSD-4-Clause", "dependencies": { "@bigcommerce/stencil-paper": "5.0.0", @@ -30,6 +30,7 @@ "front-matter": "^4.0.2", "glob": "^7.1.6", "graceful-fs": "^4.2.4", + "graphql": "^16.9.0", "husky": "^8.0.1", "image-size": "^0.9.1", "inquirer": "^8.1.5", @@ -8931,6 +8932,15 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, + "node_modules/graphql": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/handlebars": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-3.0.8.tgz", @@ -25431,6 +25441,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, + "graphql": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==" + }, "handlebars": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-3.0.8.tgz", diff --git a/package.json b/package.json index 2d34a0e3..5f127a20 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "front-matter": "^4.0.2", "glob": "^7.1.6", "graceful-fs": "^4.2.4", + "graphql": "^16.9.0", "husky": "^8.0.1", "image-size": "^0.9.1", "inquirer": "^8.1.5",