From 7030c7f84883b9dce6380b08b5fc0482c6f56b26 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 29 Jan 2025 08:05:01 +0100 Subject: [PATCH] fix: glob backward compatibility (#4775) --- lib/codecept.js | 22 ++--- lib/command/gherkin/snippets.js | 138 +++++++++++++++--------------- lib/command/run-multiple/chunk.js | 93 ++++++++++---------- lib/container.js | 4 +- package.json | 2 +- 5 files changed, 132 insertions(+), 127 deletions(-) diff --git a/lib/codecept.js b/lib/codecept.js index f1a76ca00..e18aba1f8 100644 --- a/lib/codecept.js +++ b/lib/codecept.js @@ -1,5 +1,5 @@ const { existsSync, readFileSync } = require('fs') -const glob = require('glob') +const { globSync } = require('glob') const fsPath = require('path') const { resolve } = require('path') @@ -168,15 +168,17 @@ class Codecept { } for (pattern of patterns) { - glob.sync(pattern, options).forEach(file => { - if (file.includes('node_modules')) return - if (!fsPath.isAbsolute(file)) { - file = fsPath.join(global.codecept_dir, file) - } - if (!this.testFiles.includes(fsPath.resolve(file))) { - this.testFiles.push(fsPath.resolve(file)) - } - }) + if (pattern) { + globSync(pattern, options).forEach(file => { + if (file.includes('node_modules')) return + if (!fsPath.isAbsolute(file)) { + file = fsPath.join(global.codecept_dir, file) + } + if (!this.testFiles.includes(fsPath.resolve(file))) { + this.testFiles.push(fsPath.resolve(file)) + } + }) + } } } diff --git a/lib/command/gherkin/snippets.js b/lib/command/gherkin/snippets.js index fded183cb..01b55d74c 100644 --- a/lib/command/gherkin/snippets.js +++ b/lib/command/gherkin/snippets.js @@ -1,113 +1,113 @@ -const escapeStringRegexp = require('escape-string-regexp'); -const fs = require('fs'); -const Gherkin = require('@cucumber/gherkin'); -const Messages = require('@cucumber/messages'); -const glob = require('glob'); -const fsPath = require('path'); +const escapeStringRegexp = require('escape-string-regexp') +const fs = require('fs') +const Gherkin = require('@cucumber/gherkin') +const Messages = require('@cucumber/messages') +const { globSync } = require('glob') +const fsPath = require('path') -const { getConfig, getTestRoot } = require('../utils'); -const Codecept = require('../../codecept'); -const output = require('../../output'); -const { matchStep } = require('../../mocha/bdd'); +const { getConfig, getTestRoot } = require('../utils') +const Codecept = require('../../codecept') +const output = require('../../output') +const { matchStep } = require('../../mocha/bdd') -const uuidFn = Messages.IdGenerator.uuid(); -const builder = new Gherkin.AstBuilder(uuidFn); -const matcher = new Gherkin.GherkinClassicTokenMatcher(); -const parser = new Gherkin.Parser(builder, matcher); -parser.stopAtFirstError = false; +const uuidFn = Messages.IdGenerator.uuid() +const builder = new Gherkin.AstBuilder(uuidFn) +const matcher = new Gherkin.GherkinClassicTokenMatcher() +const parser = new Gherkin.Parser(builder, matcher) +parser.stopAtFirstError = false module.exports = function (genPath, options) { - const configFile = options.config || genPath; - const testsPath = getTestRoot(configFile); - const config = getConfig(configFile); - if (!config) return; + const configFile = options.config || genPath + const testsPath = getTestRoot(configFile) + const config = getConfig(configFile) + if (!config) return - const codecept = new Codecept(config, {}); - codecept.init(testsPath); + const codecept = new Codecept(config, {}) + codecept.init(testsPath) if (!config.gherkin) { - output.error('Gherkin is not enabled in config. Run `codecept gherkin:init` to enable it'); - process.exit(1); + output.error('Gherkin is not enabled in config. Run `codecept gherkin:init` to enable it') + process.exit(1) } if (!config.gherkin.steps || !config.gherkin.steps[0]) { - output.error('No gherkin steps defined in config. Exiting'); - process.exit(1); + output.error('No gherkin steps defined in config. Exiting') + process.exit(1) } if (!options.feature && !config.gherkin.features) { - output.error('No gherkin features defined in config. Exiting'); - process.exit(1); + output.error('No gherkin features defined in config. Exiting') + process.exit(1) } if (options.path && !config.gherkin.steps.includes(options.path)) { - output.error(`You must include ${options.path} to the gherkin steps in your config file`); - process.exit(1); + output.error(`You must include ${options.path} to the gherkin steps in your config file`) + process.exit(1) } - const files = []; - glob.sync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : global.codecept_dir }).forEach(file => { + const files = [] + globSync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : global.codecept_dir }).forEach(file => { if (!fsPath.isAbsolute(file)) { - file = fsPath.join(global.codecept_dir, file); + file = fsPath.join(global.codecept_dir, file) } - files.push(fsPath.resolve(file)); - }); - output.print(`Loaded ${files.length} files`); + files.push(fsPath.resolve(file)) + }) + output.print(`Loaded ${files.length} files`) - const newSteps = new Map(); + const newSteps = new Map() const parseSteps = steps => { - const newSteps = []; - let currentKeyword = ''; + const newSteps = [] + let currentKeyword = '' for (const step of steps) { if (step.keyword.trim() === 'And') { - if (!currentKeyword) throw new Error(`There is no active keyword for step '${step.text}'`); - step.keyword = currentKeyword; + if (!currentKeyword) throw new Error(`There is no active keyword for step '${step.text}'`) + step.keyword = currentKeyword } - currentKeyword = step.keyword; + currentKeyword = step.keyword try { - matchStep(step.text); + matchStep(step.text) } catch (err) { - let stepLine; + let stepLine if (/[{}()/]/.test(step.text)) { stepLine = escapeStringRegexp(step.text) .replace(/\//g, '\\/') .replace(/\"(.*?)\"/g, '"(.*?)"') .replace(/(\d+\\\.\d+)/, '(\\d+\\.\\d+)') - .replace(/ (\d+) /, ' (\\d+) '); - stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: true }); + .replace(/ (\d+) /, ' (\\d+) ') + stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: true }) } else { stepLine = step.text .replace(/\"(.*?)\"/g, '{string}') .replace(/(\d+\.\d+)/, '{float}') - .replace(/ (\d+) /, ' {int} '); - stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: false }); + .replace(/ (\d+) /, ' {int} ') + stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: false }) } - newSteps.push(stepLine); + newSteps.push(stepLine) } } - return newSteps; - }; + return newSteps + } const parseFile = file => { - const ast = parser.parse(fs.readFileSync(file).toString()); + const ast = parser.parse(fs.readFileSync(file).toString()) for (const child of ast.feature.children) { - if (child.scenario.keyword === 'Scenario Outline') continue; // skip scenario outline + if (child.scenario.keyword === 'Scenario Outline') continue // skip scenario outline parseSteps(child.scenario.steps) .map(step => { - return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) }); + return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) }) }) - .map(step => newSteps.set(`${step.type}(${step})`, step)); + .map(step => newSteps.set(`${step.type}(${step})`, step)) } - }; + } - files.forEach(file => parseFile(file)); + files.forEach(file => parseFile(file)) - let stepFile = options.path || config.gherkin.steps[0]; + let stepFile = options.path || config.gherkin.steps[0] if (!fs.existsSync(stepFile)) { - output.error(`Please enter a valid step file path ${stepFile}`); - process.exit(1); + output.error(`Please enter a valid step file path ${stepFile}`) + process.exit(1) } if (!fsPath.isAbsolute(stepFile)) { - stepFile = fsPath.join(global.codecept_dir, stepFile); + stepFile = fsPath.join(global.codecept_dir, stepFile) } const snippets = [...newSteps.values()] @@ -117,18 +117,18 @@ module.exports = function (genPath, options) { ${step.type}(${step.regexp ? '/^' : "'"}${step}${step.regexp ? '$/' : "'"}, () => { // From "${step.file}" ${JSON.stringify(step.location)} throw new Error('Not implemented yet'); -});`; - }); +});` + }) if (!snippets.length) { - output.print('No new snippets found'); - return; + output.print('No new snippets found') + return } - output.success(`Snippets generated: ${snippets.length}`); - output.print(snippets.join('\n')); + output.success(`Snippets generated: ${snippets.length}`) + output.print(snippets.join('\n')) if (!options.dryRun) { - output.success(`Snippets added to ${output.colors.bold(stepFile)}`); - fs.writeFileSync(stepFile, fs.readFileSync(stepFile).toString() + snippets.join('\n') + '\n'); + output.success(`Snippets added to ${output.colors.bold(stepFile)}`) + fs.writeFileSync(stepFile, fs.readFileSync(stepFile).toString() + snippets.join('\n') + '\n') } -}; +} diff --git a/lib/command/run-multiple/chunk.js b/lib/command/run-multiple/chunk.js index d57fcafe4..a4e3f660e 100644 --- a/lib/command/run-multiple/chunk.js +++ b/lib/command/run-multiple/chunk.js @@ -1,60 +1,60 @@ -const glob = require('glob'); -const path = require('path'); -const fs = require('fs'); +const { globSync } = require('glob') +const path = require('path') +const fs = require('fs') /** * Splits a list to (n) parts, defined via the size argument. */ const splitFiles = (list, size) => { - const sets = []; - const chunks = list.length / size; - let i = 0; + const sets = [] + const chunks = list.length / size + let i = 0 while (i < chunks) { - sets[i] = list.splice(0, size); - i++; + sets[i] = list.splice(0, size) + i++ } - return sets; -}; + return sets +} /** * Executes a glob pattern and pushes the results to a list. */ -const findFiles = (pattern) => { - const files = []; +const findFiles = pattern => { + const files = [] - glob.sync(pattern).forEach((file) => { - files.push(path.resolve(file)); - }); + globSync(pattern).forEach(file => { + files.push(path.resolve(file)) + }) - return files; -}; + return files +} /** * Joins a list of files to a valid glob pattern */ -const flattenFiles = (list) => { - const pattern = list.join(','); - return pattern.indexOf(',') > -1 ? `{${pattern}}` : pattern; -}; +const flattenFiles = list => { + const pattern = list.join(',') + return pattern.indexOf(',') > -1 ? `{${pattern}}` : pattern +} /** * Greps a file by its content, checks if Scenario or Feature text' * matches the grep text. */ const grepFile = (file, grep) => { - const contents = fs.readFileSync(file, 'utf8'); - const pattern = new RegExp(`((Scenario|Feature)\(.*${grep}.*\))`, 'g'); // <- How future proof/solid is this? - return !!pattern.exec(contents); -}; + const contents = fs.readFileSync(file, 'utf8') + const pattern = new RegExp(`((Scenario|Feature)\(.*${grep}.*\))`, 'g') // <- How future proof/solid is this? + return !!pattern.exec(contents) +} -const mapFileFormats = (files) => { +const mapFileFormats = files => { return { gherkin: files.filter(file => file.match(/\.feature$/)), js: files.filter(file => file.match(/\.t|js$/)), - }; -}; + } +} /** * Creates a list of chunks incl. configuration by either dividing a list of scenario @@ -62,30 +62,33 @@ const mapFileFormats = (files) => { * the splitting. */ const createChunks = (config, patterns = []) => { - const files = patterns.filter(pattern => !!pattern).map((pattern) => { - return findFiles(pattern).filter((file) => { - return config.grep ? grepFile(file, config.grep) : true; - }); - }).reduce((acc, val) => acc.concat(val), []); + const files = patterns + .filter(pattern => !!pattern) + .map(pattern => { + return findFiles(pattern).filter(file => { + return config.grep ? grepFile(file, config.grep) : true + }) + }) + .reduce((acc, val) => acc.concat(val), []) - let chunks = []; + let chunks = [] if (typeof config.chunks === 'function') { - chunks = config.chunks.call(this, files); + chunks = config.chunks.call(this, files) } else if (typeof config.chunks === 'number' || typeof config.chunks === 'string') { - chunks = splitFiles(files, Math.ceil(files.length / config.chunks)); + chunks = splitFiles(files, Math.ceil(files.length / config.chunks)) } else { - throw new Error('chunks is neither a finite number or a valid function'); + throw new Error('chunks is neither a finite number or a valid function') } - const chunkConfig = { ...config }; - delete chunkConfig.chunks; + const chunkConfig = { ...config } + delete chunkConfig.chunks - return chunks.map((chunkFiles) => { - const { js, gherkin } = mapFileFormats(chunkFiles); - return { ...chunkConfig, tests: flattenFiles(js), gherkin: { features: flattenFiles(gherkin) } }; - }); -}; + return chunks.map(chunkFiles => { + const { js, gherkin } = mapFileFormats(chunkFiles) + return { ...chunkConfig, tests: flattenFiles(js), gherkin: { features: flattenFiles(gherkin) } } + }) +} module.exports = { createChunks, -}; +} diff --git a/lib/container.js b/lib/container.js index e2913bd3d..267ec5cff 100644 --- a/lib/container.js +++ b/lib/container.js @@ -1,4 +1,4 @@ -const glob = require('glob') +const { globSync } = require('glob') const path = require('path') const debug = require('debug')('codeceptjs:container') const { MetaStep } = require('./step') @@ -464,7 +464,7 @@ function loadGherkinSteps(paths) { } else { const folderPath = paths.startsWith('.') ? path.join(global.codecept_dir, paths) : '' if (folderPath !== '') { - glob.sync(folderPath).forEach(file => { + globSync(folderPath).forEach(file => { loadSupportObject(file, `Step Definition from ${file}`) }) } diff --git a/package.json b/package.json index 839056e74..ea0fced8b 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "figures": "3.2.0", "fn-args": "4.0.0", "fs-extra": "11.3.0", - "glob": "^11.0.1", + "glob": ">=9.0.0 <12", "fuse.js": "^7.0.0", "html-minifier-terser": "7.2.0", "inquirer": "6.5.2",