From cdf2968ee45b4a4ad9878c1742ed8e45ad11b730 Mon Sep 17 00:00:00 2001 From: hang-up Date: Wed, 5 Jan 2022 00:12:36 -0500 Subject: [PATCH 01/10] Added is-glob as dependency for packages/server --- packages/server/package.json | 1 + yarn.lock | 21 ++++++++------------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index 0b7651d7f9f5..9d29b0b75176 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -70,6 +70,7 @@ "human-interval": "1.0.0", "image-size": "0.8.3", "is-fork-pr": "2.5.0", + "is-glob": "4.0.3", "is-html": "2.0.0", "jimp": "0.14.0", "jsonlint": "1.6.3", diff --git a/yarn.lock b/yarn.lock index 7d856ed02336..2155adf847af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19746,12 +19746,7 @@ follow-redirects@1.5.10: dependencies: debug "=3.1.0" -follow-redirects@^1.0.0: - version "1.12.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.12.1.tgz#de54a6205311b93d60398ebc01cf7015682312b6" - integrity sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg== - -follow-redirects@^1.14.0: +follow-redirects@^1.0.0, follow-redirects@^1.14.0: version "1.14.5" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381" integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA== @@ -22992,6 +22987,13 @@ is-generator-function@^1.0.7: resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.8.tgz#dfb5c2b120e02b0a8d9d2c6806cd5621aa922f7b" integrity sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ== +is-glob@4.0.3, is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -23006,13 +23008,6 @@ is-glob@^3.0.0, is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - is-hexadecimal@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" From 9d0d6a86a967c65065c8bfc36c642ae111cdab61 Mon Sep 17 00:00:00 2001 From: hang-up Date: Wed, 5 Jan 2022 00:14:48 -0500 Subject: [PATCH 02/10] Parsed spec argv for glob patterns. --- packages/server/lib/util/args.js | 35 +++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/server/lib/util/args.js b/packages/server/lib/util/args.js index ea76dc262f37..7434b9f7bf0e 100644 --- a/packages/server/lib/util/args.js +++ b/packages/server/lib/util/args.js @@ -5,6 +5,8 @@ const path = require('path') const debug = require('debug')('cypress:server:args') const minimist = require('minimist') const { getPublicConfigKeys } = require('@packages/config') +const ig = require('is-glob') +const glob = require('glob') const coerceUtil = require('./coerce') const proxyUtil = require('./proxy') @@ -204,6 +206,18 @@ const sanitizeAndConvertNestedArgs = (str, argname) => { } } +const parseSpecArgvAsGlob = (spec, cwd) => { + return { + isGlob: ig(spec), + setGlob: (pattern) => { + const globbed = glob.sync(path.resolve(cwd, pattern)) + + // This is needed to output the glob pattern on error. + return globbed.length ? globbed : [spec] + }, + } +} + module.exports = { normalizeBackslashes, @@ -303,12 +317,27 @@ module.exports = { spec = spec.substring(1, spec.length - 1) } - options.spec = strToArray(spec).map(resolvePath) + // Before blindly parsing to an array of path, check if spec is not + // a glob pattern with commas. + const { isGlob, setGlob } = parseSpecArgvAsGlob(spec, options.cwd) + + if (isGlob) { + options.spec = setGlob(spec) + } else { + options.spec = strToArray(spec).map(resolvePath) + } } else { - options.spec = spec.map(resolvePath) + const stringifiedArg = spec.join() + const { isGlob, setGlob } = parseSpecArgvAsGlob(stringifiedArg, options.cwd) + + if (isGlob) { + options.spec = setGlob(stringifiedArg) + } else { + options.spec = spec.map(resolvePath) + } } } catch (err) { - debug('could not pass config spec value %s', spec) + debug('could not parse config spec value %s', spec) debug('error %o', err) return errors.throw('COULD_NOT_PARSE_ARGUMENTS', 'spec', spec, 'spec must be a string or comma-separated list') From c39dc4108827d14ba0f0e1e426b0529bac1b9bf3 Mon Sep 17 00:00:00 2001 From: hang-up Date: Wed, 5 Jan 2022 01:14:34 -0500 Subject: [PATCH 03/10] Removed setGlob parameter. --- packages/server/lib/util/args.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/lib/util/args.js b/packages/server/lib/util/args.js index 7434b9f7bf0e..d3e26b2e8726 100644 --- a/packages/server/lib/util/args.js +++ b/packages/server/lib/util/args.js @@ -209,8 +209,8 @@ const sanitizeAndConvertNestedArgs = (str, argname) => { const parseSpecArgvAsGlob = (spec, cwd) => { return { isGlob: ig(spec), - setGlob: (pattern) => { - const globbed = glob.sync(path.resolve(cwd, pattern)) + setGlob: () => { + const globbed = glob.sync(path.resolve(cwd, spec)) // This is needed to output the glob pattern on error. return globbed.length ? globbed : [spec] @@ -322,7 +322,7 @@ module.exports = { const { isGlob, setGlob } = parseSpecArgvAsGlob(spec, options.cwd) if (isGlob) { - options.spec = setGlob(spec) + options.spec = setGlob() } else { options.spec = strToArray(spec).map(resolvePath) } @@ -331,7 +331,7 @@ module.exports = { const { isGlob, setGlob } = parseSpecArgvAsGlob(stringifiedArg, options.cwd) if (isGlob) { - options.spec = setGlob(stringifiedArg) + options.spec = setGlob() } else { options.spec = spec.map(resolvePath) } From b4cf632e29c44851421955f3728ce47d4d5ee3dd Mon Sep 17 00:00:00 2001 From: hang-up Date: Wed, 5 Jan 2022 17:54:40 -0500 Subject: [PATCH 04/10] Updated glob implementation to revert side effects --- packages/server/lib/util/args.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/server/lib/util/args.js b/packages/server/lib/util/args.js index d3e26b2e8726..d4c663a6920a 100644 --- a/packages/server/lib/util/args.js +++ b/packages/server/lib/util/args.js @@ -6,7 +6,7 @@ const debug = require('debug')('cypress:server:args') const minimist = require('minimist') const { getPublicConfigKeys } = require('@packages/config') const ig = require('is-glob') -const glob = require('glob') +const braces = require('braces') const coerceUtil = require('./coerce') const proxyUtil = require('./proxy') @@ -210,10 +210,13 @@ const parseSpecArgvAsGlob = (spec, cwd) => { return { isGlob: ig(spec), setGlob: () => { - const globbed = glob.sync(path.resolve(cwd, spec)) + const hasBrackets = ['{', '}', '[', ']'].some((token) => spec.includes(token)) - // This is needed to output the glob pattern on error. - return globbed.length ? globbed : [spec] + if (hasBrackets) { + return braces.expand(spec) + } + + return strToArray(spec) }, } } @@ -322,19 +325,12 @@ module.exports = { const { isGlob, setGlob } = parseSpecArgvAsGlob(spec, options.cwd) if (isGlob) { - options.spec = setGlob() + options.spec = setGlob().map(resolvePath) } else { options.spec = strToArray(spec).map(resolvePath) } } else { - const stringifiedArg = spec.join() - const { isGlob, setGlob } = parseSpecArgvAsGlob(stringifiedArg, options.cwd) - - if (isGlob) { - options.spec = setGlob() - } else { - options.spec = spec.map(resolvePath) - } + options.spec = spec.map(resolvePath) } } catch (err) { debug('could not parse config spec value %s', spec) From a6cb20a46b768ccd731e416eb4144ba372cbf3a8 Mon Sep 17 00:00:00 2001 From: hang-up Date: Sat, 8 Jan 2022 23:41:24 -0500 Subject: [PATCH 05/10] Removed is-glob dependency. --- packages/server/package.json | 1 - yarn.lock | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index 9d29b0b75176..0b7651d7f9f5 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -70,7 +70,6 @@ "human-interval": "1.0.0", "image-size": "0.8.3", "is-fork-pr": "2.5.0", - "is-glob": "4.0.3", "is-html": "2.0.0", "jimp": "0.14.0", "jsonlint": "1.6.3", diff --git a/yarn.lock b/yarn.lock index 2155adf847af..d8654a3c3a87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22987,13 +22987,6 @@ is-generator-function@^1.0.7: resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.8.tgz#dfb5c2b120e02b0a8d9d2c6806cd5621aa922f7b" integrity sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ== -is-glob@4.0.3, is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -23008,6 +23001,13 @@ is-glob@^3.0.0, is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-hexadecimal@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" From d513c0bc36a01a72589ac4ada8310cc2fafdbb9a Mon Sep 17 00:00:00 2001 From: hang-up Date: Sat, 8 Jan 2022 23:42:48 -0500 Subject: [PATCH 06/10] Wrote parseSpecArgv method --- packages/server/lib/util/args.js | 123 +++++++++++++++++++++++++------ 1 file changed, 102 insertions(+), 21 deletions(-) diff --git a/packages/server/lib/util/args.js b/packages/server/lib/util/args.js index d4c663a6920a..06a636768b8a 100644 --- a/packages/server/lib/util/args.js +++ b/packages/server/lib/util/args.js @@ -5,8 +5,6 @@ const path = require('path') const debug = require('debug')('cypress:server:args') const minimist = require('minimist') const { getPublicConfigKeys } = require('@packages/config') -const ig = require('is-glob') -const braces = require('braces') const coerceUtil = require('./coerce') const proxyUtil = require('./proxy') @@ -206,19 +204,110 @@ const sanitizeAndConvertNestedArgs = (str, argname) => { } } -const parseSpecArgvAsGlob = (spec, cwd) => { - return { - isGlob: ig(spec), - setGlob: () => { - const hasBrackets = ['{', '}', '[', ']'].some((token) => spec.includes(token)) +const parseSpecArgv = (pattern) => { + const TOKENS = { + OPEN: ['{', '['], + CLOSE: ['}', ']'], + } + const hasToken = [...TOKENS.OPEN, ...TOKENS.CLOSE].some((t) => { + return pattern.includes(t) + }) + const hasComma = pattern.includes(',') + + /** + * Slice & mutate a string. + * + * @param {String} str String to slice & mutate + * @param {Number} end Index to slice to + * @returns [String, String, Number] + */ + const sliceAndMutate = (str, end) => { + return [ + str.slice(0, end), + str.substring(end, str.length), + str.slice(0, end).length, + ] + } - if (hasBrackets) { - return braces.expand(spec) - } + /** + * Sanitizes a path from leftover commas. + * + * @param {String} path + * @returns String + */ + const sanitizeFinalPath = (path) => { + return path.split('')[0] === ',' ? path.substring(1, path.length) : path + } + + if (!hasToken) { + return [].concat(pattern.split(',')) + } - return strToArray(spec) - }, + if (!hasComma) { + return pattern } + + // Get comma rules. + let opens = [] + let closes = [] + const rules = pattern + .split('') + .map((token, index) => { + if (TOKENS.OPEN.includes(token)) { + opens.push(index) + } + + if (TOKENS.CLOSE.includes(token)) { + closes.push(index) + } + + if (token === ',') { + const isBreakable = + index > opens[opens.length - 1] && + index > closes[closes.length - 1] && + opens.length === closes.length + + if (isBreakable) { + return { + comma: index, + isBreakable: true, + } + } + + return { + comma: index, + isBreakable: false, + } + } + + return null + }) + .filter(Boolean) + + // Perform comma breaking logic. + let carry = pattern + let offset = 0 + const partial = rules + .map((rule) => { + if (!rule.isBreakable) { + return null + } + + const [res, mutated, offsettedBy] = sliceAndMutate( + carry, + rule.comma - offset, + ) + + offset = offsettedBy + carry = mutated + + return res + }) + .filter(Boolean) + .map(sanitizeFinalPath) + + // In the end, carry will be left with the last path that hasn't been cut. + return [...partial, sanitizeFinalPath(carry)] } module.exports = { @@ -320,15 +409,7 @@ module.exports = { spec = spec.substring(1, spec.length - 1) } - // Before blindly parsing to an array of path, check if spec is not - // a glob pattern with commas. - const { isGlob, setGlob } = parseSpecArgvAsGlob(spec, options.cwd) - - if (isGlob) { - options.spec = setGlob().map(resolvePath) - } else { - options.spec = strToArray(spec).map(resolvePath) - } + options.spec = parseSpecArgv(spec) } else { options.spec = spec.map(resolvePath) } From 0eef84898fe056183a799485676bcee0921d366b Mon Sep 17 00:00:00 2001 From: hang-up Date: Sun, 9 Jan 2022 00:44:00 -0500 Subject: [PATCH 07/10] Added back resolvePath. --- packages/server/lib/util/args.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/lib/util/args.js b/packages/server/lib/util/args.js index 06a636768b8a..628f873cc0bd 100644 --- a/packages/server/lib/util/args.js +++ b/packages/server/lib/util/args.js @@ -409,7 +409,7 @@ module.exports = { spec = spec.substring(1, spec.length - 1) } - options.spec = parseSpecArgv(spec) + options.spec = parseSpecArgv(spec).map(resolvePath) } else { options.spec = spec.map(resolvePath) } From 56a9d43dda50e1c26a2598bbfbe181071e12033f Mon Sep 17 00:00:00 2001 From: hang-up Date: Sun, 9 Jan 2022 01:59:58 -0500 Subject: [PATCH 08/10] Added unit tests. --- packages/server/test/unit/util/args_spec.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/server/test/unit/util/args_spec.js b/packages/server/test/unit/util/args_spec.js index 3286282a1df2..45c0029b27e1 100644 --- a/packages/server/test/unit/util/args_spec.js +++ b/packages/server/test/unit/util/args_spec.js @@ -164,6 +164,19 @@ describe('lib/util/args', () => { return snapshot('invalid spec error', stripAnsi(err.message)) } }) + + it('should be correctly parsing globs with lists & ranges', function () { + const options = this.setup('--spec', 'cypress/integration/{[!a]*.spec.js,sub1,{sub2,sub3/sub4}}/*.js') + + expect(options.spec[0]).to.eq(`${cwd}/cypress/integration/{[!a]*.spec.js,sub1,{sub2,sub3/sub4}}/*.js`) + }) + + it('should be correctly parsing globs with a mix of lists, ranges & regular paths', function () { + const options = this.setup('--spec', 'cypress/integration/{[!a]*.spec.js,sub1,{sub2,sub3/sub4}}/*.js,cypress/integration/foo.spec.js') + + expect(options.spec[0]).to.eq(`${cwd}/cypress/integration/{[!a]*.spec.js,sub1,{sub2,sub3/sub4}}/*.js`) + expect(options.spec[1]).to.eq(`${cwd}/cypress/integration/foo.spec.js`) + }) }) context('--tag', () => { From d68c6be74ba8e4b9f0a03007699d69d23309a706 Mon Sep 17 00:00:00 2001 From: hang-up Date: Wed, 12 Jan 2022 18:05:46 -0500 Subject: [PATCH 09/10] Added jsdoc & comments. --- packages/server/lib/util/args.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/server/lib/util/args.js b/packages/server/lib/util/args.js index 628f873cc0bd..106ec0fa0889 100644 --- a/packages/server/lib/util/args.js +++ b/packages/server/lib/util/args.js @@ -204,6 +204,12 @@ const sanitizeAndConvertNestedArgs = (str, argname) => { } } +/** + * Parses the '--spec' cli parameter to return an array of valid patterns. + * + * @param {Strng} pattern pattern to parse + * @returns Array of patterns + */ const parseSpecArgv = (pattern) => { const TOKENS = { OPEN: ['{', '['], @@ -230,7 +236,7 @@ const parseSpecArgv = (pattern) => { } /** - * Sanitizes a path from leftover commas. + * Sanitizes a path's leftover commas. * * @param {String} path * @returns String From 108315bfe7be543737e90940c4e4480c6cfabebc Mon Sep 17 00:00:00 2001 From: Matt Henkes Date: Fri, 14 Jan 2022 14:29:36 -0600 Subject: [PATCH 10/10] pushing trivial update to kick off build --- packages/server/lib/util/args.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/lib/util/args.js b/packages/server/lib/util/args.js index 106ec0fa0889..088f4eac790a 100644 --- a/packages/server/lib/util/args.js +++ b/packages/server/lib/util/args.js @@ -221,7 +221,7 @@ const parseSpecArgv = (pattern) => { const hasComma = pattern.includes(',') /** - * Slice & mutate a string. + * Slice and mutate a string. * * @param {String} str String to slice & mutate * @param {Number} end Index to slice to