diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8d33d0b..30afa6a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -88,9 +88,8 @@ jobs: affects_cypress: ${{ steps.affects_plugins.outputs.cypress }} affects_jest: ${{ steps.affects_plugins.outputs.jest }} - cypress_integration_tests: - name: "Cypress Integration Tests: Cypress ${{ matrix.cypress }} + Node ${{ matrix.node }}" - # FIXME: also test on Windows + cypress_linux_integration_tests: + name: "Cypress ${{ matrix.cypress }} Linux Node ${{ matrix.node }} Integration Tests" runs-on: ubuntu-latest timeout-minutes: 60 needs: @@ -114,6 +113,7 @@ jobs: - "12.10" - "12.14" - "12.15" + - "12.16" steps: - uses: actions/checkout@v3 @@ -126,12 +126,38 @@ jobs: uses: actions/cache@v3 with: path: ~/.cache/Cypress - key: cypress-${{ runner.os }}-node${{ matrix.node }}-cypress${{ matrix.cypress }}-modules-${{ hashFiles('yarn.lock') }} + key: cypress-${{ runner.os }}-node${{ matrix.node }}-cypress${{ matrix.cypress }} - id: install + env: + CYPRESS_INSTALL_BINARY: "0" run: yarn install --immutable + - uses: actions/download-artifact@v3 + with: + path: .artifacts + + - name: Install pre-built plugin packages + env: + CYPRESS_INSTALL_BINARY: "0" + run: | + curl -Lo jq https://github.com/jqlang/jq/releases/download/jq-1.6/jq-linux64 + chmod +x jq + cat package.json \ + | ./jq '. + {"resolutions": (.resolutions + { + "@unflakable/cypress-plugin": "file:./.artifacts/cypress-plugin/package.tgz", + "@unflakable/jest-plugin": "file:./.artifacts/jest-plugin/package.tgz", + "@unflakable/js-api": "file:./.artifacts/js-api/package.tgz" + })}' > package-new.json + mv package-new.json package.json + yarn install --no-immutable + + - name: Build test dependencies + run: yarn build:plugins-common && yarn build:cypress-tests + - name: Set Cypress version + env: + CYPRESS_INSTALL_BINARY: "0" run: | yarn set resolution "cypress@npm:10 - 12" ${{ matrix.cypress }} grep --after-context=1 "^\".*cypress.*" yarn.lock @@ -139,8 +165,6 @@ jobs: - name: Install Cypress binary run: yarn workspace cypress-integration exec cypress install - - run: yarn build:plugins && yarn build:cypress-tests - - name: Test env: # Enable debug logs within the Jest tests that run Cypress. WARNING: these are very @@ -163,11 +187,110 @@ jobs: fi UNFLAKABLE_API_KEY=${{ secrets.UNFLAKABLE_API_KEY }} \ yarn workspace cypress-integration test \ - --reporters @unflakable/jest-plugin/dist/reporter \ - --runner @unflakable/jest-plugin/dist/runner + --reporters @unflakable/jest-plugin/dist/reporter \ + --runner @unflakable/jest-plugin/dist/runner + + cypress_windows_integration_tests: + name: "Cypress ${{ matrix.cypress }} Windows Node ${{ matrix.node }} Integration Tests" + runs-on: windows-2019 + # Cypress on Windows is slowwww... + timeout-minutes: 90 + needs: + # Don't incur the cost of the test matrix if the basic build fails. + - check + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || needs.check.outputs.affects_cypress == 'true' + strategy: + fail-fast: false + matrix: + node: + - 16 + - 18 + - 20 + cypress: + # FIXME: support earlier versions + #- "10.0" + #- "10.11" + #- "11.0" + - "11.2" + - "12.0" + - "12.10" + - "12.14" + - "12.15" + - "12.16" + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + cache: yarn + + - name: Get AppData directory + run: echo ("LOCALAPPDATA=" + $env:LOCALAPPDATA) >> $env:GITHUB_ENV + + - name: Cache Cypress binary + uses: actions/cache@v3 + with: + path: ${{ env.LOCALAPPDATA }}\Cypress\Cache + key: cypress-${{ runner.os }}-node${{ matrix.node }}-cypress${{ matrix.cypress }} + + - id: install + env: + CYPRESS_INSTALL_BINARY: "0" + run: | + yarn install --immutable + + - uses: actions/download-artifact@v3 + with: + path: .artifacts + + - name: Install pre-built plugin packages + env: + CYPRESS_INSTALL_BINARY: "0" + shell: bash + run: | + curl -Lo jq.exe https://github.com/jqlang/jq/releases/download/jq-1.6/jq-win64.exe + cat package.json \ + | ./jq.exe '. + {"resolutions": (.resolutions + { + "@unflakable/cypress-plugin": "file:./.artifacts/cypress-plugin/package.tgz", + "@unflakable/jest-plugin": "file:./.artifacts/jest-plugin/package.tgz", + "@unflakable/js-api": "file:./.artifacts/js-api/package.tgz" + })}' > package-new.json + mv package-new.json package.json + yarn install --no-immutable + + - name: Build test dependencies + run: yarn build:plugins-common && yarn build:cypress-tests + + - name: Set Cypress version + env: + CYPRESS_INSTALL_BINARY: "0" + run: | + yarn set resolution "cypress@npm:10 - 12" ${{ matrix.cypress }} + Select-String -Pattern '^".*cypress.*' -Path yarn.lock -Context 0,1 + + - name: Install Cypress binary + run: yarn workspace cypress-integration exec cypress install + + - name: Test + env: + # Enable debug logs within the Jest tests that run Cypress. WARNING: these are very + # verbose but are useful for seeing the raw chalk terminal codes. + # DEBUG: unflakable:* + + # Enable debug logs within the Cypress plugin. + TEST_DEBUG: "unflakable:*,cypress:server:*" + + # Enable terminal colors for debug() output. + DEBUG_COLORS: "1" + + # Make chalk emit TTY colors. + FORCE_COLOR: "1" + # FIXME: use Jest plugin once it works on Windows + run: yarn workspace cypress-integration test jest_integration_tests: - name: "Jest Integration Tests: Jest ${{ matrix.jest }} + Node ${{ matrix.node }}" + name: "Jest ${{ matrix.jest }} Linux Node ${{ matrix.node }} Integration Tests" # FIXME: also test on Windows runs-on: ubuntu-latest timeout-minutes: 20 @@ -209,6 +332,8 @@ jobs: - "25.3" - "25.2" - "25.1" + env: + CYPRESS_INSTALL_BINARY: "0" steps: - uses: actions/checkout@v3 @@ -221,6 +346,22 @@ jobs: - id: install run: yarn install --immutable + - uses: actions/download-artifact@v3 + with: + path: .artifacts + + - name: Install pre-built plugin packages + run: | + curl -Lo jq https://github.com/jqlang/jq/releases/download/jq-1.6/jq-linux64 + chmod +x jq + cat package.json \ + | ./jq '. + {"resolutions": (.resolutions + { + "@unflakable/jest-plugin": "file:./.artifacts/jest-plugin/package.tgz", + "@unflakable/js-api": "file:./.artifacts/js-api/package.tgz" + })}' > package-new.json + mv package-new.json package.json + yarn install --no-immutable + # Since Jest is composed of many packages, we need to make sure that they're all using the # same major and minor versions. Otherwise, NPM will keep the newer minor versions since # they're semver-compatible. This script iteratively installs the lowest compatible semver @@ -235,8 +376,6 @@ jobs: if: ${{ startsWith(matrix.jest, '25.') }} run: yarn set resolution "chalk@npm:^3.0.0 || ^4.0.0" 3.0 - - run: yarn build:plugins - - name: Test env: DEBUG: unflakable:* diff --git a/packages/cypress-plugin/package.json b/packages/cypress-plugin/package.json index 7f4d6f7..1dba670 100644 --- a/packages/cypress-plugin/package.json +++ b/packages/cypress-plugin/package.json @@ -8,7 +8,7 @@ "bugs": "https://github.com/unflakable/unflakable-javascript/issues", "homepage": "https://unflakable.com", "license": "MIT", - "version": "0.1.2", + "version": "0.2.0", "exports": { ".": { "types": "./dist/index.d.ts", @@ -35,9 +35,9 @@ "README.md", "dist/**/*.js", "dist/**/*.mjs", - "dist/index.d.ts", - "dist/config-wrapper.d.ts", "dist/config-wrapper-sync.d.ts", + "dist/config-wrapper.d.ts", + "dist/index.d.ts", "dist/reporter.d.ts", "dist/skip-tests.d.ts" ], @@ -45,6 +45,7 @@ "cypress-unflakable": "./dist/main.js" }, "dependencies": { + "@stdlib/utils-convert-path": "^0.0.8", "@unflakable/js-api": "workspace:^", "ansi-styles": "^4.3.0", "chalk": "^4.1.0", @@ -82,7 +83,9 @@ "cypress": "10 - 12", "jest": "^29.5.0", "jest-environment-node": "^29.5.0", + "rimraf": "^5.0.1", "rollup": "^3.21.1", + "rollup-plugin-dts": "^5.3.0", "typescript": "^4.9.5", "widest-line": "3.1.0" }, @@ -90,10 +93,8 @@ "cypress": "11.2 - 12" }, "scripts": { - "build": "yarn clean && tsc --noEmit && tsc --noEmit -p src && yarn build:cjs && yarn build:esm", - "build:cjs": "rollup --config --dir dist && rollup --config --input src/config-wrapper-sync.ts --file dist/config-wrapper-sync.js --chunkFileNames \"[name]-[hash]-sync.js\"", - "build:esm": "rollup --config --input src/config-wrapper.ts --file dist/config-wrapper.mjs --format es --chunkFileNames \"[name]-[hash].mjs\"", - "clean": "rm -rf dist/", + "build": "yarn clean && tsc --noEmit && tsc --noEmit -p src && rollup --config", + "clean": "rimraf dist/", "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --useStderr --verbose" } } diff --git a/packages/cypress-plugin/rollup.config.mjs b/packages/cypress-plugin/rollup.config.mjs index 32bb23a..f7b899a 100644 --- a/packages/cypress-plugin/rollup.config.mjs +++ b/packages/cypress-plugin/rollup.config.mjs @@ -1,64 +1,116 @@ // Copyright (c) 2023 Developer Innovations, LLC import pluginCommonJs from "@rollup/plugin-commonjs"; +import pluginDts from "rollup-plugin-dts"; import pluginJson from "@rollup/plugin-json"; import pluginNodeResolve from "@rollup/plugin-node-resolve"; import pluginTypescript from "@rollup/plugin-typescript"; +import path from "path"; + +/** + * Bundle the internal @unflakable/plugins-common package, along with dependencies used by + * vendored Cypress code that Cypress itself also bundles (i.e., doesn't list in its public + * package.json as deps) in dist/, but leave most other imported packages as external. Internal + * modules begin with `.` or `/`. We don't include `term-size` here because it depends on a + * bundled vendor/ directory that rollup doesn't include. + * + * @type {import("rollup").IsExternal} + */ +const isExternal = (id) => + !id.startsWith(".") && + !path.isAbsolute(id) && + !id.startsWith(`src/`) && + !id.startsWith("@unflakable/plugins-common/") && + ![ + // Avoid having skip-tests depend on @unflakable/js-api, which could pull in Node dependencies + // that Webpack v5 doesn't bundle by default. It's also unclear which versions of Webpack + // support sub-path exports from package.json like this. To avoid requiring any changes to + // the user's Webpack config, we try to make skip-tests self-contained and easy to import. + "@unflakable/js-api/consts", + "@unflakable/plugins-common", + "widest-line", + ].includes(id); + +const plugins = [ + pluginCommonJs(), + pluginJson(), + pluginNodeResolve({ preferBuiltins: true }), + pluginTypescript({ tsconfig: "src/tsconfig.json" }), +]; + +const treeshake = { + // Assume internal modules do not have side effects when they're imported. This helps remove + // unnecessary require()'s from the transpiled code. + moduleSideEffects: (id, external) => external, +}; /** * @type {import("rollup").NormalizedInputOptions} */ -export default { - // NB: We exclude src/config-wrapper.ts since that needs to be compiled as an ESM target in - // order to be able to import user cypress.config.js files for projects that use ESM. The reason - // is that Cypress loads our config file and expects the default export to be the config - // object, which requires us to load the user config file when our script loads. Dynamic - // import() of ESM (require() can't import ESM modules) is async, but CommonJS doesn't support - // await at the top level of a file (ESM does). To summarize: (1) code that's imported by Cypress - // (with the exception of the config file, due to (2)) should be CommonJS, while (2) code that - // imports user code should be ESM so that it supports both CommonJS and ESM user code. - input: [ - "src/index.ts", - "src/main.ts", - "src/reporter.ts", - "src/skip-tests.ts", - ], - output: { - format: "cjs", - /** - * @param {import("rollup").PreRenderedChunk} chunk - * @returns {string} - */ - banner: (chunk) => (chunk.name === "main" ? "#!/usr/bin/env node" : ""), +export default [ + { + // NB: We exclude src/config-wrapper.ts since that needs to be compiled as an ESM target in + // order to be able to import user cypress.config.js files for projects that use ESM. The reason + // is that Cypress loads our config file and expects the default export to be the config + // object, which requires us to load the user config file when our script loads. Dynamic + // import() of ESM (require() can't import ESM modules) is async, but CommonJS doesn't support + // await at the top level of a file (ESM does). To summarize: (1) code that's imported by Cypress + // (with the exception of the config file, due to (2)) should be CommonJS, while (2) code that + // imports user code should be ESM so that it supports both CommonJS and ESM user code. + input: [ + "src/config-wrapper-sync.ts", + "src/index.ts", + "src/main.ts", + "src/reporter.ts", + "src/skip-tests.ts", + ], + output: { + format: "cjs", + dir: "dist", + /** + * @param {import("rollup").PreRenderedChunk} chunk + * @returns {string} + */ + banner: (chunk) => (chunk.name === "main" ? "#!/usr/bin/env node" : ""), + }, + external: isExternal, + plugins, + treeshake, }, - // Bundle the internal @unflakable/plugins-common package, along with dependencies used by - // vendored Cypress code that Cypress itself also bundles (i.e., doesn't list in its public - // package.json as deps) in dist/, but leave most other imported packages as an external. Internal - // modules begin with `.` or `/`. We don't include `term-size` here because it depends on a - // bundled vendor/ directory that rollup doesn't include. - external: (id) => - !id.startsWith(".") && - !id.startsWith("/") && - !id.startsWith("src/") && - !id.startsWith("@unflakable/plugins-common/") && - ![ - // Avoid having skip-tests depend on @unflakable/js-api, which could pull in Node dependencies - // that Webpack v5 doesn't bundle by default. It's also unclear which versions of Webpack - // support sub-path exports from package.json like this. To avoid requiring any changes to - // the user's Webpack config, we try to make skip-tests self-contained and easy to import. - "@unflakable/js-api/consts", - "@unflakable/plugins-common", - "widest-line", - ].includes(id), - plugins: [ - pluginCommonJs(), - pluginJson(), - pluginNodeResolve({ preferBuiltins: true }), - pluginTypescript({ tsconfig: "src/tsconfig.json" }), - ], - treeshake: { - // Assume internal modules do not have side effects when they're imported. This helps remove - // unnecessary require()'s from the transpiled code. - moduleSideEffects: (id, external) => external, + { + input: "src/config-wrapper.ts", + output: { + chunkFileNames: "[name]-[hash].mjs", + file: "dist/config-wrapper.mjs", + format: "es", + }, + external: isExternal, + plugins, + treeshake, }, -}; + // Rollup types so that UnflakableConfig from @unflakable/plugins-common is bundled. This package + // doesn't get published separately, so our public types shouldn't reference it. + { + input: [ + // NB: This should include every exported .d.ts from package.json. + "dist/config-wrapper-sync.d.ts", + "dist/config-wrapper.d.ts", + "dist/index.d.ts", + "dist/reporter.d.ts", + "dist/skip-tests.d.ts", + ], + output: { + dir: ".", + entryFileNames: "[name].d.ts", + format: "cjs", + }, + external: isExternal, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + plugins: [ + pluginNodeResolve({ preferBuiltins: true }), + pluginDts({ + respectExternal: true, + }), + ], + }, +]; diff --git a/packages/cypress-plugin/src/load-user-config.ts b/packages/cypress-plugin/src/load-user-config.ts index aee923e..48a26d1 100644 --- a/packages/cypress-plugin/src/load-user-config.ts +++ b/packages/cypress-plugin/src/load-user-config.ts @@ -7,6 +7,7 @@ import { ENV_VAR_USER_CONFIG_PATH, } from "./config-env-vars"; import path from "path"; +import { pathToFileURL } from "url"; const debug = _debug("unflakable:load-user-config"); @@ -55,7 +56,8 @@ export const loadUserConfig = async (): Promise< // for ESM projects. debug(`require() failed; attempting dynamic import(): ${e as string}`); const config = (await import( - ENV_VAR_USER_CONFIG_PATH.value as string + // Windows paths don't work unless we convert them to file:// URLs. + pathToFileURL(ENV_VAR_USER_CONFIG_PATH.value as string).href )) as LoadedConfig; return config.default ?? config; } diff --git a/packages/cypress-plugin/src/plugin.ts b/packages/cypress-plugin/src/plugin.ts index d5beec6..ad9b93a 100644 --- a/packages/cypress-plugin/src/plugin.ts +++ b/packages/cypress-plugin/src/plugin.ts @@ -16,9 +16,10 @@ import { commitOverride, isTestQuarantined, normalizeTestName, + toPosix, UnflakableConfig, } from "@unflakable/plugins-common"; -import { require, userAgent } from "./utils"; +import { printWarning, require, userAgent } from "./utils"; import { configureMochaReporter } from "./reporter-config"; import { color, @@ -393,7 +394,7 @@ export class UnflakableCypressPlugin { options.autoSupportFile !== false ) { const updatedSupportFile = await this.generateSkipTestsSupportFile( - config.supportFile + config ); this.supportFilePath = updatedSupportFile; config.supportFile = updatedSupportFile; @@ -411,32 +412,37 @@ export class UnflakableCypressPlugin { }; private generateSkipTestsSupportFile = async ( - configuredSupportFile: string | false + config: Cypress.PluginConfigOptions ): Promise => { const debug = baseDebug.extend("generateSkipTestsSupportFile"); // We have to write a temporary Cypress support file on the fly because Cypress can't load // support files inside of node_modules. See https://github.com/cypress-io/cypress/issues/23616. // Once Cypress loads our support file, it's fine to load other modules that live in - // node_modules. + // node_modules. Cypress also requires the support file to be located within the project root + // due to https://github.com/cypress-io/cypress/issues/8599#issuecomment-1290526416. + const tmpdir = path.join(config.projectRoot, ".unflakable-tmp"); + await fs.mkdir(tmpdir, { recursive: true }); + const supportFilePath = (await promisify(tmpName)({ - prefix: "unflakable-cypress-support-file", - })) + ".js"; + prefix: "cypress-support-file", + tmpdir, + })) + ".cjs"; debug(`Using temp path \`${supportFilePath}\` for Cypress support file`); const skipTestsPath = require.resolve(SKIP_TESTS_MODULE); debug(`Support file will load skip-tests from ${skipTestsPath}`); - if (configuredSupportFile !== false) { - debug(`Will load existing support file from ${configuredSupportFile}`); + if (config.supportFile !== false) { + debug(`Will load existing support file from ${config.supportFile}`); } const supportFileContents = ` require(${JSON.stringify(skipTestsPath)}).registerMochaInstrumentation(); ${ - configuredSupportFile !== false - ? `require(${JSON.stringify(configuredSupportFile)});` + config.supportFile !== false + ? `require(${JSON.stringify(config.supportFile)});` : "" } `; @@ -560,6 +566,17 @@ ${ const debug = baseDebug.extend("afterRun"); debug("Received afterRun event"); + if (this.supportFilePath !== null) { + debug(`Deleting temp support file ${this.supportFilePath}`); + await fs.unlink(this.supportFilePath).catch((e) => { + printWarning( + `Failed to delete temp support file ${ + this.supportFilePath as string + }: ${e as string}` + ); + }); + } + if (results.status === "finished") { renderSummaryTable(results); @@ -568,7 +585,9 @@ ${ // compile (e.g., due to Webpack errors). (tests ?? []) .map((test): TestRunRecord => { - const filename = path.relative(this.repoRoot, spec.absolute); + const filename = toPosix( + path.relative(this.repoRoot, spec.absolute) + ); const isQuarantined = this.manifest !== null && this.unflakableConfig.quarantineMode !== "no_quarantine" && @@ -597,11 +616,6 @@ ${ await this.uploadResults(results, testRuns); } } - - if (this.supportFilePath !== null) { - debug(`Deleting temp support file ${this.supportFilePath}`); - await fs.unlink(this.supportFilePath); - } }; private onBeforeSpec = (spec: Cypress.Spec): void => { diff --git a/packages/cypress-plugin/src/reporter.ts b/packages/cypress-plugin/src/reporter.ts index 5e24966..8e3551a 100644 --- a/packages/cypress-plugin/src/reporter.ts +++ b/packages/cypress-plugin/src/reporter.ts @@ -13,6 +13,7 @@ import { import { TestSuiteManifest } from "@unflakable/js-api"; import { isTestQuarantined, + toPosix, UnflakableConfig, } from "@unflakable/plugins-common"; import path from "path"; @@ -277,7 +278,7 @@ export default class UnflakableSpecReporter extends reporters.Base { private readonly config: UnflakableConfig; private readonly manifest: TestSuiteManifest | null; - private readonly testFilename: string | null; + private readonly posixTestFilename: string | null; private indents = 0; @@ -316,17 +317,19 @@ export default class UnflakableSpecReporter extends reporters.Base { options.reporterOptions as ReporterConfig; this.config = config; this.manifest = manifest ?? null; - this.testFilename = + this.posixTestFilename = runner.suite.file !== undefined - ? path.relative( - repoRoot, - // Absolute path of the spec file. - path.join(projectRoot, runner.suite.file) + ? toPosix( + path.relative( + repoRoot, + // Absolute path of the spec file. + path.join(projectRoot, runner.suite.file) + ) ) : // This can happen if a file has no tests, which should be ok since none of the event // handlers should get called. null; - debug(`Repo-relative test filename: ${this.testFilename ?? "null"}`); + debug(`Repo-relative test filename: ${this.posixTestFilename ?? "null"}`); runner.on(Runner.constants.EVENT_RUN_BEGIN, this.onRunBegin.bind(this)); runner.once(Runner.constants.EVENT_RUN_END, this.onRunEnd.bind(this)); @@ -365,18 +368,18 @@ export default class UnflakableSpecReporter extends reporters.Base { return false; } - if (this.testFilename === null) { + if (this.posixTestFilename === null) { throw new Error("Suite has no `file` attribute"); } const isQuarantined = this.manifest !== null && - isTestQuarantined(this.manifest, this.testFilename, titlePath); + isTestQuarantined(this.manifest, this.posixTestFilename, titlePath); debug( `Test is ${isQuarantined ? "" : "NOT "}quarantined: ${JSON.stringify( titlePath - )} in file ${this.testFilename}` + )} in file ${this.posixTestFilename}` ); return isQuarantined; @@ -845,12 +848,12 @@ export default class UnflakableSpecReporter extends reporters.Base { // warning in this case rather than treating the test as flaky or potentially quarantining // it. if (currentTestRetry(test) > 0) { - if (this.testFilename === null) { + if (this.posixTestFilename === null) { throw new Error("Suite has no `file` attribute"); } printWarning( - `test ${titlePathJson} in file ${this.testFilename} was pending (skipped) during retry` + `test ${titlePathJson} in file ${this.posixTestFilename} was pending (skipped) during retry` ); } diff --git a/packages/cypress-plugin/src/skip-tests.ts b/packages/cypress-plugin/src/skip-tests.ts index 01ac7f5..18531b2 100644 --- a/packages/cypress-plugin/src/skip-tests.ts +++ b/packages/cypress-plugin/src/skip-tests.ts @@ -27,6 +27,7 @@ import { CYPRESS_ENV_VAR_MANIFEST, CYPRESS_ENV_VAR_REPO_ROOT, } from "./cypress-env-vars"; +import convertPath from "@stdlib/utils-convert-path"; const debug = _debug("unflakable:skip-tests"); @@ -110,7 +111,15 @@ const instrumentTestFn = ( config: Cypress.TestConfigOverrides | undefined, fn: Mocha.Func | undefined ): Mocha.Test => { - const testFilename = path.relative(repoRoot, Cypress.spec.absolute); + // NB: On Windows, Cypress.spec.absolute uses a mixed path convention (e.g., C:/foo/bar), + // while repoRoot uses a Windows path convention (e.g., C:\foo\bar). In order for + // path.relative to work correctly, we have to convert both to POSIX conventions (e.g., + // /c/foo/bar). This is because path-browserify doesn't have win32 support: + // https://github.com/browserify/path-browserify/issues/1. + const testFilename = path.relative( + convertPath(repoRoot, "posix"), + convertPath(Cypress.spec.absolute, "posix") + ); const titlePath = [...suiteStack, title]; const isQuarantined = manifest !== null && diff --git a/packages/cypress-plugin/test/integration-common/package.json b/packages/cypress-plugin/test/integration-common/package.json index ea472b5..a852526 100644 --- a/packages/cypress-plugin/test/integration-common/package.json +++ b/packages/cypress-plugin/test/integration-common/package.json @@ -11,12 +11,13 @@ "@rollup/plugin-node-resolve": "^15.0.2", "@rollup/plugin-typescript": "^11.1.1", "@unflakable/plugins-common": "workspace:^", + "rimraf": "^5.0.1", "rollup": "^3.21.1", "rollup-plugin-dts": "^5.3.0", "typescript": "^4.9.5" }, "scripts": { "build": "yarn clean && tsc --noEmit && tsc --noEmit -p src && rollup --config", - "clean": "rm -rf dist/" + "clean": "rimraf dist/" } } diff --git a/packages/cypress-plugin/test/integration-common/rollup.config.mjs b/packages/cypress-plugin/test/integration-common/rollup.config.mjs index c7801cf..1474adc 100644 --- a/packages/cypress-plugin/test/integration-common/rollup.config.mjs +++ b/packages/cypress-plugin/test/integration-common/rollup.config.mjs @@ -1,5 +1,6 @@ // Copyright (c) 2023 Developer Innovations, LLC +import path from "path"; import pluginCommonJs from "@rollup/plugin-commonjs"; import pluginNodeResolve from "@rollup/plugin-node-resolve"; import pluginTypescript from "@rollup/plugin-typescript"; @@ -14,7 +15,7 @@ import pluginDts from "rollup-plugin-dts"; */ const isExternal = (id) => !id.startsWith(".") && - !id.startsWith("/") && + !path.isAbsolute(id) && !id.startsWith("src/") && !["@unflakable/plugins-common"].includes(id); diff --git a/packages/cypress-plugin/test/integration/package.json b/packages/cypress-plugin/test/integration/package.json index 43c3ea0..15c65f1 100644 --- a/packages/cypress-plugin/test/integration/package.json +++ b/packages/cypress-plugin/test/integration/package.json @@ -3,7 +3,6 @@ "private": true, "devDependencies": { "@types/jest": "^29.5.2", - "@unflakable/cypress-plugin": "workspace:^", "@unflakable/jest-plugin": "workspace:^", "@unflakable/js-api": "workspace:^", "@unflakable/plugins-common": "workspace:^", diff --git a/packages/cypress-plugin/test/integration/src/parse-output.ts b/packages/cypress-plugin/test/integration/src/parse-output.ts index d8e31f9..17ad736 100644 --- a/packages/cypress-plugin/test/integration/src/parse-output.ts +++ b/packages/cypress-plugin/test/integration/src/parse-output.ts @@ -2,11 +2,7 @@ /* eslint-disable no-control-regex */ -import { - specProjectPath, - TEST_SPEC_STUBS, - TestCaseParams, -} from "./run-test-case"; +import { specPattern, TEST_SPEC_STUBS, TestCaseParams } from "./run-test-case"; export type RunStarting = { specs: string[]; @@ -185,8 +181,6 @@ const parseRunStarting = ( linesRead: number; runStarting: RunStarting; } => { - const { specNameStubs, testMode } = params; - const runStartingLine = stdoutLines.findIndex( (line) => line === "\x1B[0m (\x1B[4m\x1B[1mRun Starting\x1B[22m\x1B[24m)\x1B[0m" @@ -213,7 +207,12 @@ const parseRunStarting = ( expect(tableEntries["Cypress"]).toMatch(/^[0-9]+\.[0-9]+\.[0-9]+$/); expect(tableEntries["Browser"]).toMatch( - /^Chrome [0-9]+ \x1B\[90m\(headless\)\x1B\[39m$/ + new RegExp( + `^${ + // Chrome is slow to launch on Windows. + process.platform === "win32" ? "Edge" : "Chrome" + } [0-9]+ \x1B\\[90m\\(headless\\)\x1B\\[39m$` + ) ); expect(tableEntries["Node Version"]).toMatch( new RegExp( @@ -224,13 +223,7 @@ const parseRunStarting = ( )} \\x1B\\[90m\\(.+\\)\\x1B\\[39m$` ) ); - expect(tableEntries["Searched"]).toBe( - specNameStubs !== undefined && specNameStubs.length > 0 - ? specNameStubs.map((stub) => specProjectPath(params, stub)).join(", ") - : testMode === "component" - ? "**/*.cy.{js,jsx,ts,tsx}" - : `cypress/e2e/**/*.cy.{js,jsx,ts,tsx}` - ); + expect(tableEntries["Searched"]).toBe(specPattern(params)); const parsedSpecsLine = tableEntries["Specs"].match( /^([0-9]+) found \((.*)\)$/ @@ -942,7 +935,7 @@ const parseSummaryTable = ( .filter((line) => !TABLE_BETWEEN_ROWS_BORDER_LINE.test(line)) .map((line): SummaryRow => { const parsedRowOrNull = line.match( - /^\x1B\[90m +│\x1B\[39m \x1B\[3(?:2m(?✔)|1m(?✖))\x1B\[39m +\x1B\[0m(?.+?)\x1B\[0m +\x1B\[90m(?.+?)\x1B\[39m +\x1B\[(?:0m(?[0-9]+)\x1B\[0m|90m-\x1B\[39m) +\x1B\[(?:32m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:31m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:33m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:35m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:36m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:34m(?[0-9]+)|90m-)\x1B\[39m \x1B\[90m│\x1B\[39m$/ + /^\x1B\[90m +│\x1B\[39m \x1B\[3(?:2m(?[✔√])|1m(?[✖×]))\x1B\[39m +\x1B\[0m(?.+?)\x1B\[0m +\x1B\[90m(?.+?)\x1B\[39m +\x1B\[(?:0m(?[0-9]+)\x1B\[0m|90m-\x1B\[39m) +\x1B\[(?:32m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:31m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:33m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:35m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:36m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:34m(?[0-9]+)|90m-)\x1B\[39m \x1B\[90m│\x1B\[39m$/ ); expect( @@ -991,7 +984,7 @@ const parseSummaryTable = ( const summaryTotalsLine = stdoutLinesAfterLastSpecTable[runFinishedLine + 5 + numTableLines + 1]; const parsedSummaryTotalsOrNull = summaryTotalsLine.match( - /^\x1B\[90m +\x1B\[39m \x1B\[3(?:2m(?✔)|1m(?✖))\x1B\[39m +\x1B\[3(:?2mAll specs passed!|1m(?[0-9]+) of (?[0-9]+) failed \([0-9]+%\))\x1B\[39m +\x1B\[90m(?.+?)\x1B\[39m +\x1B\[(?:0m(?[0-9]+)|90m-)\x1B\[0m +\x1B\[(?:32m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:31m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:33m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:35m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:36m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:34m(?[0-9]+)|90m-)\x1B\[39m \x1B\[90m \x1B\[39m$/ + /^\x1B\[90m +\x1B\[39m \x1B\[3(?:2m(?[✔√])|1m(?[✖×]))\x1B\[39m +\x1B\[3(:?2mAll specs passed!|1m(?[0-9]+) of (?[0-9]+) failed \([0-9]+%\))\x1B\[39m +\x1B\[90m(?.+?)\x1B\[39m +\x1B\[(?:0m(?[0-9]+)|90m-)\x1B\[0m +\x1B\[(?:32m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:31m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:33m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:35m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:36m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:34m(?[0-9]+)|90m-)\x1B\[39m \x1B\[90m \x1B\[39m$/ ); expect( parsedSummaryTotalsOrNull, @@ -1074,7 +1067,7 @@ const parsePluginDisabledSummaryTable = ( .filter((line) => !TABLE_BETWEEN_ROWS_BORDER_LINE.test(line)) .map((line): SummaryRow => { const parsedRowOrNull = line.match( - /^\x1B\[90m +│\x1B\[39m \x1B\[3(?:2m(?✔)|1m(?✖))\x1B\[39m +\x1B\[0m(?.+?)\x1B\[0m +\x1B\[90m(?.+?)\x1B\[39m +\x1B\[(?:0m(?[0-9]+)\x1B\[0m|90m-\x1B\[39m) +\x1B\[(?:32m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:31m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:36m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:34m(?[0-9]+)|90m-)\x1B\[39m \x1B\[90m│\x1B\[39m$/ + /^\x1B\[90m +│\x1B\[39m \x1B\[3(?:2m(?[✔√])|1m(?[✖×]))\x1B\[39m +\x1B\[0m(?.+?)\x1B\[0m +\x1B\[90m(?.+?)\x1B\[39m +\x1B\[(?:0m(?[0-9]+)\x1B\[0m|90m-\x1B\[39m) +\x1B\[(?:32m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:31m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:36m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:[39]4m(?[0-9]+)|90m-)\x1B\[39m \x1B\[90m│\x1B\[39m$/ ); expect( @@ -1116,7 +1109,7 @@ const parsePluginDisabledSummaryTable = ( const summaryTotalsLine = stdoutLinesAfterLastSpecTable[runFinishedLine + 5 + numTableLines + 1]; const parsedSummaryTotalsOrNull = summaryTotalsLine.match( - /^\x1B\[90m +\x1B\[39m \x1B\[3(?:2m(?✔)|1m(?✖))\x1B\[39m +\x1B\[3(:?2mAll specs passed!|1m(?[0-9]+) of (?[0-9]+) failed \([0-9]+%\))\x1B\[39m +\x1B\[90m(?.+?)\x1B\[39m +\x1B\[(?:0m(?[0-9]+)|90m-)\x1B\[0m +\x1B\[(?:32m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:31m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:36m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:34m(?[0-9]+)|90m-)\x1B\[39m \x1B\[90m \x1B\[39m$/ + /^\x1B\[90m +\x1B\[39m \x1B\[3(?:2m(?[✔√])|1m(?[✖×]))\x1B\[39m +\x1B\[3(:?2mAll specs passed!|1m(?[0-9]+) of (?[0-9]+) failed \([0-9]+%\))\x1B\[39m +\x1B\[90m(?.+?)\x1B\[39m +\x1B\[(?:0m(?[0-9]+)|90m-)\x1B\[0m +\x1B\[(?:32m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:31m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:36m(?[0-9]+)|90m-)\x1B\[39m +\x1B\[(?:[39]4m(?[0-9]+)|90m-)\x1B\[39m \x1B\[90m \x1B\[39m$/ ); expect( parsedSummaryTotalsOrNull, diff --git a/packages/cypress-plugin/test/integration/src/run-test-case.ts b/packages/cypress-plugin/test/integration/src/run-test-case.ts index e5ab45a..2a66fc4 100644 --- a/packages/cypress-plugin/test/integration/src/run-test-case.ts +++ b/packages/cypress-plugin/test/integration/src/run-test-case.ts @@ -132,6 +132,9 @@ export type TestCaseParams = { testMode: TestMode; }; +export const projectPath = (params: TestCaseParams): string => + path.join("..", params.project); + export const specFilename = ( params: TestCaseParams, specNameStub: string @@ -145,6 +148,27 @@ export const specProjectPath = ( specNameStub: string ): string => `cypress/${params.testMode}/${specFilename(params, specNameStub)}`; +export const specPattern = (params: TestCaseParams): string => { + const { specNameStubs, testMode } = params; + return specNameStubs !== undefined && specNameStubs.length > 0 + ? // Cypress doesn't convert to relative path on Windows due to hard-coding a forward slash + // into the path. See: + // https://github.com/cypress-io/cypress/blob/3d0a2b406115db292130df774348c4f1fd4a3240/packages/server/lib/modes/run.ts#L52 + specNameStubs + .map((stub) => + process.platform === "win32" + ? `${path.resolve(projectPath(params))}\\${specProjectPath( + params, + stub + ).replaceAll("/", "\\")}` + : specProjectPath(params, stub) + ) + .join(", ") + : testMode === "component" + ? "**/*.cy.{js,jsx,ts,tsx}" + : `cypress/e2e/**/*.cy.{js,jsx,ts,tsx}`; +}; + const specRepoPath = (params: TestCaseParams, specNameStub: string): string => params.expectedRepoRelativePathPrefix + specProjectPath(params, specNameStub); @@ -746,20 +770,19 @@ export const runTestCase = async ( multipleHookErrors, } = params; - const fetchMismatch = { error: undefined as unknown | undefined }; + const testError = { error: undefined as unknown | undefined }; const { unmatchedApiRequestEndpoint, unmatchedObjectStoreRequestEndpoint } = await addFetchMockExpectations(params, summaryTotals, (error) => { - if (fetchMismatch.error === undefined) { - fetchMismatch.error = error ?? new Error("undefined error"); + if (testError.error === undefined) { + testError.error = error ?? new Error("undefined error"); } else { console.error("Multiple failed fetch expectations", error); } }); - const projectPath = path.join("..", params.project); const configMockParams: CosmiconfigMockParams = { - searchFrom: path.resolve(projectPath), + searchFrom: path.resolve(projectPath(params)), searchResult: params.config !== null ? { @@ -773,7 +796,11 @@ export const runTestCase = async ( // in order to mock cosmiconfig for testing. Instead, we resolve the binary to an absolute path // using `yarn bin` and then invoke node directly. const cypressPluginBin = ( - await promisify(execFile)("yarn", ["bin", "cypress-unflakable"]) + await promisify(execFile)("yarn", ["bin", "cypress-unflakable"], { + cwd: projectPath(params), + // yarn.CMD isn't executable without a shell on Windows. + shell: process.platform === "win32", + }) ).stdout.trimEnd(); Object.entries(params.testEnvVars).forEach(([key, value]) => { @@ -808,9 +835,9 @@ export const runTestCase = async ( "--", // e2e/component `--${params.testMode}`, - // Chrome is faster than Electron, at least on Mac. + // Chrome is faster than Electron, at least on Mac. However, it's much slower on Windows. "--browser", - "chrome", + process.platform === "win32" ? "edge" : "chrome", ...(params.specNameStubs !== undefined ? [ "--spec", @@ -828,8 +855,6 @@ export const runTestCase = async ( const env = { ...params.envVars, DEBUG: process.env.TEST_DEBUG, - // Enable terminal colors for debug() output. - DEBUG_COLORS: "1", // Ensure Cypress prints output with TTY colors. FORCE_COLOR: "1", // NODE_OPTIONS: "--loader=testdouble", @@ -838,23 +863,32 @@ export const runTestCase = async ( UNFLAKABLE_API_BASE_URL: `http://localhost:${apiServer.port}`, [CONFIG_MOCK_ENV_VAR]: JSON.stringify(configMockParams), [GIT_MOCK_ENV_VAR]: JSON.stringify(params.git), + // Windows requires these environment variables to be propagated. + ...(process.platform === "win32" + ? { + APPDATA: process.env.APPDATA, + LOCALAPPDATA: process.env.LOCALAPPDATA, + TMP: process.env.TMP, + TEMP: process.env.TEMP, + } + : {}), }; debug( `Spawning test:\n args = %o\n environment = %o\n cwd = %s`, args, env, - projectPath + projectPath(params) ); const cypressChild = spawn("node", args, { - cwd: projectPath, + cwd: projectPath(params), env, }); const onOutput = ( name: string, - onLine: (line: string) => void, + onLine: (line: string, now: Date) => void, escapeDebugOutput: boolean ): ((data: Buffer) => void) => { const debugExt = debug.extend(name); @@ -865,12 +899,13 @@ export const runTestCase = async ( // Don't eat the last line of output. cypressChild.on("exit", () => { if (pending.s !== "") { - onLine(pending.s); + onLine(pending.s, new Date()); debugExt(escapeDebugOutput ? JSON.stringify(pending.s) : pending.s); } }); return (data: Buffer): void => { + const now = new Date(); // In case data terminates in the middle of a Unicode sequence, we need to use a stateful // TextDecoder with `stream: true`. Otherwise, invalid UTF-8 sequences at the end get // converted to 0xFFFD, which breaks the tests non-deterministically (i.e., makes them flaky). @@ -880,7 +915,7 @@ export const runTestCase = async ( // partial line that we want to defer until the next call. lines.slice(0, lines.length - 1).forEach((line, idx) => { const lineWithPending = idx === 0 ? pending.s + line : line; - onLine(lineWithPending); + onLine(lineWithPending, now); debugExt( escapeDebugOutput ? JSON.stringify(lineWithPending) : lineWithPending ); @@ -897,7 +932,9 @@ export const runTestCase = async ( "data", onOutput( "stderr", - combinedLines.push.bind(combinedLines), + (line, now) => { + combinedLines.push(`${now.toISOString()} ${line}`); + }, // Don't escape stderr output since it likely comes from debug output in the subprocess, which // is intended for human consumption and not for verifying test results. false @@ -907,9 +944,9 @@ export const runTestCase = async ( "data", onOutput( "stdout", - (line) => { + (line, now) => { stdoutLines.push(line); - combinedLines.push(line); + combinedLines.push(`${now.toISOString()} ${line}`); }, // Escape special characters in debug output so that we can more easily understand test // failures related to unexpected output. @@ -929,9 +966,13 @@ export const runTestCase = async ( console.error( `Test timed out after ${TEST_TIMEOUT_MS}ms; killing Cypress process tree` ); - treeKill(cypressChild.pid, "SIGKILL", () => { - reject(new Error(`Test timed out after ${TEST_TIMEOUT_MS}ms`)); - }); + const timeoutError = new Error( + `Test timed out after ${TEST_TIMEOUT_MS}ms` + ); + if (testError.error === undefined) { + testError.error = timeoutError; + } + treeKill(cypressChild.pid, "SIGKILL", () => reject(timeoutError)); }, TEST_TIMEOUT_MS); cypressChild.on("error", (err) => { @@ -945,8 +986,8 @@ export const runTestCase = async ( } ); - if (fetchMismatch.error !== undefined) { - throw fetchMismatch.error; + if (testError.error !== undefined) { + throw testError.error; } verifyOutput(params, stdoutLines, summaryTotals, apiServer.port); diff --git a/packages/cypress-plugin/test/integration/src/test-wrappers.ts b/packages/cypress-plugin/test/integration/src/test-wrappers.ts index 7f94fc2..f3697b7 100644 --- a/packages/cypress-plugin/test/integration/src/test-wrappers.ts +++ b/packages/cypress-plugin/test/integration/src/test-wrappers.ts @@ -10,6 +10,7 @@ import path from "path"; import _debug from "debug"; import cypressPackage from "cypress/package.json"; import { SummaryTotals } from "./parse-output"; +import * as os from "os"; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -142,11 +143,23 @@ export const integrationTestSuite = (runTests: () => void): void => { ? cypressMinorVersion[0] : cypressPackage.version }`, () => { - // Only use Node major version for test name. - describe(`Node ${ - nodeMajorVersion !== null ? nodeMajorVersion[0] : process.version - }`, () => { - runTests(); - }); + const platform = os.platform(); + describe( + platform === "darwin" + ? `MacOS` + : platform === "linux" + ? "Linux" + : platform === "win32" + ? "Windows" + : platform, + () => { + // Only use Node major version for test name. + describe(`Node ${ + nodeMajorVersion !== null ? nodeMajorVersion[0] : process.version + }`, () => { + runTests(); + }); + } + ); }); }; diff --git a/packages/cypress-plugin/test/integration/src/verify-output.ts b/packages/cypress-plugin/test/integration/src/verify-output.ts index ec5b440..6512caf 100644 --- a/packages/cypress-plugin/test/integration/src/verify-output.ts +++ b/packages/cypress-plugin/test/integration/src/verify-output.ts @@ -24,6 +24,8 @@ import escapeStringRegexp from "escape-string-regexp"; import { TestAttemptResult } from "@unflakable/js-api"; const THROWN_ERROR = "\x1B[0m\x1B[31m Error\x1B[0m\x1B[90m"; +const FAIL_SYMBOL = process.platform === "win32" ? "×" : "✖"; +const PASS_SYMBOL = process.platform === "win32" ? "√" : "✓"; const verifySpecOutput = ( params: TestCaseParams, @@ -119,11 +121,11 @@ const verifySpecOutputs = ( // Last retry has different output. { length: expectedRetries }, (_, idx) => - ` \x1B[31m ✖ mixed: failure should be quarantined\x1B[0m\x1B[33m (attempt ${ + ` \x1B[31m ${FAIL_SYMBOL} mixed: failure should be quarantined\x1B[0m\x1B[33m (attempt ${ idx + 1 } of ${expectedRetries + 1})\x1B[0m` ), - ` \x1B[35m ✖ mixed: failure should be quarantined [failed, quarantined]\x1B[39m${ + ` \x1B[35m ${FAIL_SYMBOL} mixed: failure should be quarantined [failed, quarantined]\x1B[39m${ expectedRetries > 0 ? `\x1B[33m (attempt ${expectedRetries + 1} of ${ expectedRetries + 1 @@ -132,27 +134,27 @@ const verifySpecOutputs = ( }`, ...(expectedRetries > 0 ? [ - ` \x1B[31m ✖ mixed: flake should be quarantined\x1B[0m\x1B[33m (attempt 1 of ${ + ` \x1B[31m ${FAIL_SYMBOL} mixed: flake should be quarantined\x1B[0m\x1B[33m (attempt 1 of ${ expectedRetries + 1 })\x1B[0m`, expectExt.stringMatching( new RegExp( // eslint-disable-next-line no-control-regex - `^ {2}\x1B\\[35m {2}✓\x1B\\[39m\x1B\\[90m mixed: flake should be quarantined\x1B\\[0m\x1B\\[35m \\[flaky, quarantined]\x1B\\[39m\x1B\\[33m \\(attempt 2 of ${ + `^ {2}\x1B\\[35m {2}${PASS_SYMBOL}\x1B\\[39m\x1B\\[90m mixed: flake should be quarantined\x1B\\[0m\x1B\\[35m \\[flaky, quarantined]\x1B\\[39m\x1B\\[33m \\(attempt 2 of ${ expectedRetries + 1 - }\\)\x1B\\[0m\x1B\\[90m \\([0-9]+.+?\\)\x1B\\[0m$` + }\\)\x1B\\[0m\x1B\\[(?:33|90)m \\([0-9]+.+?\\)\x1B\\[0m$` ) ), ] : [ - " \x1B[35m ✖ mixed: flake should be quarantined [failed, quarantined]\x1B[39m", + ` \x1B[35m ${FAIL_SYMBOL} mixed: flake should be quarantined [failed, quarantined]\x1B[39m`, ]), ] : [ ...Array.from( { length: expectedRetries + 1 }, (_, idx) => - ` \x1B[31m ✖ mixed: failure should be quarantined\x1B[0m${ + ` \x1B[31m ${FAIL_SYMBOL} mixed: failure should be quarantined\x1B[0m${ expectedRetries > 0 ? `\x1B[33m (attempt ${idx + 1} of ${ expectedRetries + 1 @@ -160,15 +162,15 @@ const verifySpecOutputs = ( : "" }` ), - ` \x1B[31m ✖ mixed: flake should be quarantined\x1B[0m\x1B[33m (attempt 1 of ${ + ` \x1B[31m ${FAIL_SYMBOL} mixed: flake should be quarantined\x1B[0m\x1B[33m (attempt 1 of ${ expectedRetries + 1 })\x1B[0m`, expectExt.stringMatching( new RegExp( // eslint-disable-next-line no-control-regex - `^ {2}\x1B\\[33m {2}✓\x1B\\[0m\x1B\\[90m mixed: flake should be quarantined\x1B\\[0m\x1B\\[33m \\[flaky]\x1B\\[0m\x1B\\[33m \\(attempt 2 of ${ + `^ {2}\x1B\\[33m {2}${PASS_SYMBOL}\x1B\\[0m\x1B\\[90m mixed: flake should be quarantined\x1B\\[0m\x1B\\[33m \\[flaky]\x1B\\[0m\x1B\\[33m \\(attempt 2 of ${ expectedRetries + 1 - }\\)\x1B\\[0m\x1B\\[90m \\([0-9]+.+?\\)\x1B\\[0m$` + }\\)\x1B\\[0m\x1B\\[(?:33|90)m \\([0-9]+.+?\\)\x1B\\[0m$` ) ), ] @@ -180,7 +182,7 @@ const verifySpecOutputs = ( ? Array.from( { length: expectedRetries + 1 }, (_, idx) => - ` \x1B[31m ✖ mixed: should fail\x1B[0m${ + ` \x1B[31m ${FAIL_SYMBOL} mixed: should fail\x1B[0m${ expectedRetries > 0 ? `\x1B[33m (attempt ${idx + 1} of ${ expectedRetries + 1 @@ -198,32 +200,34 @@ const verifySpecOutputs = ( ] : expectedRetries > 0 ? [ - ` \x1B[31m ✖ mixed: should be flaky\x1B[0m\x1B[33m (attempt 1 of ${ + ` \x1B[31m ${FAIL_SYMBOL} mixed: should be flaky\x1B[0m\x1B[33m (attempt 1 of ${ expectedRetries + 1 })\x1B[0m`, quarantineFlake && expectQuarantinedTestsToBeQuarantined ? expectExt.stringMatching( new RegExp( // eslint-disable-next-line no-control-regex - `^ {2}\x1B\\[35m {2}✓\x1B\\[39m\x1B\\[90m mixed: should be flaky\x1B\\[0m\x1B\\[35m \\[flaky, quarantined]\x1B\\[39m\x1B\\[33m \\(attempt 2 of ${ + `^ {2}\x1B\\[35m {2}${PASS_SYMBOL}\x1B\\[39m\x1B\\[90m mixed: should be flaky\x1B\\[0m\x1B\\[35m \\[flaky, quarantined]\x1B\\[39m\x1B\\[33m \\(attempt 2 of ${ expectedRetries + 1 - }\\)\x1B\\[0m\x1B\\[90m \\([0-9]+.+?\\)\x1B\\[0m$` + }\\)\x1B\\[0m\x1B\\[(?:33|90)m \\([0-9]+.+?\\)\x1B\\[0m$` ) ) : expectExt.stringMatching( new RegExp( // eslint-disable-next-line no-control-regex - `^ {2}\x1B\\[33m {2}✓\x1B\\[0m\x1B\\[90m mixed: should be flaky\x1B\\[0m\x1B\\[33m \\[flaky]\x1B\\[0m\x1B\\[33m \\(attempt 2 of ${ + `^ {2}\x1B\\[33m {2}${PASS_SYMBOL}\x1B\\[0m\x1B\\[90m mixed: should be flaky\x1B\\[0m\x1B\\[33m \\[flaky]\x1B\\[0m\x1B\\[33m \\(attempt 2 of ${ expectedRetries + 1 - }\\)\x1B\\[0m\x1B\\[90m \\([0-9]+.+?\\)\x1B\\[0m$` + }\\)\x1B\\[0m\x1B\\[(?:33|90)m \\([0-9]+.+?\\)\x1B\\[0m$` ) ), ] - : [" \x1B[31m ✖ mixed: should be flaky\x1B[0m"] + : [` \x1B[31m ${FAIL_SYMBOL} mixed: should be flaky\x1B[0m`] : [" \x1B[36m - mixed: should be flaky\x1B[0m"]), expectExt.stringMatching( // eslint-disable-next-line no-control-regex - /^ {2}\x1B\[32m {2}✓\x1B\[0m\x1B\[90m mixed: should pass\x1B\[0m\x1B\[90m \([0-9]+.+?\)\x1B\[0m$/ + new RegExp( + `^ {2}\\x1B\\[32m {2}${PASS_SYMBOL}\\x1B\\[0m\\x1B\\[90m mixed: should pass\\x1B\\[0m\\x1B\\[(?:33|90)m \\([0-9]+.+?\\)\\x1B\\[0m$` + ) ), " \x1B[36m - mixed: should be skipped\x1B[0m", ], @@ -599,7 +603,7 @@ const verifySpecOutputs = ( ? Array.from( { length: expectedRetries + 1 }, (_, idx) => - ` \x1B[31m ✖ should fail\x1B[0m${ + ` \x1B[31m ${FAIL_SYMBOL} should fail\x1B[0m${ expectedRetries > 0 ? `\x1B[33m (attempt ${idx + 1} of ${ expectedRetries + 1 @@ -612,7 +616,7 @@ const verifySpecOutputs = ( ? Array.from( { length: expectedRetries + 1 }, (_, idx) => - ` \x1B[31m ✖ should fail with multiple exceptions\x1B[0m${ + ` \x1B[31m ${FAIL_SYMBOL} should fail with multiple exceptions\x1B[0m${ expectedRetries > 0 ? `\x1B[33m (attempt ${idx + 1} of ${ expectedRetries + 1 @@ -626,7 +630,7 @@ const verifySpecOutputs = ( ? Array.from( { length: expectedRetries + 1 }, (_, idx) => - ` \x1B[31m ✖ should showDiff\x1B[0m${ + ` \x1B[31m ${FAIL_SYMBOL} should showDiff\x1B[0m${ expectedRetries > 0 ? `\x1B[33m (attempt ${idx + 1} of ${ expectedRetries + 1 @@ -694,33 +698,33 @@ const verifySpecOutputs = ( !expectQuarantinedTestsToBeSkipped) ? expectedRetries > 0 ? [ - `\x1B[31m ✖ should be flaky${expectedFlakeTestNameSuffix}\x1B[0m\x1B[33m (attempt 1 of ${ + `\x1B[31m ${FAIL_SYMBOL} should be flaky${expectedFlakeTestNameSuffix}\x1B[0m\x1B[33m (attempt 1 of ${ expectedRetries + 1 })\x1B[0m`, quarantineFlake && expectQuarantinedTestsToBeQuarantined ? expectExt.stringMatching( new RegExp( // eslint-disable-next-line no-control-regex - `^\x1B\\[35m {2}✓\x1B\\[39m\x1B\\[90m should be flaky${escapeStringRegexp( + `^\x1B\\[35m {2}${PASS_SYMBOL}\x1B\\[39m\x1B\\[90m should be flaky${escapeStringRegexp( expectedFlakeTestNameSuffix )}\x1B\\[0m\x1B\\[35m \\[flaky, quarantined]\x1B\\[39m\x1B\\[33m \\(attempt 2 of ${ expectedRetries + 1 - }\\)\x1B\\[0m\x1B\\[90m \\([0-9]+.+?\\)\x1B\\[0m$` + }\\)\x1B\\[0m\x1B\\[(?:33|90)m \\([0-9]+.+?\\)\x1B\\[0m$` ) ) : expectExt.stringMatching( new RegExp( // eslint-disable-next-line no-control-regex - `^\x1B\\[33m {2}✓\x1B\\[0m\x1B\\[90m should be flaky${escapeStringRegexp( + `^\x1B\\[33m {2}${PASS_SYMBOL}\x1B\\[0m\x1B\\[90m should be flaky${escapeStringRegexp( expectedFlakeTestNameSuffix )}\x1B\\[0m\x1B\\[33m \\[flaky]\x1B\\[0m\x1B\\[33m \\(attempt 2 of ${ expectedRetries + 1 - }\\)\x1B\\[0m\x1B\\[90m \\([0-9]+.+?\\)\x1B\\[0m$` + }\\)\x1B\\[0m\x1B\\[(?:33|90)m \\([0-9]+.+?\\)\x1B\\[0m$` ) ), ] : [ - `\x1B[31m ✖ should be flaky${expectedFlakeTestNameSuffix}\x1B[0m`, + `\x1B[31m ${FAIL_SYMBOL} should be flaky${expectedFlakeTestNameSuffix}\x1B[0m`, ] : [ `\x1B[36m - should be flaky${expectedFlakeTestNameSuffix}\x1B[0m${ @@ -938,7 +942,9 @@ const verifySpecOutputs = ( ? [ expectExt.stringMatching( // eslint-disable-next-line no-control-regex - /^ {2}\x1B\[32m {2}✓\x1B\[0m\x1B\[90m should fail due to hook\x1B\[0m\x1B\[90m \([0-9]+.+?\)\x1B\[0m$/ + new RegExp( + `^ {2}\\x1B\\[32m {2}${PASS_SYMBOL}\\x1B\\[0m\\x1B\\[90m should fail due to hook\\x1B\\[0m\\x1B\\[(?:33|90)m \\([0-9]+.+?\\)\\x1B\\[0m$` + ) ), ] : Array.from( @@ -961,8 +967,8 @@ const verifySpecOutputs = ( // https://github.com/mochajs/mocha/blob/0be3f78491bbbcdc4dcea660ee7bfd557a225d9c/lib/runner.js#L332 (hookFailResult === "quarantined" && (!skipBeforeHook || idx === expectedRetries) - ? " \x1B[35m ✖ should fail due to hook [failed, quarantined]\x1B[39m" - : " \x1B[31m ✖ should fail due to hook\x1B[0m") + + ? ` \x1B[35m ${FAIL_SYMBOL} should fail due to hook [failed, quarantined]\x1B[39m` + : ` \x1B[31m ${FAIL_SYMBOL} should fail due to hook\x1B[0m`) + (skipBeforeHook && expectedRetries > 0 ? `\x1B[33m (attempt ${idx + 1} of ${ expectedRetries + 1 @@ -973,13 +979,17 @@ const verifySpecOutputs = ( ? [ expectExt.stringMatching( // eslint-disable-next-line no-control-regex - /^ {2}\x1B\[32m {2}✓\x1B\[0m\x1B\[90m should be skipped\x1B\[0m\x1B\[90m \([0-9]+.+?\)\x1B\[0m$/ + new RegExp( + `^ {2}\\x1B\\[32m {2}${PASS_SYMBOL}\\x1B\\[0m\\x1B\\[90m should be skipped\\x1B\\[0m\\x1B\\[(?:33|90)m \\([0-9]+.+?\\)\\x1B\\[0m$` + ) ), ] : hookSkipResult === "fail" - ? [" \x1B[31m ✖ should be skipped\x1B[0m"] + ? [` \x1B[31m ${FAIL_SYMBOL} should be skipped\x1B[0m`] : hookSkipResult === "quarantined" - ? [" \x1B[35m ✖ should be skipped [failed, quarantined]\x1B[39m"] + ? [ + ` \x1B[35m ${FAIL_SYMBOL} should be skipped [failed, quarantined]\x1B[39m`, + ] : []), ], passing: @@ -1048,7 +1058,7 @@ const verifySpecOutputs = ( ? Array.from( { length: expectedRetries + 1 }, (_, idx) => - `\x1B[31m ✖ An uncaught error was detected outside of a test\x1B[0m${ + `\x1B[31m ${FAIL_SYMBOL} An uncaught error was detected outside of a test\x1B[0m${ expectedRetries > 0 ? `\x1B[33m (attempt ${idx + 1} of ${ expectedRetries + 1 @@ -1091,12 +1101,16 @@ const verifySpecOutputs = ( "called consoleLog command", expectExt.stringMatching( // eslint-disable-next-line no-control-regex - /^\x1B\[32m +✓\x1B\[0m\x1B\[90m should pass\x1B\[0m\x1B\[90m \([0-9]+.+?\)\x1B\[0m$/ + new RegExp( + `^\\x1B\\[32m +${PASS_SYMBOL}\\x1B\\[0m\\x1B\\[90m should pass\\x1B\\[0m\\x1B\\[(?:33|90)m \\([0-9]+.+?\\)\\x1B\\[0m$` + ) ), "\x1B[0m suite name\x1B[0m", expectExt.stringMatching( // eslint-disable-next-line no-control-regex - /^ {2}\x1B\[32m {2}✓\x1B\[0m\x1B\[90m suite test should pass\x1B\[0m\x1B\[90m \([0-9]+.+?\)\x1B\[0m$/ + new RegExp( + `^ {2}\\x1B\\[32m {2}${PASS_SYMBOL}\\x1B\\[0m\\x1B\\[90m suite test should pass\\x1B\\[0m\\x1B\\[(?:33|90)m \\([0-9]+.+?\\)\\x1B\\[0m$` + ) ), ], passing: 2, @@ -1193,7 +1207,7 @@ const verifySpecOutputs = ( // Last retry has different output. { length: expectedRetries }, (_, idx) => - ` \x1B[31m ✖ should be quarantined\x1B[0m${ + ` \x1B[31m ${FAIL_SYMBOL} should be quarantined\x1B[0m${ expectedRetries > 0 ? `\x1B[33m (attempt ${idx + 1} of ${ expectedRetries + 1 @@ -1201,7 +1215,7 @@ const verifySpecOutputs = ( : "" }` ), - ` \x1B[35m ✖ should be quarantined [failed, quarantined]\x1B[39m${ + ` \x1B[35m ${FAIL_SYMBOL} should be quarantined [failed, quarantined]\x1B[39m${ expectedRetries > 0 ? `\x1B[33m (attempt ${expectedRetries + 1} of ${ expectedRetries + 1 @@ -1215,7 +1229,7 @@ const verifySpecOutputs = ( : Array.from( { length: expectedRetries + 1 }, (_, idx) => - ` \x1B[31m ✖ should be quarantined\x1B[0m${ + ` \x1B[31m ${FAIL_SYMBOL} should be quarantined\x1B[0m${ expectedRetries > 0 ? `\x1B[33m (attempt ${idx + 1} of ${ expectedRetries + 1 diff --git a/packages/jest-plugin/package.json b/packages/jest-plugin/package.json index 0c25483..5e00edc 100644 --- a/packages/jest-plugin/package.json +++ b/packages/jest-plugin/package.json @@ -52,6 +52,7 @@ "exit": "^0.1.2", "jest-runner": "25.1.0 - 29", "jest-util": "25.1.0 - 29", + "rimraf": "^5.0.1", "rollup": "^3.21.1", "typescript": "^4.9.5" }, @@ -62,7 +63,8 @@ "jest-util": "25.1.0 - 29" }, "scripts": { - "build": "tsc --noEmit && rollup --config", - "build:watch": "rollup --config --watch" + "build": "yarn clean && tsc --noEmit && rollup --config", + "build:watch": "rollup --config --watch", + "clean": "rimraf dist/" } } diff --git a/packages/jest-plugin/rollup.config.mjs b/packages/jest-plugin/rollup.config.mjs index d39da52..d9474d2 100644 --- a/packages/jest-plugin/rollup.config.mjs +++ b/packages/jest-plugin/rollup.config.mjs @@ -1,41 +1,74 @@ // Copyright (c) 2023 Developer Innovations, LLC -import pluginTypescript from "@rollup/plugin-typescript"; -import pluginNodeResolve from "@rollup/plugin-node-resolve"; +import path from "path"; import pluginCommonJs from "@rollup/plugin-commonjs"; +import pluginDts from "rollup-plugin-dts"; import pluginJson from "@rollup/plugin-json"; +import pluginNodeResolve from "@rollup/plugin-node-resolve"; +import pluginTypescript from "@rollup/plugin-typescript"; + +/** + * Bundle the internal @unflakable/plugins-common package, but leave most other imported packages + * as external. Internal modules begin with `.` or `/`. + * + * @type {import("rollup").IsExternal} + */ +const isExternal = (id) => + !id.startsWith(".") && + !path.isAbsolute(id) && + !id.startsWith("src/") && + !id.startsWith("@unflakable/plugins-common/") && + ![ + // Support older versions of Jest that don't support sub-path externals in package.json. + "@unflakable/js-api/consts", + "@unflakable/plugins-common", + ].includes(id); /** * @type {import("rollup").NormalizedInputOptions} */ -export default { - input: ["src/reporter.ts", "src/runner.ts"], - output: { - dir: "dist", - format: "cjs", - // Mimicks TypeScript `esModuleInterop` (see - // https://rollupjs.org/configuration-options/#output-format). - interop: "auto", - sourcemap: true, +export default [ + { + input: ["src/reporter.ts", "src/runner.ts"], + output: { + dir: "dist", + format: "cjs", + // Mimicks TypeScript `esModuleInterop` (see + // https://rollupjs.org/configuration-options/#output-format). + interop: "auto", + }, + // Bundle the internal @unflakable/plugins-common package in dist/, but leave most other + // imported packages as an external. Internal modules begin with `.` or `/`. + external: isExternal, + // Unclear why this is necessary. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + plugins: [ + pluginCommonJs(), + pluginJson(), + pluginNodeResolve(), + pluginTypescript(), + ], + }, + // Rollup types so that UnflakableConfig from @unflakable/plugins-common is bundled. This package + // doesn't get published separately, so our public types shouldn't reference it. + { + input: [ + // NB: This should include every exported .d.ts from package.json. + "dist/reporter.d.ts", + "dist/runner.d.ts", + ], + output: { + dir: ".", + entryFileNames: "[name].d.ts", + format: "cjs", + }, + external: isExternal, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + plugins: [ + pluginNodeResolve({ preferBuiltins: true }), + pluginDts({ + respectExternal: true, + }), + ], }, - // Bundle the internal @unflakable/plugins-common package in dist/, but leave most other - // imported packages as an external. Internal modules begin with `.` or `/`. - external: (id) => - !id.startsWith(".") && - !id.startsWith("/") && - !id.startsWith("src/") && - !id.startsWith("@unflakable/plugins-common/") && - ![ - // Support older versions of Jest that don't support sub-path externals in package.json. - "@unflakable/js-api/consts", - "@unflakable/plugins-common", - ].includes(id), - // Unclear why this is necessary. - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - plugins: [ - pluginCommonJs(), - pluginJson(), - pluginNodeResolve(), - pluginTypescript(), - ], -}; +]; diff --git a/packages/jest-plugin/tsconfig.json b/packages/jest-plugin/tsconfig.json index c2f62b5..d5ff9c4 100644 --- a/packages/jest-plugin/tsconfig.json +++ b/packages/jest-plugin/tsconfig.json @@ -12,8 +12,7 @@ // Avoids conflicting global definitions from, e.g., jasmine. "types": ["node", "jest"], // Some versions of Jest (e.g., 28.0.0) have internally broken types. - "skipLibCheck": true, - "sourceMap": true + "skipLibCheck": true }, "include": [ ".eslintrc.js", diff --git a/packages/js-api/package.json b/packages/js-api/package.json index 6ea366b..d4347b7 100644 --- a/packages/js-api/package.json +++ b/packages/js-api/package.json @@ -32,10 +32,11 @@ }, "devDependencies": { "@types/async-retry": "^1.4.5", - "@types/node-fetch": "^2.6.2" + "@types/node-fetch": "^2.6.2", + "rimraf": "^5.0.1" }, "scripts": { - "build": "rm -rf dist && tsc --noEmit && tsc -p src", + "build": "rimraf dist && tsc --noEmit && tsc -p src", "build:watch": "tsc --build --watch" } } diff --git a/packages/plugins-common/package.json b/packages/plugins-common/package.json index ac4dd1b..9315964 100644 --- a/packages/plugins-common/package.json +++ b/packages/plugins-common/package.json @@ -24,10 +24,11 @@ "@unflakable/js-api": "workspace:^", "cosmiconfig": "^7.0.1", "debug": "^4.3.3", + "rimraf": "^5.0.1", "simple-git": "^3.16.0" }, "scripts": { - "build": "rm -rf dist && tsc --noEmit && tsc -p src", + "build": "rimraf dist && tsc --noEmit && tsc -p src", "build:watch": "tsc --build --watch" } } diff --git a/packages/plugins-common/src/index.ts b/packages/plugins-common/src/index.ts index c934470..32218ac 100644 --- a/packages/plugins-common/src/index.ts +++ b/packages/plugins-common/src/index.ts @@ -1,5 +1,7 @@ // Copyright (c) 2023 Developer Innovations, LLC +import path from "path"; + export { QuarantineMode, UnflakableConfig, @@ -23,3 +25,8 @@ export { } from "./git"; export { getTestSuiteManifest } from "./manifest"; export { isTestQuarantined, normalizeTestName } from "./quarantine"; + +// On Windows, we need to convert backslashes to forward slashes before reporting results to the +// backend or checking whether tests are quarantined. +export const toPosix = (file: string): string => + file.split(path.sep).join(path.posix.sep); diff --git a/packages/plugins-common/src/quarantine.ts b/packages/plugins-common/src/quarantine.ts index 0fc27f5..70cd1b6 100644 --- a/packages/plugins-common/src/quarantine.ts +++ b/packages/plugins-common/src/quarantine.ts @@ -31,13 +31,13 @@ export const normalizeTestName = (fullTestName: string[]): string[] => export const isTestQuarantined = ( manifest: TestSuiteManifest, - testFilename: string, + posixTestFilename: string, fullTestName: string[] ): boolean => { const testName = normalizeTestName(fullTestName); return manifest.quarantined_tests.some( (quarantinedTest) => - quarantinedTest.filename === testFilename && + quarantinedTest.filename === posixTestFilename && deepEqual(quarantinedTest.name, testName) ); }; diff --git a/yarn.lock b/yarn.lock index d57fbcf..ca4db0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1577,6 +1577,20 @@ __metadata: languageName: node linkType: hard +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: ^5.1.2 + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: ^7.0.1 + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: ^8.1.0 + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 4a473b9b32a7d4d3cfb7a614226e555091ff0c5a29a1734c28c72a182c2f6699b26fc6b5c2131dfd841e86b185aea714c72201d7c98c2fba5f17709333a67aeb + languageName: node + linkType: hard + "@istanbuljs/load-nyc-config@npm:^1.0.0": version: 1.1.0 resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" @@ -1976,6 +1990,13 @@ __metadata: languageName: node linkType: hard +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 6ad6a00fc4f2f2cfc6bff76fb1d88b8ee20bc0601e18ebb01b6d4be583733a860239a521a7fbca73b612e66705078809483549d2b18f370eb346c5155c8e4a0f + languageName: node + linkType: hard + "@pkgr/utils@npm:^2.3.1": version: 2.3.1 resolution: "@pkgr/utils@npm:2.3.1" @@ -2102,6 +2123,508 @@ __metadata: languageName: node linkType: hard +"@stdlib/assert-has-node-buffer-support@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/assert-has-node-buffer-support@npm:0.0.8" + dependencies: + "@stdlib/assert-is-buffer": ^0.0.x + "@stdlib/cli-ctor": ^0.0.x + "@stdlib/fs-read-file": ^0.0.x + bin: + has-node-buffer-support: bin/cli + checksum: 30fa31c3c675ce1e2b235327a5c074f7bc0a50d57eaf63d985b021073164ae1ef4be1bed842d5b56773218b0088f4fa1aa09fd488a450956a7dd088825b02864 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-has-own-property@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/assert-has-own-property@npm:0.0.7" + checksum: 4f1efbd2352214898792fea5bf83d190f9899a28d2eab52bca95de19370c112a385961390c423863be9977851f22003aee2268c1806138ad20d90bfa8d87eb95 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-has-symbol-support@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/assert-has-symbol-support@npm:0.0.8" + dependencies: + "@stdlib/cli-ctor": ^0.0.x + "@stdlib/fs-read-file": ^0.0.x + bin: + has-symbol-support: bin/cli + checksum: 122d53efd4f8489a975486f1c79ec302102158f7bc92b3b13f936c8eae01189c2bf93483adb3942a310357425f9a4251959793e9a77ef310b2944af44396c8fc + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-has-tostringtag-support@npm:^0.0.x": + version: 0.0.9 + resolution: "@stdlib/assert-has-tostringtag-support@npm:0.0.9" + dependencies: + "@stdlib/assert-has-symbol-support": ^0.0.x + "@stdlib/cli-ctor": ^0.0.x + "@stdlib/fs-read-file": ^0.0.x + bin: + has-tostringtag-support: bin/cli + checksum: e2d10af88f36ba0ae9e0a205761eb6c6497b4dba037fdd1302c345c419c3ff33dcc9e3162b83a98816a079ac26415e28bdfc160ae2822941d7bec41d516be2e4 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-is-array@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/assert-is-array@npm:0.0.7" + dependencies: + "@stdlib/utils-native-class": ^0.0.x + checksum: 17d64b5982fa6f1feb093e8c8a631adc780035070168b92ceafb8cd5162ad3f9a81952a4938dd6515a8caee20f24aca9112e4f219eb2f03789a8030aa434ad07 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-is-boolean@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/assert-is-boolean@npm:0.0.8" + dependencies: + "@stdlib/assert-has-tostringtag-support": ^0.0.x + "@stdlib/utils-define-nonenumerable-read-only-property": ^0.0.x + "@stdlib/utils-native-class": ^0.0.x + checksum: 501a78fa4a60816fe24de8870d2bccf1cdc2f6a1d2d4ae2b4d10ba6b55f667fc4a658504dc12fe89c697aa6f81020f14c9c8cb5512b0e24d33f88c705028b7b7 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-is-buffer@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/assert-is-buffer@npm:0.0.8" + dependencies: + "@stdlib/assert-is-object-like": ^0.0.x + checksum: 2548c1d0a4c0240bef5677fb422c419b14dbc78a6499404eecd85afc332994660284c67adbf9f2de7ae429bed0b8683491528ba75e4baa3ec374748a569c1eaa + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-is-function@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/assert-is-function@npm:0.0.8" + dependencies: + "@stdlib/utils-type-of": ^0.0.x + checksum: 57017ac110fa14ea61efd48b8b61c35141a5f6438312a846d38b30b7e22d8048e38c267bccf91f3c0e6d47f893e5ba75f93816d1dc529595955dfb2a22ddfd6e + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-is-object-like@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/assert-is-object-like@npm:0.0.8" + dependencies: + "@stdlib/assert-tools-array-function": ^0.0.x + "@stdlib/utils-define-nonenumerable-read-only-property": ^0.0.x + checksum: 45b1dbf8dc43abac2890afc21d64773bc2cc932696d7372ca5366402b7351314921293b14722454ab1a7a324449dfb924e519d82df188b37f9bfc8cc54ba863b + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-is-object@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/assert-is-object@npm:0.0.8" + dependencies: + "@stdlib/assert-is-array": ^0.0.x + checksum: e50b56c32a6693f73f7d65a29726e76b4df8ebae601fd3508b3ef512a1988ff2ce9d436607555888979c715c13600a1a0ebb205fdb0235a30ca3de5580f164ae + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-is-plain-object@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/assert-is-plain-object@npm:0.0.7" + dependencies: + "@stdlib/assert-has-own-property": ^0.0.x + "@stdlib/assert-is-function": ^0.0.x + "@stdlib/assert-is-object": ^0.0.x + "@stdlib/utils-get-prototype-of": ^0.0.x + "@stdlib/utils-native-class": ^0.0.x + checksum: 3ec502a4954ef959dc478d4674f1fd4b0929bb5d420be0097db6a8eb899a1e65c23bdbdfb73e1b750f058058ac0634b1fc26b4275eb8ccffa196967a3da427d7 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-is-regexp-string@npm:^0.0.x": + version: 0.0.9 + resolution: "@stdlib/assert-is-regexp-string@npm:0.0.9" + dependencies: + "@stdlib/assert-is-string": ^0.0.x + "@stdlib/cli-ctor": ^0.0.x + "@stdlib/fs-read-file": ^0.0.x + "@stdlib/process-read-stdin": ^0.0.x + "@stdlib/regexp-eol": ^0.0.x + "@stdlib/regexp-regexp": ^0.0.x + "@stdlib/streams-node-stdin": ^0.0.x + bin: + is-regexp-string: bin/cli + checksum: 10155625e04f3d79466993e080b2cf01742bf88a1adb9d383d1854b3a7464792ed8d59e810c7a35b5a8fbbfb63eb0317a540fd687cec2afdf59052b79a734911 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-is-regexp@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/assert-is-regexp@npm:0.0.7" + dependencies: + "@stdlib/assert-has-tostringtag-support": ^0.0.x + "@stdlib/utils-native-class": ^0.0.x + checksum: 6d881f57b1e295b0abf1f7698404cfa9aa8f0851099b5d1cb786b809742df266f17e4287af8932351a303825d475a6d6e22751a4f521b6e6504acab6d9aabd33 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-is-string@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/assert-is-string@npm:0.0.8" + dependencies: + "@stdlib/assert-has-tostringtag-support": ^0.0.x + "@stdlib/utils-define-nonenumerable-read-only-property": ^0.0.x + "@stdlib/utils-native-class": ^0.0.x + checksum: c1bde204d24c2debf348202b432fa2376f0d53d8949a8a2f1cebc298e417495242267732bdb87df693ba46de3f2777895ed09d5271ba8ca83e107a7412e23c84 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/assert-tools-array-function@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/assert-tools-array-function@npm:0.0.7" + dependencies: + "@stdlib/assert-is-array": ^0.0.x + checksum: 2092c89eae72b5da2192fedecc1cddfd7ac553298425039d895cc858ee981df67f13560db60387b166354761cd74017bc5cc959e555d317c07358153bd9d79ef + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/buffer-ctor@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/buffer-ctor@npm:0.0.7" + dependencies: + "@stdlib/assert-has-node-buffer-support": ^0.0.x + checksum: 211b5eb199a6037381339bd0f7759f0dbeb912e8312d6ccb2a7c58fc77a4d311998efaecc1fce0e689e0483c213e9a24cfe3441e0130ca099f11abf5fb25cde7 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/buffer-from-string@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/buffer-from-string@npm:0.0.8" + dependencies: + "@stdlib/assert-is-function": ^0.0.x + "@stdlib/assert-is-string": ^0.0.x + "@stdlib/buffer-ctor": ^0.0.x + "@stdlib/string-format": ^0.0.x + checksum: f436d106e816339f6ae8e438b5d36e8e522c50958663370bc3c028061aeae225026549f6de2fd50c9d34c08f1c581130dd8702b22a7002c5bdd8bd137d9ad863 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/cli-ctor@npm:^0.0.x": + version: 0.0.3 + resolution: "@stdlib/cli-ctor@npm:0.0.3" + dependencies: + "@stdlib/utils-define-nonenumerable-read-only-property": ^0.0.x + "@stdlib/utils-noop": ^0.0.x + minimist: ^1.2.0 + checksum: c3b6e8b9d149b96a1e97df91e77fbc5245a92b504c277d993d2c08ff263ce46fc83dd211ee71e0e0641f700d7622b88265ce9a73b7628176f16626e8abe0e213 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/fs-read-file@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/fs-read-file@npm:0.0.8" + dependencies: + "@stdlib/cli-ctor": ^0.0.x + "@stdlib/utils-define-nonenumerable-read-only-property": ^0.0.x + bin: + read-file: bin/cli + checksum: 10a1ead656b8a5d7f6f4975eb0d65c385f827e106452fb055caf9a471b672da33b6f282ea5a91cfb294a7d94152ab7133529ddaabdbfa63706253ba88c50bd0d + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/process-read-stdin@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/process-read-stdin@npm:0.0.7" + dependencies: + "@stdlib/assert-is-function": ^0.0.x + "@stdlib/assert-is-string": ^0.0.x + "@stdlib/buffer-ctor": ^0.0.x + "@stdlib/buffer-from-string": ^0.0.x + "@stdlib/streams-node-stdin": ^0.0.x + "@stdlib/utils-next-tick": ^0.0.x + checksum: 5dd79b3e9939906af94025845f52f5107f9b371a3eabc131a3b456219b37f5df7f95ba04d526e37c0c8128d263f21d7371d98e77afc80fc06e926836e7150bf2 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/regexp-eol@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/regexp-eol@npm:0.0.7" + dependencies: + "@stdlib/assert-has-own-property": ^0.0.x + "@stdlib/assert-is-boolean": ^0.0.x + "@stdlib/assert-is-plain-object": ^0.0.x + "@stdlib/assert-is-string": ^0.0.x + "@stdlib/utils-define-nonenumerable-read-only-property": ^0.0.x + checksum: c4cbe08a20192681395958ebcb4fdb3adaf243d229d8d681308903fb4587a990f3e8cea09ae101dd36e9d0fdff0e1206418001c99863c72a8d6e0670dd57ce65 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/regexp-extended-length-path@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/regexp-extended-length-path@npm:0.0.7" + dependencies: + "@stdlib/utils-define-nonenumerable-read-only-property": ^0.0.x + checksum: 21a9595c94a1ce39a2b41c13346f50300ebc1a6822708336ac2433bf3dabfc312be863bb3e94e6e5aa42793df457d4aab429d6299351cddb369cac9238fbcc6f + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/regexp-function-name@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/regexp-function-name@npm:0.0.7" + dependencies: + "@stdlib/utils-define-nonenumerable-read-only-property": ^0.0.x + checksum: 47ac8ca5b7cec2b0716de4919ac9c9c3b05af22aaa0955d458e58d330688ec3cd6742988c05703ea75373a9ffdec13e0961a8c977126dfc22ed61c0787cadbf3 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/regexp-regexp@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/regexp-regexp@npm:0.0.8" + dependencies: + "@stdlib/utils-define-nonenumerable-read-only-property": ^0.0.x + checksum: 2a771dc4485fd7c05243627bb60b8de7395d53b7dac737952d16a55773b5912adc8a5cd4191b061e3739cd725662c8101c40c9900066e2faf04e51f11b8ff5d0 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/streams-node-stdin@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/streams-node-stdin@npm:0.0.7" + checksum: 750415b3c1cbb56592e9fbd9d7ccbb3b4e7f49be3f5ba95e1156efd0382ba23bf47eb3cf4088b5f6948a76c212af6b9e81ab78c24762d513b790bd8424700240 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/string-base-format-interpolate@npm:^0.0.x": + version: 0.0.4 + resolution: "@stdlib/string-base-format-interpolate@npm:0.0.4" + checksum: 6eec4db6689fc7db21b85d70a944995721d5dcc6805f7a0c1c90070ed574e831252d252ce121abf4c76d2fdf0290ba04687cd867b23bd7f8229bd4e11b4df7fd + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/string-base-format-tokenize@npm:^0.0.x": + version: 0.0.4 + resolution: "@stdlib/string-base-format-tokenize@npm:0.0.4" + checksum: 40a18396c16c75ecfc9b517119a276f5bdcc3f45ef6107ed6d3f4549e2be3b88a5eee38149dd5010a5f79bd091add273b162cbe2a95af90a86749744a0dc32dd + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/string-format@npm:^0.0.x": + version: 0.0.3 + resolution: "@stdlib/string-format@npm:0.0.3" + dependencies: + "@stdlib/string-base-format-interpolate": ^0.0.x + "@stdlib/string-base-format-tokenize": ^0.0.x + checksum: b66be4fb1afa0d9b1ba3ee9fd00e0cf4d466e15e209f349d123e542292fa2489a1e2253dd170e87182ef7a51d103e2458df44f4ca3a697add6795525736982fe + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/string-lowercase@npm:^0.0.x": + version: 0.0.9 + resolution: "@stdlib/string-lowercase@npm:0.0.9" + dependencies: + "@stdlib/assert-is-string": ^0.0.x + "@stdlib/cli-ctor": ^0.0.x + "@stdlib/fs-read-file": ^0.0.x + "@stdlib/process-read-stdin": ^0.0.x + "@stdlib/streams-node-stdin": ^0.0.x + "@stdlib/string-format": ^0.0.x + bin: + lowercase: bin/cli + checksum: 278ababf4abf70eb08b58a79b9049a42ea867a51ab4391eca07afddc6ea643b7ceed8cbd5484f10c5602e1b7c89c7f98a68ed4a4f92c99b093919f10fe3a2d7d + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/string-replace@npm:^0.0.x": + version: 0.0.11 + resolution: "@stdlib/string-replace@npm:0.0.11" + dependencies: + "@stdlib/assert-is-function": ^0.0.x + "@stdlib/assert-is-regexp": ^0.0.x + "@stdlib/assert-is-regexp-string": ^0.0.x + "@stdlib/assert-is-string": ^0.0.x + "@stdlib/cli-ctor": ^0.0.x + "@stdlib/fs-read-file": ^0.0.x + "@stdlib/process-read-stdin": ^0.0.x + "@stdlib/regexp-eol": ^0.0.x + "@stdlib/streams-node-stdin": ^0.0.x + "@stdlib/string-format": ^0.0.x + "@stdlib/utils-escape-regexp-string": ^0.0.x + "@stdlib/utils-regexp-from-string": ^0.0.x + bin: + replace: bin/cli + checksum: 63c28624c18caf0557dcfb3a4837d9188b390e8a7ac31b5bdc1f8299d84812bed7f83f49128b7faefc2a4b5d67ec46997243f2c524c6e013d9ba7f9811625d67 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/types@npm:^0.0.x": + version: 0.0.14 + resolution: "@stdlib/types@npm:0.0.14" + checksum: 5680a655ddb3ad730f5c7eb2363a43e089f3e6a1b85b12546cab49f7749bb3baf293bd50fbfe55486f233f4227f1020b65eb461b754b94fb4a4bc2799647ec22 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-constructor-name@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/utils-constructor-name@npm:0.0.8" + dependencies: + "@stdlib/assert-is-buffer": ^0.0.x + "@stdlib/regexp-function-name": ^0.0.x + "@stdlib/utils-native-class": ^0.0.x + checksum: a8ad0c7db0b34d0d6015f5a01907cdfedfe9419b11cc949bd3ec28f63f29f402e6c101217ab658d8c0e815eb5c8f0672bcf9dae9a3582f4b56bef5157c2fac4c + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-convert-path@npm:^0.0.8": + version: 0.0.8 + resolution: "@stdlib/utils-convert-path@npm:0.0.8" + dependencies: + "@stdlib/assert-is-string": ^0.0.x + "@stdlib/cli-ctor": ^0.0.x + "@stdlib/fs-read-file": ^0.0.x + "@stdlib/process-read-stdin": ^0.0.x + "@stdlib/regexp-eol": ^0.0.x + "@stdlib/regexp-extended-length-path": ^0.0.x + "@stdlib/streams-node-stdin": ^0.0.x + "@stdlib/string-lowercase": ^0.0.x + "@stdlib/string-replace": ^0.0.x + bin: + convert-path: bin/cli + checksum: d8eb6dd6b2530b05af7a58c6be2b842c5abbfb02860ba7c9a4bb035c6092d3e6781708ec00d46af97c04598b0df18ce2a81cc4068ce48907d84ba975594e52a6 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-define-nonenumerable-read-only-property@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/utils-define-nonenumerable-read-only-property@npm:0.0.7" + dependencies: + "@stdlib/types": ^0.0.x + "@stdlib/utils-define-property": ^0.0.x + checksum: 0ec1480e58c25d64144cf0c88e241604a07a9faa613e0468d79e7597adfc109a57cb08afac1868180e6fe0b02cd3517b6fc2da80d03300e63e61fc0709cf8090 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-define-property@npm:^0.0.x": + version: 0.0.9 + resolution: "@stdlib/utils-define-property@npm:0.0.9" + dependencies: + "@stdlib/types": ^0.0.x + checksum: f9b7b20765ce7fcae7a0b57b90133be4259344f37faea929d873f46d5e4bb56c9da74fdbea26d945da94536e94b477ebd1a41708d2dbcdfe3d69208674175438 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-escape-regexp-string@npm:^0.0.x": + version: 0.0.9 + resolution: "@stdlib/utils-escape-regexp-string@npm:0.0.9" + dependencies: + "@stdlib/assert-is-string": ^0.0.x + "@stdlib/string-format": ^0.0.x + checksum: ffe0d50b217fbaaf65bfcc852fe422cf1c21dca52aadf50130d7132d38f7a108081e71a3397ea8d32cf1f15a6b9a71f9dc05e8ce39b715b53faedcb1b517d21f + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-get-prototype-of@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/utils-get-prototype-of@npm:0.0.7" + dependencies: + "@stdlib/assert-is-function": ^0.0.x + "@stdlib/utils-native-class": ^0.0.x + checksum: 850f374a4b3bb49e6812827872e4e2e0cdbebefba62ab51d5888f1434f9b4c17ea56fde861899d6ca720e98260bc1f797fcd47d52651964e728474a8e1fd085a + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-global@npm:^0.0.x": + version: 0.0.7 + resolution: "@stdlib/utils-global@npm:0.0.7" + dependencies: + "@stdlib/assert-is-boolean": ^0.0.x + checksum: 64f429ae756ad7c515696e537d76fe1ea278863c2fb6bc92dcb989b2f3a8d4f258d4c3f152df2f480fb1b5af6a960c8c8a948d6a37fbf6bae229940bcc5c076e + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-native-class@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/utils-native-class@npm:0.0.8" + dependencies: + "@stdlib/assert-has-own-property": ^0.0.x + "@stdlib/assert-has-tostringtag-support": ^0.0.x + checksum: 4867eb4107d3924dfb60fa3351412c6790440e917087be6029ca89780b4a528aacaa672f588d482f5ca905842893cc6ae3cf6d0ee44ec378d1a578ae223fea37 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-next-tick@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/utils-next-tick@npm:0.0.8" + checksum: 0ef1d398e10ec35fa2c84168d9d85b110c6900405dd851071af36c4bdc42ad3f0239b501f8bde9cdfa4ee0d8540a0453e15838a6db2d3604d92fd94d6e412ec6 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-noop@npm:^0.0.x": + version: 0.0.14 + resolution: "@stdlib/utils-noop@npm:0.0.14" + checksum: 907017cedeb7329b611dc6217a1c4c5a39070dc06e3bf5c895b11786d126eea0367d83315d48519395d5ac4af713155e0f1ccf5f2741ed03701b7a76355b975c + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-regexp-from-string@npm:^0.0.x": + version: 0.0.9 + resolution: "@stdlib/utils-regexp-from-string@npm:0.0.9" + dependencies: + "@stdlib/assert-is-string": ^0.0.x + "@stdlib/regexp-regexp": ^0.0.x + "@stdlib/string-format": ^0.0.x + checksum: 78e2fd5e007744320f8b81ae753bcb58e7b702878f04fc90cb073beed79d1a35d753835eb4a17ec5a8b187b0a7587ac652f96309cb4c6d37119e615240c015d5 + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + +"@stdlib/utils-type-of@npm:^0.0.x": + version: 0.0.8 + resolution: "@stdlib/utils-type-of@npm:0.0.8" + dependencies: + "@stdlib/utils-constructor-name": ^0.0.x + "@stdlib/utils-global": ^0.0.x + checksum: de4e2a5c797010060107ae620de4b64f198f9b28a4e4a6910a6ae310b3a28594fbe47c7ab701ad2dc5d58076b5ed06e7fcf77e89ca609980aa0367c7bfcf4dcd + conditions: (os=aix | os=darwin | os=freebsd | os=linux | os=macos | os=openbsd | os=sunos | os=win32 | os=windows) + languageName: node + linkType: hard + "@tootallnate/once@npm:1": version: 1.1.2 resolution: "@tootallnate/once@npm:1.1.2" @@ -2650,6 +3173,7 @@ __metadata: "@rollup/plugin-json": ^6.0.0 "@rollup/plugin-node-resolve": ^15.0.2 "@rollup/plugin-typescript": ^11.1.1 + "@stdlib/utils-convert-path": ^0.0.8 "@types/debug": ^4.1.7 "@types/deep-equal": ^1.0.1 "@types/es6-promisify": ^6.0.1 @@ -2677,7 +3201,9 @@ __metadata: mocha: =7.0.1 ms: 2.1.1 path-browserify: ^1.0.1 + rimraf: ^5.0.1 rollup: ^3.21.1 + rollup-plugin-dts: ^5.3.0 simple-git: ^3.16.0 term-size: 2.1.0 tmp: ~0.2.1 @@ -2718,6 +3244,7 @@ __metadata: exit: ^0.1.2 jest-runner: 25.1.0 - 29 jest-util: 25.1.0 - 29 + rimraf: ^5.0.1 rollup: ^3.21.1 simple-git: ^3.16.0 typescript: ^4.9.5 @@ -2737,6 +3264,7 @@ __metadata: "@types/node-fetch": ^2.6.2 async-retry: ^1.3.3 node-fetch: ^2.6.7 + rimraf: ^5.0.1 languageName: unknown linkType: soft @@ -2747,6 +3275,7 @@ __metadata: "@unflakable/js-api": "workspace:^" cosmiconfig: ^7.0.1 debug: ^4.3.3 + rimraf: ^5.0.1 simple-git: ^3.16.0 languageName: unknown linkType: soft @@ -3062,6 +3591,13 @@ __metadata: languageName: node linkType: hard +"ansi-regex@npm:^6.0.1": + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: 1ff8b7667cded1de4fa2c9ae283e979fc87036864317da86a2e546725f96406746411d0d85e87a2d12fa5abd715d90006de7fa4fa0477c92321ad3b4c7d4e169 + languageName: node + linkType: hard + "ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" @@ -3087,6 +3623,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 + languageName: node + linkType: hard + "anymatch@npm:^3.0.3, anymatch@npm:~3.1.1": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -4187,6 +4730,7 @@ __metadata: "@unflakable/plugins-common": "workspace:^" debug: ^4.3.3 expect: ^29.5.0 + rimraf: ^5.0.1 rollup: ^3.21.1 rollup-plugin-dts: ^5.3.0 simple-git: ^3.16.0 @@ -4260,7 +4804,6 @@ __metadata: resolution: "cypress-integration@workspace:packages/cypress-plugin/test/integration" dependencies: "@types/jest": ^29.5.2 - "@unflakable/cypress-plugin": "workspace:^" "@unflakable/jest-plugin": "workspace:^" "@unflakable/js-api": "workspace:^" "@unflakable/plugins-common": "workspace:^" @@ -4595,6 +5138,13 @@ __metadata: languageName: node linkType: hard +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 7d00d7cd8e49b9afa762a813faac332dee781932d6f2c848dc348939c4253f1d4564341b7af1d041853bc3f32c2ef141b58e0a4d9862c17a7f08f68df1e0f1ed + languageName: node + linkType: hard + "ecc-jsbn@npm:~0.1.1": version: 0.1.2 resolution: "ecc-jsbn@npm:0.1.2" @@ -4640,6 +5190,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 8487182da74aabd810ac6d6f1994111dfc0e331b01271ae01ec1eb0ad7b5ecc2bbbbd2f053c05cb55a1ac30449527d819bbfbf0e3de1023db308cbcb47f86601 + languageName: node + linkType: hard + "encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" @@ -5520,6 +6077,16 @@ __metadata: languageName: node linkType: hard +"foreground-child@npm:^3.1.0": + version: 3.1.1 + resolution: "foreground-child@npm:3.1.1" + dependencies: + cross-spawn: ^7.0.0 + signal-exit: ^4.0.1 + checksum: 139d270bc82dc9e6f8bc045fe2aae4001dc2472157044fdfad376d0a3457f77857fa883c1c8b21b491c6caade9a926a4bed3d3d2e8d3c9202b151a4cbbd0bcd5 + languageName: node + linkType: hard + "forever-agent@npm:~0.6.1": version: 0.6.1 resolution: "forever-agent@npm:0.6.1" @@ -5829,6 +6396,21 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.2.5": + version: 10.3.0 + resolution: "glob@npm:10.3.0" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^2.0.3 + minimatch: ^9.0.1 + minipass: ^5.0.0 || ^6.0.2 + path-scurry: ^1.7.0 + bin: + glob: dist/cjs/src/bin.js + checksum: 6fa4ac0a86ffec1c5715a2e6fbdd63e1e7f1c2c8f5db08cc3256cdfcb81093678e7c80a3d100b502a1b9d141369ecf87bc24fe2bcb72acec7b14626d358a4eb0 + languageName: node + linkType: hard + "glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -6780,6 +7362,19 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^2.0.3": + version: 2.2.1 + resolution: "jackspeak@npm:2.2.1" + dependencies: + "@isaacs/cliui": ^8.0.2 + "@pkgjs/parseargs": ^0.11.0 + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: e29291c0d0f280a063fa18fbd1e891ab8c2d7519fd34052c0ebde38538a15c603140d60c2c7f432375ff7ee4c5f1c10daa8b2ae19a97c3d4affe308c8360c1df + languageName: node + linkType: hard + "jest-changed-files@npm:^29.5.0": version: 29.5.0 resolution: "jest-changed-files@npm:29.5.0" @@ -7655,6 +8250,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^9.1.1": + version: 9.1.2 + resolution: "lru-cache@npm:9.1.2" + checksum: d3415634be3908909081fc4c56371a8d562d9081eba70543d86871b978702fffd0e9e362b83921b27a29ae2b37b90f55675aad770a54ac83bb3e4de5049d4b15 + languageName: node + linkType: hard + "magic-string@npm:^0.27.0": version: 0.27.0 resolution: "magic-string@npm:0.27.0" @@ -7837,6 +8439,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^9.0.1": + version: 9.0.2 + resolution: "minimatch@npm:9.0.2" + dependencies: + brace-expansion: ^2.0.1 + checksum: 2eb12e2047a062fdb973fb51b9803f2455e3a00977858c038d66646d303a5a15bbcbc6ed5a2fc403bc869b1309f829ed3acd881d3246faf044ea7a494974b924 + languageName: node + linkType: hard + "minimist@npm:0.0.8": version: 0.0.8 resolution: "minimist@npm:0.0.8" @@ -7911,6 +8522,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^5.0.0 || ^6.0.2": + version: 6.0.2 + resolution: "minipass@npm:6.0.2" + checksum: d140b91f4ab2e5ce5a9b6c468c0e82223504acc89114c1a120d4495188b81fedf8cade72a9f4793642b4e66672f990f1e0d902dd858485216a07cd3c8a62fac9 + languageName: node + linkType: hard + "minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": version: 2.1.2 resolution: "minizlib@npm:2.1.2" @@ -8552,6 +9170,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^1.7.0": + version: 1.9.2 + resolution: "path-scurry@npm:1.9.2" + dependencies: + lru-cache: ^9.1.1 + minipass: ^5.0.0 || ^6.0.2 + checksum: 92888dfb68e285043c6d3291c8e971d5d2bc2f5082f4d7b5392896f34be47024c9d0a8b688dd7ae6d125acc424699195474927cb4f00049a9b1ec7c4256fa8e0 + languageName: node + linkType: hard + "path-to-regexp@npm:0.1.7": version: 0.1.7 resolution: "path-to-regexp@npm:0.1.7" @@ -9138,6 +9766,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^5.0.1": + version: 5.0.1 + resolution: "rimraf@npm:5.0.1" + dependencies: + glob: ^10.2.5 + bin: + rimraf: dist/cjs/src/bin.js + checksum: bafce85391349a2d960847980bf9b5caa2a8887f481af630f1ea27e08288217293cec72d75e9a2ba35495c212789f66a7f3d23366ba6197026ab71c535126857 + languageName: node + linkType: hard + "rimraf@npm:~2.6.2": version: 2.6.3 resolution: "rimraf@npm:2.6.3" @@ -9407,6 +10046,13 @@ __metadata: languageName: node linkType: hard +"signal-exit@npm:^4.0.1": + version: 4.0.2 + resolution: "signal-exit@npm:4.0.2" + checksum: 41f5928431cc6e91087bf0343db786a6313dd7c6fd7e551dbc141c95bb5fb26663444fd9df8ea47c5d7fc202f60aa7468c3162a9365cbb0615fc5e1b1328fe31 + languageName: node + linkType: hard + "simple-git@npm:^3.16.0": version: 3.17.0 resolution: "simple-git@npm:3.17.0" @@ -9615,7 +10261,7 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -9647,6 +10293,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: ^0.2.0 + emoji-regex: ^9.2.2 + strip-ansi: ^7.0.1 + checksum: 7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 + languageName: node + linkType: hard + "string.prototype.trim@npm:^1.2.7": version: 1.2.7 resolution: "string.prototype.trim@npm:1.2.7" @@ -9705,6 +10362,15 @@ __metadata: languageName: node linkType: hard +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: ^5.0.1 + checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c + languageName: node + linkType: hard + "strip-ansi@npm:^4.0.0": version: 4.0.0 resolution: "strip-ansi@npm:4.0.0" @@ -9723,12 +10389,12 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" dependencies: - ansi-regex: ^5.0.1 - checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c + ansi-regex: ^6.0.1 + checksum: 859c73fcf27869c22a4e4d8c6acfe690064659e84bef9458aa6d13719d09ca88dcfd40cbf31fd0be63518ea1a643fe070b4827d353e09533a5b0b9fd4553d64d languageName: node linkType: hard @@ -10640,6 +11306,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + languageName: node + linkType: hard + "wrap-ansi@npm:^5.1.0": version: 5.1.0 resolution: "wrap-ansi@npm:5.1.0" @@ -10662,14 +11339,14 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" dependencies: - ansi-styles: ^4.0.0 - string-width: ^4.1.0 - strip-ansi: ^6.0.0 - checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + ansi-styles: ^6.1.0 + string-width: ^5.0.1 + strip-ansi: ^7.0.1 + checksum: 371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238 languageName: node linkType: hard pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy