From 2333d04a54acfd2d89d9d53cc60a49d46228ceef Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 1 Nov 2018 12:34:37 -0400 Subject: [PATCH] secure cookie error crash (#2685) - fixes #1264 - fixes #1321 - fixes #1799 - fixes #2689 - fixes #2688 - fixes #2687 - fixes #2686 --- .eslintrc | 59 +++ .gitignore | 2 +- .vscode/launch.json | 32 ++ .vscode/tasks.json | 29 ++ .vscode/terminals.json | 56 +++ bulk-decaffeinate.config.js | 17 + cli/lib/cli.js | 8 + cli/lib/cypress.js | 2 + cli/lib/errors.js | 51 +- cli/lib/exec/spawn.js | 6 +- cli/lib/exec/versions.js | 3 + cli/lib/exec/xvfb.js | 3 + cli/lib/logger.js | 7 +- cli/lib/tasks/cache.js | 1 + cli/lib/tasks/download.js | 16 +- cli/lib/tasks/install.js | 56 ++- cli/lib/tasks/state.js | 13 +- cli/lib/tasks/unzip.js | 18 +- cli/lib/tasks/verify.js | 42 +- cli/lib/util.js | 22 +- cli/scripts/build.js | 3 +- cli/test/lib/build_spec.js | 10 +- cli/test/lib/cli_spec.js | 38 +- cli/test/lib/cypress_spec.js | 5 + cli/test/lib/errors_spec.js | 10 +- cli/test/lib/util_spec.js | 16 + cli/test/spec_helper.js | 5 +- jsconfig.json | 15 + package.json | 19 +- packages/coffee/package.json | 3 +- packages/desktop-gui/src/app/intro.jsx | 4 + packages/desktop-gui/src/app/nav.jsx | 15 +- packages/desktop-gui/src/auth/auth-api.js | 2 + packages/desktop-gui/src/auth/auth-store.js | 1 + packages/desktop-gui/src/auth/login-form.jsx | 16 +- .../desktop-gui/src/dropdown/dropdown.jsx | 13 +- .../duration-timer/duration-timer-store.js | 5 +- packages/desktop-gui/src/footer/footer.jsx | 1 + packages/desktop-gui/src/lib/app-store.js | 3 + .../src/lib/handle-global-errors.js | 1 + packages/desktop-gui/src/lib/ipc-bus.js | 14 +- packages/desktop-gui/src/lib/ipc.js | 4 +- packages/desktop-gui/src/lib/local-data.js | 1 + packages/desktop-gui/src/lib/routing.jsx | 1 + packages/desktop-gui/src/lib/utils.js | 5 +- .../src/organizations/organizations-api.js | 2 + .../src/organizations/organizations-store.js | 8 +- .../desktop-gui/src/project/onboarding.jsx | 21 +- .../desktop-gui/src/project/project-model.js | 3 + .../desktop-gui/src/projects/projects-api.js | 12 +- .../src/projects/projects-store.js | 17 +- .../desktop-gui/src/runs/error-message.jsx | 1 + .../src/runs/permission-message.jsx | 43 +- packages/desktop-gui/src/runs/runs-api.js | 2 + .../desktop-gui/src/runs/runs-list-item.jsx | 5 +- packages/desktop-gui/src/runs/runs-list.jsx | 21 +- packages/desktop-gui/src/runs/runs-store.js | 4 +- .../src/runs/setup-project-modal.jsx | 2 + .../src/settings/configuration.jsx | 1 + .../desktop-gui/src/settings/record-key.jsx | 1 + packages/desktop-gui/src/specs/specs-list.jsx | 2 + packages/desktop-gui/src/specs/specs-store.js | 5 +- .../desktop-gui/src/update/update-banner.jsx | 29 +- .../driver/test/cypress/fixtures/sinon.html | 4 +- .../test/cypress/fixtures/sync_error.html | 4 +- packages/electron/lib/electron.coffee | 58 ++- packages/example/test/example_spec.js | 1 + packages/launcher/index.js | 3 + packages/launcher/lib/log.ts | 2 +- packages/launcher/lib/windows/index.ts | 2 +- packages/reporter/lib/test-setup.js | 4 +- packages/reporter/src/agents/agents.jsx | 1 + packages/reporter/src/agents/agents.spec.jsx | 13 + .../src/collapsible/collapsible.spec.jsx | 9 + .../reporter/src/commands/command-model.js | 4 +- .../src/commands/command-model.spec.js | 2 + packages/reporter/src/commands/command.jsx | 2 + .../reporter/src/commands/command.spec.jsx | 37 ++ .../reporter/src/errors/an-error.spec.jsx | 6 + .../reporter/src/header/controls.spec.jsx | 28 ++ packages/reporter/src/header/header.spec.jsx | 3 + packages/reporter/src/header/stats-store.js | 2 + .../reporter/src/header/stats-store.spec.js | 2 + packages/reporter/src/header/stats.spec.jsx | 8 + packages/reporter/src/hooks/hook-model.js | 6 +- .../reporter/src/hooks/hook-model.spec.js | 46 +- packages/reporter/src/hooks/hooks.jsx | 1 + packages/reporter/src/hooks/hooks.spec.jsx | 8 + packages/reporter/src/lib/app-state.js | 1 + packages/reporter/src/lib/app-state.spec.js | 22 + packages/reporter/src/lib/err-model.spec.js | 6 + packages/reporter/src/lib/events.js | 3 + packages/reporter/src/lib/events.spec.js | 91 ++-- .../reporter/src/lib/flash-on-click.spec.jsx | 2 + packages/reporter/src/lib/scroller.js | 4 + packages/reporter/src/lib/scroller.spec.js | 11 +- packages/reporter/src/main.spec.jsx | 9 + packages/reporter/src/routes/routes.jsx | 1 + packages/reporter/src/routes/routes.spec.jsx | 16 + .../src/runnables/runnable-and-suite.jsx | 1 + .../src/runnables/runnable-and-suite.spec.jsx | 14 + .../reporter/src/runnables/runnables-store.js | 39 +- .../src/runnables/runnables-store.spec.js | 66 ++- packages/reporter/src/runnables/runnables.jsx | 1 + .../reporter/src/runnables/runnables.spec.jsx | 7 + .../reporter/src/runnables/suite-model.js | 25 +- .../src/runnables/suite-model.spec.js | 13 + packages/reporter/src/test/test-model.js | 21 +- packages/reporter/src/test/test-model.spec.js | 24 +- packages/reporter/src/test/test.jsx | 6 +- packages/reporter/src/test/test.spec.jsx | 17 + packages/runner/scripts/set-zunder-config.js | 1 + packages/runner/src/app/app.jsx | 3 + packages/runner/src/app/app.spec.jsx | 23 + packages/runner/src/app/container.jsx | 1 + packages/runner/src/app/container.spec.jsx | 2 + packages/runner/src/app/resizer.jsx | 3 + .../errors/automation-disconnected.spec.jsx | 2 + .../runner/src/errors/no-automation.spec.jsx | 9 + .../runner/src/errors/script-error.spec.jsx | 1 + packages/runner/src/header/header.spec.jsx | 14 +- packages/runner/src/iframe/aut-iframe.js | 20 +- packages/runner/src/iframe/iframe-model.js | 4 + packages/runner/src/iframe/iframes.jsx | 4 + packages/runner/src/lib/dom.js | 27 +- packages/runner/src/lib/event-manager.js | 26 +- packages/runner/src/lib/logger.js | 11 + packages/runner/src/lib/state.js | 19 +- packages/runner/src/lib/util.js | 7 +- packages/runner/src/main.jsx | 1 + .../selector-playground.jsx | 3 + .../selector-playground.spec.jsx | 24 + .../__snapshots__/2_cookies_spec.coffee.js | 25 +- packages/server/lib/automation/cookies.coffee | 51 +- packages/server/lib/automation/index.coffee | 3 +- packages/server/lib/environment.coffee | 9 +- packages/server/lib/request.coffee | 21 +- packages/server/lib/server.coffee | 19 +- packages/server/lib/socket.coffee | 2 +- packages/server/lib/util/app_data.coffee | 79 --- packages/server/lib/util/app_data.js | 103 ++++ packages/server/lib/util/args.coffee | 227 --------- packages/server/lib/util/args.js | 288 +++++++++++ packages/server/lib/util/blacklist.coffee | 21 - packages/server/lib/util/blacklist.js | 22 + packages/server/lib/util/buffers.coffee | 46 -- packages/server/lib/util/buffers.js | 67 +++ packages/server/lib/util/cache_buster.coffee | 17 - packages/server/lib/util/cache_buster.js | 20 + packages/server/lib/util/child_process.coffee | 4 - packages/server/lib/util/child_process.js | 4 + packages/server/lib/util/ci_provider.coffee | 423 ---------------- packages/server/lib/util/ci_provider.js | 454 ++++++++++++++++++ packages/server/lib/util/coerce.coffee | 13 - packages/server/lib/util/coerce.js | 19 + .../server/lib/util/conditional_stream.coffee | 8 - .../server/lib/util/conditional_stream.js | 11 + packages/server/lib/util/config.coffee | 5 - packages/server/lib/util/config.js | 5 + packages/server/lib/util/connect.coffee | 40 -- packages/server/lib/util/connect.js | 51 ++ packages/server/lib/util/cors.coffee | 51 -- packages/server/lib/util/cors.js | 60 +++ packages/server/lib/util/duration.js | 4 +- packages/server/lib/util/electron_app.coffee | 23 - packages/server/lib/util/electron_app.js | 31 ++ packages/server/lib/util/escape_regexp.coffee | 4 - packages/server/lib/util/escape_regexp.js | 5 + packages/server/lib/util/exit.coffee | 5 - packages/server/lib/util/exit.js | 5 + packages/server/lib/util/file.coffee | 168 ------- packages/server/lib/util/file.js | 241 ++++++++++ packages/server/lib/util/fs.coffee | 31 -- packages/server/lib/util/fs.js | 42 ++ packages/server/lib/util/glob.coffee | 4 - packages/server/lib/util/glob.js | 4 + packages/server/lib/util/headers.coffee | 17 - packages/server/lib/util/headers.js | 21 + .../server/lib/util/http_overrides.coffee | 37 -- packages/server/lib/util/http_overrides.js | 47 ++ packages/server/lib/util/human_time.coffee | 63 --- packages/server/lib/util/human_time.js | 77 +++ .../lib/util/{inject.coffee => inject.js} | 29 +- .../server/lib/util/network_failures.coffee | 48 -- packages/server/lib/util/network_failures.js | 67 +++ packages/server/lib/util/open.coffee | 11 - packages/server/lib/util/open.js | 20 + packages/server/lib/util/origin.coffee | 12 - packages/server/lib/util/origin.js | 20 + packages/server/lib/util/path_helpers.coffee | 76 --- packages/server/lib/util/path_helpers.js | 95 ++++ packages/server/lib/util/progress_bar.coffee | 61 --- packages/server/lib/util/progress_bar.js | 73 +++ packages/server/lib/util/random.coffee | 12 - packages/server/lib/util/random.js | 25 + packages/server/lib/util/rewriter.coffee | 41 -- packages/server/lib/util/rewriter.js | 57 +++ packages/server/lib/util/routes.coffee | 47 -- packages/server/lib/util/routes.js | 70 +++ packages/server/lib/util/saved_state.coffee | 53 -- packages/server/lib/util/saved_state.js | 74 +++ packages/server/lib/util/security.coffee | 29 -- packages/server/lib/util/security.js | 43 ++ .../server/lib/util/server_destroy.coffee | 10 - packages/server/lib/util/server_destroy.js | 19 + packages/server/lib/util/settings.coffee | 142 ------ packages/server/lib/util/settings.js | 210 ++++++++ packages/server/lib/util/shell.coffee | 83 ---- packages/server/lib/util/shell.js | 123 +++++ packages/server/lib/util/specs.coffee | 124 ----- packages/server/lib/util/specs.js | 154 ++++++ packages/server/lib/util/status_code.coffee | 15 - packages/server/lib/util/status_code.js | 25 + packages/server/lib/util/system.coffee | 29 -- packages/server/lib/util/system.js | 47 ++ packages/server/lib/util/terminal.coffee | 195 -------- packages/server/lib/util/terminal.js | 238 +++++++++ packages/server/lib/util/trash.coffee | 14 - packages/server/lib/util/trash.js | 23 + packages/server/lib/util/uri.coffee | 35 -- packages/server/lib/util/uri.js | 47 ++ packages/server/lib/util/validation.coffee | 78 --- packages/server/lib/util/validation.js | 120 +++++ packages/server/package.json | 5 +- .../server/test/e2e/2_cookies_spec.coffee | 40 +- .../integration/http_requests_spec.coffee | 2 +- packages/server/test/scripts/run.js | 30 +- .../cypress/integration/cookies_spec.coffee | 187 +++++--- .../server/absolute_url_expected.html | 2 +- .../fixtures/server/expected_head_inject.html | 2 +- .../server/expected_https_inject.html | 2 +- .../server/expected_no_head_tag_inject.html | 2 +- packages/static/package.json | 1 + packages/ts/package.json | 3 +- scripts/binary.js | 6 +- scripts/check-deps.js | 40 +- scripts/debug.js | 34 ++ scripts/link-packages.js | 27 +- scripts/run-all.js | 62 ++- scripts/run-cypress-tests.js | 105 ++-- scripts/test-debug-package.js | 30 ++ scripts/test-other-projects.js | 14 +- scripts/test-unique-npm-and-binary.js | 2 + scripts/utils.js | 4 + scripts/win-appveyor-build.js | 16 +- 245 files changed, 5045 insertions(+), 2955 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 .vscode/terminals.json create mode 100644 bulk-decaffeinate.config.js create mode 100644 jsconfig.json delete mode 100644 packages/server/lib/util/app_data.coffee create mode 100644 packages/server/lib/util/app_data.js delete mode 100644 packages/server/lib/util/args.coffee create mode 100644 packages/server/lib/util/args.js delete mode 100644 packages/server/lib/util/blacklist.coffee create mode 100644 packages/server/lib/util/blacklist.js delete mode 100644 packages/server/lib/util/buffers.coffee create mode 100644 packages/server/lib/util/buffers.js delete mode 100644 packages/server/lib/util/cache_buster.coffee create mode 100644 packages/server/lib/util/cache_buster.js delete mode 100644 packages/server/lib/util/child_process.coffee create mode 100644 packages/server/lib/util/child_process.js delete mode 100644 packages/server/lib/util/ci_provider.coffee create mode 100644 packages/server/lib/util/ci_provider.js delete mode 100644 packages/server/lib/util/coerce.coffee create mode 100644 packages/server/lib/util/coerce.js delete mode 100644 packages/server/lib/util/conditional_stream.coffee create mode 100644 packages/server/lib/util/conditional_stream.js delete mode 100644 packages/server/lib/util/config.coffee create mode 100644 packages/server/lib/util/config.js delete mode 100644 packages/server/lib/util/connect.coffee create mode 100644 packages/server/lib/util/connect.js delete mode 100644 packages/server/lib/util/cors.coffee create mode 100644 packages/server/lib/util/cors.js delete mode 100644 packages/server/lib/util/electron_app.coffee create mode 100644 packages/server/lib/util/electron_app.js delete mode 100644 packages/server/lib/util/escape_regexp.coffee create mode 100644 packages/server/lib/util/escape_regexp.js delete mode 100644 packages/server/lib/util/exit.coffee create mode 100644 packages/server/lib/util/exit.js delete mode 100644 packages/server/lib/util/file.coffee create mode 100644 packages/server/lib/util/file.js delete mode 100644 packages/server/lib/util/fs.coffee create mode 100644 packages/server/lib/util/fs.js delete mode 100644 packages/server/lib/util/glob.coffee create mode 100644 packages/server/lib/util/glob.js delete mode 100644 packages/server/lib/util/headers.coffee create mode 100644 packages/server/lib/util/headers.js delete mode 100644 packages/server/lib/util/http_overrides.coffee create mode 100644 packages/server/lib/util/http_overrides.js delete mode 100644 packages/server/lib/util/human_time.coffee create mode 100644 packages/server/lib/util/human_time.js rename packages/server/lib/util/{inject.coffee => inject.js} (66%) delete mode 100644 packages/server/lib/util/network_failures.coffee create mode 100644 packages/server/lib/util/network_failures.js delete mode 100644 packages/server/lib/util/open.coffee create mode 100644 packages/server/lib/util/open.js delete mode 100644 packages/server/lib/util/origin.coffee create mode 100644 packages/server/lib/util/origin.js delete mode 100644 packages/server/lib/util/path_helpers.coffee create mode 100644 packages/server/lib/util/path_helpers.js delete mode 100644 packages/server/lib/util/progress_bar.coffee create mode 100644 packages/server/lib/util/progress_bar.js delete mode 100644 packages/server/lib/util/random.coffee create mode 100644 packages/server/lib/util/random.js delete mode 100644 packages/server/lib/util/rewriter.coffee create mode 100644 packages/server/lib/util/rewriter.js delete mode 100644 packages/server/lib/util/routes.coffee create mode 100644 packages/server/lib/util/routes.js delete mode 100644 packages/server/lib/util/saved_state.coffee create mode 100644 packages/server/lib/util/saved_state.js delete mode 100644 packages/server/lib/util/security.coffee create mode 100644 packages/server/lib/util/security.js delete mode 100644 packages/server/lib/util/server_destroy.coffee create mode 100644 packages/server/lib/util/server_destroy.js delete mode 100644 packages/server/lib/util/settings.coffee create mode 100644 packages/server/lib/util/settings.js delete mode 100644 packages/server/lib/util/shell.coffee create mode 100644 packages/server/lib/util/shell.js delete mode 100644 packages/server/lib/util/specs.coffee create mode 100644 packages/server/lib/util/specs.js delete mode 100644 packages/server/lib/util/status_code.coffee create mode 100644 packages/server/lib/util/status_code.js delete mode 100644 packages/server/lib/util/system.coffee create mode 100644 packages/server/lib/util/system.js delete mode 100644 packages/server/lib/util/terminal.coffee create mode 100644 packages/server/lib/util/terminal.js delete mode 100644 packages/server/lib/util/trash.coffee create mode 100644 packages/server/lib/util/trash.js delete mode 100644 packages/server/lib/util/uri.coffee create mode 100644 packages/server/lib/util/uri.js delete mode 100644 packages/server/lib/util/validation.coffee create mode 100644 packages/server/lib/util/validation.js create mode 100644 scripts/debug.js create mode 100644 scripts/test-debug-package.js diff --git a/.eslintrc b/.eslintrc index ae9c088641..0a9455e9f1 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,8 +2,67 @@ "extends": [ "plugin:cypress-dev/general" ], + "rules": { + "no-multiple-empty-lines": ["error", { "max": 1 } ], + "no-else-return": [ "error", { "allowElseIf": false } ], + "brace-style": ["error", "1tbs", { "allowSingleLine": false }], + "no-unneeded-ternary": ["error"], + "array-bracket-newline": ["error", "consistent"], + "arrow-body-style": ["error", "always"], + "padding-line-between-statements": [ + "error", + { + "blankLine": "always", + "prev": "*", + "next": "return" + }, + { + "blankLine": "always", + "prev": [ + "const", + "let", + "var", + "if", + "while", + "export", + "cjs-export", + "import", + "cjs-import" + ], + "next": "*" + }, + { + "blankLine": "any", + "prev": [ + "const", + "let", + "var", + "import", + "cjs-import" + ], + "next": [ + "const", + "let", + "var", + "import", + "cjs-import" + ] + } + ] + }, "env": { "es6": true, "node": true + }, + "parserOptions": { + "ecmaFeatures": { + "legacyDecorators": true + } + }, + "overrides": { + "files": ["**/*.jsx"], + "rules": { + "arrow-body-style": "off", + } } } diff --git a/.gitignore b/.gitignore index 2ea3a4839a..15ccb3d6fc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,12 +8,12 @@ dist dist-* build .history -.vscode .publish _test-output cypress.zip tmp/ .nyc_output +.vscode/settings.json # from extension Cached Theme.pak diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..06947329b1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach by Process ID", + "processId": "${command:PickProcess}" + }, + { + "type": "node", + "request": "launch", + "name": "test: active", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "run", + "test-debug-package", + ], + "args": [ + "${file}" + ], + "port": 5566, + "console": "integratedTerminal" + }, + { + "type": "node", + "request": "attach", + "name": "electron", + "port": 5567, + }, + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000..e0be7458b6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,29 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "decaffeinate-bulk file", + "type": "shell", + "command": "npm run decaffeinate-bulk -- --file ${file} convert", + "problemMatcher": [] + }, + { + "label": "decaffeinate-bulk dir", + "type": "shell", + "command": "npm run decaffeinate-bulk -- --dir ${fileDirname} convert", + "problemMatcher": [] + }, + { + "label": "decaffeinate", + "type": "shell", + "command": "npm run decaffeinate -- ${file}" + }, + { + "label": "jscodeshift", + "type": "shell", + "command": "npm run jscodeshift -- ${file}" + } + ] +} diff --git a/.vscode/terminals.json b/.vscode/terminals.json new file mode 100644 index 0000000000..6f82f2758c --- /dev/null +++ b/.vscode/terminals.json @@ -0,0 +1,56 @@ +{ + "autorun": false, + "terminals": [ + { + "name": "cypress open", + "focus": true, + "onlySingle": true, + "execute": true, + "command": "npm run cypress:open" + }, + { + "name": "cypress run", + "focus": true, + "onlySingle": true, + "execute": false, + "command": "npm run cypress:run -- --project ../project" + }, + { + "name": "packages/server test-watch", + "focus": true, + "onlySingle": true, + "execute": false, + "cwd": "[workspaceFolder]/packages/server", + "command": "npm run test-watch -- [file]" + }, + { + "name": "packages/server test-e2e", + "focus": true, + "onlySingle": true, + "execute": false, + "cwd": "[workspaceFolder]/packages/server", + "command": "npm run test-e2e -- --spec name" + }, + { + "name": "packages/runner watch", + "focus": true, + "onlySingle": true, + "cwd": "[workspaceFolder]/packages/runner", + "command": "npm run watch" + }, + { + "name": "packages/driver cypress open", + "focus": true, + "onlySingle": true, + "cwd": "[workspaceFolder]/packages/driver", + "command": "npm run cypress:open" + }, + { + "name": "packages/desktop-gui cypress open", + "focus": true, + "onlySingle": true, + "cwd": "[workspaceFolder]/packages/desktop-gui", + "command": "npm run cypress:open" + } + ] +} diff --git a/bulk-decaffeinate.config.js b/bulk-decaffeinate.config.js new file mode 100644 index 0000000000..7f81e1d7f5 --- /dev/null +++ b/bulk-decaffeinate.config.js @@ -0,0 +1,17 @@ +const path = require('path') + +module.exports = { + decaffeinateArgs: [ + '--use-cs2', + '--loose', + ], + jscodeshiftScripts: [ + path.resolve('node_modules', 'js-codemod', 'transforms', 'arrow-function.js'), + path.resolve('node_modules', 'js-codemod', 'transforms', 'arrow-function-arguments.js'), + path.resolve('node_modules', 'js-codemod', 'transforms', 'no-vars.js'), + path.resolve('node_modules', 'jscodemods', 'transforms', 'fix-class-assign-construct.js'), + path.resolve('node_modules', 'jscodemods', 'decaffeinate', 'fix-multi-assign-class-export.js'), + path.resolve('node_modules', 'jscodemods', 'decaffeinate', 'fix-implicit-return-assignment.js'), + path.resolve('node_modules', 'jscodemods', 'decaffeinate', 'fix-existential-conditional-assignment.js'), + ], +} diff --git a/cli/lib/cli.js b/cli/lib/cli.js index ca72d301cd..48914b732e 100644 --- a/cli/lib/cli.js +++ b/cli/lib/cli.js @@ -10,6 +10,7 @@ const cache = require('./tasks/cache') // we want to print help for the current command and exit with an error function unknownOption (flag, type = 'option') { if (this._allowUnknownOption) return + logger.error() logger.error(` error: unknown ${type}:`, flag) logger.error() @@ -87,6 +88,7 @@ function includesVersion (args) { function showVersions () { debug('printing Cypress version') + return require('./exec/versions') .getVersions() .then((versions = {}) => { @@ -187,6 +189,7 @@ module.exports = { const defaultOpts = { force: true, welcomeMessage: false } const parsedOpts = parseOpts(opts) const options = _.extend(parsedOpts, defaultOpts) + require('./tasks/verify') .start(options) .catch(util.logErrorExit1) @@ -203,6 +206,7 @@ module.exports = { if (opts.command || !_.includes(['list', 'path', 'clear'], opts)) { unknownOption.call(this, `cache ${opts}`, 'sub-command') } + cache[opts]() }) @@ -219,10 +223,12 @@ module.exports = { // Deprecated Catches const firstCommand = args[2] + if (!_.includes(knownCommands, firstCommand)) { debug('unknown command %s', firstCommand) logger.error('Unknown command', `"${firstCommand}"`) program.outputHelp() + return util.exit(1) } @@ -233,7 +239,9 @@ module.exports = { // so we have to manually catch '-v, --version' return showVersions() } + debug('program parsing arguments') + return program.parse(args) }, } diff --git a/cli/lib/cypress.js b/cli/lib/cypress.js index 9fca6b4a54..ad49ce60b1 100644 --- a/cli/lib/cypress.js +++ b/cli/lib/cypress.js @@ -11,6 +11,7 @@ const util = require('./util') const cypressModuleApi = { open (options = {}) { options = util.normalizeModuleOptions(options) + return open.start(options) }, @@ -31,6 +32,7 @@ const cypressModuleApi = { message: 'Could not find Cypress test run results', } } + return output }) }) diff --git a/cli/lib/errors.js b/cli/lib/errors.js index 54ed4d7bb3..9e952ea0f7 100644 --- a/cli/lib/errors.js +++ b/cli/lib/errors.js @@ -25,16 +25,19 @@ const failedUnzip = { `, } -const missingApp = (binaryDir) => ({ - description: `No version of Cypress is installed in: ${chalk.cyan(binaryDir)}`, - solution: stripIndent` +const missingApp = (binaryDir) => { + return { + description: `No version of Cypress is installed in: ${chalk.cyan(binaryDir)}`, + solution: stripIndent` \nPlease reinstall Cypress by running: ${chalk.cyan('cypress install')} `, -}) + } +} -const binaryNotExecutable = (executable) => ({ - description: `Cypress cannot run because the binary does not have executable permissions: ${executable}`, - solution: stripIndent`\n +const binaryNotExecutable = (executable) => { + return { + description: `Cypress cannot run because the binary does not have executable permissions: ${executable}`, + solution: stripIndent`\n Reasons this may happen: - node was installed as 'root' or with 'sudo' @@ -42,12 +45,13 @@ const binaryNotExecutable = (executable) => ({ Please check that you have the appropriate user permissions. `, -}) + } +} - -const notInstalledCI = (executable) => ({ - description: 'The cypress npm package is installed, but the Cypress binary is missing.', - solution: stripIndent`\n +const notInstalledCI = (executable) => { + return { + description: 'The cypress npm package is installed, but the Cypress binary is missing.', + solution: stripIndent`\n We expected the binary to be installed here: ${chalk.cyan(executable)} Reasons it may be missing: @@ -61,7 +65,8 @@ const notInstalledCI = (executable) => ({ ${chalk.blue('https://on.cypress.io/not-installed-ci-error')} `, -}) + } +} const nonZeroExitCodeXvfb = { description: 'XVFB exited with a non zero exit code.', @@ -146,6 +151,7 @@ const removed = { const CYPRESS_RUN_BINARY = { notValid: (value) => { const properFormat = `**/${state.getPlatformExecutable()}` + return { description: `Could not run binary set by environment variable CYPRESS_RUN_BINARY=${value}`, solution: `Ensure the environment variable is a path to the Cypress binary, matching ${properFormat}`, @@ -155,15 +161,19 @@ const CYPRESS_RUN_BINARY = { function getPlatformInfo () { return util.getOsVersionAsync() - .then((version) => stripIndent` + .then((version) => { + return stripIndent` Platform: ${os.platform()} (${version}) Cypress Version: ${util.pkgVersion()} - `) + ` + }) } function addPlatformInformation (info) { return getPlatformInfo() - .then((platform) => merge(info, { platform })) + .then((platform) => { + return merge(info, { platform }) + }) } function formErrorText (info, msg) { @@ -216,13 +226,16 @@ function formErrorText (info, msg) { const raise = (text) => { const err = new Error(text) + err.known = true throw err } -const throwFormErrorText = (info) => (msg) => { - return formErrorText(info, msg) - .then(raise) +const throwFormErrorText = (info) => { + return (msg) => { + return formErrorText(info, msg) + .then(raise) + } } module.exports = { diff --git a/cli/lib/exec/spawn.js b/cli/lib/exec/spawn.js index a6f6364c7d..e49f0059cd 100644 --- a/cli/lib/exec/spawn.js +++ b/cli/lib/exec/spawn.js @@ -88,6 +88,7 @@ module.exports = { options.env = _.extend({}, options.env, overrides) const child = cp.spawn(executable, args, options) + child.on('close', resolve) child.on('error', reject) @@ -137,8 +138,9 @@ module.exports = { return xvfb.start() .then(userFriendlySpawn) .finally(xvfb.stop) - } else { - return userFriendlySpawn() } + + return userFriendlySpawn() + }, } diff --git a/cli/lib/exec/versions.js b/cli/lib/exec/versions.js index dd299a1f2a..58ed2e1726 100644 --- a/cli/lib/exec/versions.js +++ b/cli/lib/exec/versions.js @@ -11,11 +11,13 @@ const getVersions = () => { if (util.getEnv('CYPRESS_RUN_BINARY')) { let envBinaryPath = path.resolve(util.getEnv('CYPRESS_RUN_BINARY')) + return state.parseRealPlatformBinaryFolderAsync(envBinaryPath) .then((envBinaryDir) => { if (!envBinaryDir) { return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))() } + debug('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir) return envBinaryDir @@ -24,6 +26,7 @@ const getVersions = () => { return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(err.message) }) } + return state.getBinaryDir() }) .then(state.getBinaryPkgVersionAsync) diff --git a/cli/lib/exec/xvfb.js b/cli/lib/exec/xvfb.js index 148525b2f0..6d66104a30 100644 --- a/cli/lib/exec/xvfb.js +++ b/cli/lib/exec/xvfb.js @@ -22,6 +22,7 @@ module.exports = { start () { debug('Starting XVFB') + return xvfb.startAsync() .catch({ nonZeroExitCode: true }, throwFormErrorText(errors.nonZeroExitCodeXvfb)) .catch((err) => { @@ -35,6 +36,7 @@ module.exports = { stop () { debug('Stopping XVFB') + return xvfb.stopAsync() }, @@ -48,6 +50,7 @@ module.exports = { .then(R.T) .catch((err) => { debug('Could not verify xvfb: %s', err.message) + return false }) .finally(xvfb.stopAsync) diff --git a/cli/lib/logger.js b/cli/lib/logger.js index 7da7df0944..0ab54f3111 100644 --- a/cli/lib/logger.js +++ b/cli/lib/logger.js @@ -3,7 +3,9 @@ const chalk = require('chalk') let logs = [] -const logLevel = () => (process.env.npm_config_loglevel || 'notice') +const logLevel = () => { + return (process.env.npm_config_loglevel || 'notice') +} const error = (...messages) => { logs.push(messages.join(' ')) @@ -12,12 +14,14 @@ const error = (...messages) => { const warn = (...messages) => { if (logLevel() === 'silent') return + logs.push(messages.join(' ')) console.log(chalk.yellow(...messages)) // eslint-disable-line no-console } const log = (...messages) => { if (logLevel() === 'silent' || logLevel() === 'warn') return + logs.push(messages.join(' ')) console.log(...messages) // eslint-disable-line no-console } @@ -26,6 +30,7 @@ const log = (...messages) => { // on each one to allow easy unit testing for specific message const logLines = (text) => { const lines = text.split('\n') + R.forEach(log, lines) } diff --git a/cli/lib/tasks/cache.js b/cli/lib/tasks/cache.js index a194e80402..0c53f353f0 100644 --- a/cli/lib/tasks/cache.js +++ b/cli/lib/tasks/cache.js @@ -5,6 +5,7 @@ const util = require('../util') const path = () => { logger.log(state.getCacheDir()) + return undefined } diff --git a/cli/lib/tasks/download.js b/cli/lib/tasks/download.js index 741a1e674c..39b6bd48bc 100644 --- a/cli/lib/tasks/download.js +++ b/cli/lib/tasks/download.js @@ -32,27 +32,32 @@ const prepend = (urlPath) => { const endpoint = url.resolve(getBaseUrl(), urlPath) const platform = os.platform() const arch = os.arch() + return `${endpoint}?platform=${platform}&arch=${arch}` } const getUrl = (version) => { if (is.url(version)) { debug('version is already an url', version) + return version } + return version ? prepend(`desktop/${version}`) : prepend('desktop') } -const statusMessage = (err) => - (err.statusCode +const statusMessage = (err) => { + return (err.statusCode ? [err.statusCode, err.statusMessage].join(' - ') : err.toString()) +} const prettyDownloadErr = (err, version) => { const msg = stripIndent` URL: ${getUrl(version)} ${statusMessage(err)} ` + debug(msg) return throwFormErrorText(errors.failedDownload)(msg) @@ -72,6 +77,7 @@ const downloadFromUrl = ({ url, downloadDestination, progress }) => { url, followRedirect (response) { const version = response.headers['x-version'] + debug('redirect version:', version) if (version) { // set the version in options if we have one. @@ -136,11 +142,15 @@ const start = ({ version, downloadDestination, progress }) => { if (!downloadDestination) { la(is.unemptyString(downloadDestination), 'missing download dir', arguments) } + if (!progress) { - progress = { onProgress: () => ({}) } + progress = { onProgress: () => { + return {} + } } } const url = getUrl(version) + progress.throttle = 100 debug('needed Cypress version: %s', version) diff --git a/cli/lib/tasks/install.js b/cli/lib/tasks/install.js index 07ff631ca4..62f7780803 100644 --- a/cli/lib/tasks/install.js +++ b/cli/lib/tasks/install.js @@ -81,6 +81,7 @@ const downloadAndUnzip = ({ version, installDir, downloadDir }) => { return download.start({ version, downloadDestination, progress }) .then((redirectVersion) => { if (redirectVersion) version = redirectVersion + debug(`finished downloading file: ${downloadDestination}`) }) .then(() => { @@ -105,6 +106,7 @@ const downloadAndUnzip = ({ version, installDir, downloadDir }) => { const cleanup = () => { debug('removing zip file %s', downloadDestination) + return fs.removeAsync(downloadDestination) } @@ -145,12 +147,14 @@ const start = (options = {}) => { const pkgVersion = util.pkgVersion() let needVersion = pkgVersion + debug('version in package.json is', needVersion) // let this environment variable reset the binary version we need if (util.getEnv('CYPRESS_INSTALL_BINARY')) { const envVarVersion = util.getEnv('CYPRESS_INSTALL_BINARY') + debug('using environment variable CYPRESS_INSTALL_BINARY %s', envVarVersion) if (envVarVersion === '0') { @@ -159,6 +163,7 @@ const start = (options = {}) => { stripIndent` ${chalk.yellow('Note:')} Skipping binary installation: Environment variable CYPRESS_INSTALL_BINARY = 0.`) logger.log() + return Promise.resolve() } @@ -173,6 +178,7 @@ const start = (options = {}) => { if (util.getEnv('CYPRESS_CACHE_FOLDER')) { const envCache = util.getEnv('CYPRESS_CACHE_FOLDER') + logger.log( stripIndent` ${chalk.yellow('Note:')} Overriding Cypress cache directory to: ${chalk.cyan(envCache)} @@ -194,11 +200,14 @@ const start = (options = {}) => { ${err.message} `) }) - .then(() => state.getBinaryPkgVersionAsync(binaryDir)) + .then(() => { + return state.getBinaryPkgVersionAsync(binaryDir) + }) .then((binaryVersion) => { if (!binaryVersion) { debug('no binary installed under cli version') + return true } @@ -212,22 +221,24 @@ const start = (options = {}) => { if (options.force) { debug('performing force install over existing binary') + return true } if ((binaryVersion === needVersion) || !util.isSemver(needVersion)) { // our version matches, tell the user this is a noop alreadyInstalledMsg() + return false } - return true }) .then((shouldInstall) => { // noop if we've been told not to download if (!shouldInstall) { debug('Not downloading or installing binary') + return } @@ -254,6 +265,7 @@ const start = (options = {}) => { } const possibleFile = util.formAbsolutePath(needVersion) + debug('checking local file', possibleFile, 'cwd', process.cwd()) return fs.pathExistsAsync(possibleFile) @@ -263,16 +275,19 @@ const start = (options = {}) => { if (exists && path.extname(possibleFile) === '.zip') { return possibleFile } + return false }) }) .then((pathToLocalFile) => { if (pathToLocalFile) { const absolutePath = path.resolve(needVersion) + debug('found local file at', absolutePath) debug('skipping download') const rendererOptions = getRendererOptions() + return new Listr([unzipTask({ progress: { throttle: 100, @@ -292,10 +307,13 @@ const start = (options = {}) => { debug('preparing to download and unzip version ', needVersion, 'to path', installDir) const downloadDir = os.tmpdir() + return downloadAndUnzip({ version: needVersion, installDir, downloadDir }) }) // delay 1 sec for UX, unless we are testing - .then(() => Promise.delay(1000)) + .then(() => { + return Promise.delay(1000) + }) .then(displayCompletionMsg) }) } @@ -304,22 +322,24 @@ module.exports = { start, } -const unzipTask = ({ zipFilePath, installDir, progress, rendererOptions }) => ({ - title: util.titleize('Unzipping Cypress'), - task: (ctx, task) => { +const unzipTask = ({ zipFilePath, installDir, progress, rendererOptions }) => { + return { + title: util.titleize('Unzipping Cypress'), + task: (ctx, task) => { // as our unzip progresses indicate the status - progress.onProgress = progessify(task, 'Unzipping Cypress') + progress.onProgress = progessify(task, 'Unzipping Cypress') - return unzip.start({ zipFilePath, installDir, progress }) - .then(() => { - util.setTaskTitle( - task, - util.titleize(chalk.green('Unzipped Cypress')), - rendererOptions.renderer - ) - }) - }, -}) + return unzip.start({ zipFilePath, installDir, progress }) + .then(() => { + util.setTaskTitle( + task, + util.titleize(chalk.green('Unzipped Cypress')), + rendererOptions.renderer + ) + }) + }, + } +} const progessify = (task, title) => { // return higher order function @@ -342,9 +362,11 @@ const progessify = (task, title) => { // the default const getRendererOptions = () => { let renderer = util.isCi() ? verbose : 'default' + if (logger.logLevel() === 'silent') { renderer = 'silent' } + return { renderer, } diff --git a/cli/lib/tasks/state.js b/cli/lib/tasks/state.js index 1104f00cc4..eb887c46b9 100644 --- a/cli/lib/tasks/state.js +++ b/cli/lib/tasks/state.js @@ -8,6 +8,7 @@ const util = require('../util') const getPlatformExecutable = () => { const platform = os.platform() + switch (platform) { case 'darwin': return 'Contents/MacOS/Cypress' case 'linux': return 'Cypress' @@ -19,6 +20,7 @@ const getPlatformExecutable = () => { const getPlatFormBinaryFolder = () => { const platform = os.platform() + switch (platform) { case 'darwin': return 'Cypress.app' case 'linux': return 'Cypress' @@ -30,6 +32,7 @@ const getPlatFormBinaryFolder = () => { const getBinaryPkgPath = (binaryDir) => { const platform = os.platform() + switch (platform) { case 'darwin': return path.join(binaryDir, 'Contents', 'Resources', 'app', 'package.json') case 'linux': return path.join(binaryDir, 'resources', 'app', 'package.json') @@ -52,11 +55,14 @@ const getVersionDir = (version = util.pkgVersion()) => { const getCacheDir = () => { let cache_directory = util.getCacheDir() + if (util.getEnv('CYPRESS_CACHE_FOLDER')) { const envVarCacheDir = util.getEnv('CYPRESS_CACHE_FOLDER') + debug('using environment variable CYPRESS_CACHE_FOLDER %s', envVarCacheDir) cache_directory = path.resolve(envVarCacheDir) } + return cache_directory } @@ -67,9 +73,11 @@ const parseRealPlatformBinaryFolderAsync = (binaryPath) => { if (!realPath.toString().endsWith(getPlatformExecutable())) { return false } + if (os.platform() === 'darwin') { return path.resolve(realPath, '..', '..', '..') } + return path.resolve(realPath, '..') }) } @@ -86,6 +94,7 @@ const getBinaryStateContentsAsync = (binaryDir) => { return fs.readJsonAsync(getBinaryStatePath(binaryDir)) .catch({ code: 'ENOENT' }, SyntaxError, () => { debug('could not read binary_state.json file') + return {} }) } @@ -119,18 +128,20 @@ const getPathToExecutable = (binaryDir) => { const getBinaryPkgVersionAsync = (binaryDir) => { const pathToPackageJson = getBinaryPkgPath(binaryDir) + debug('Reading binary package.json from:', pathToPackageJson) + return fs.pathExistsAsync(pathToPackageJson) .then((exists) => { if (!exists) { return null } + return fs.readJsonAsync(pathToPackageJson) .get('version') }) } - module.exports = { getPathToExecutable, getPlatformExecutable, diff --git a/cli/lib/tasks/unzip.js b/cli/lib/tasks/unzip.js index 8b443672ce..a7a7124290 100644 --- a/cli/lib/tasks/unzip.js +++ b/cli/lib/tasks/unzip.js @@ -27,6 +27,7 @@ const unzip = ({ zipFilePath, installDir, progress }) => { return new Promise((resolve, reject) => { return yauzl.open(zipFilePath, (err, zipFile) => { if (err) return reject(err) + // debug('zipfile.paths:', zipFile) // zipFile.on('entry', debug) // debug(zipFile.readEntry()) @@ -34,7 +35,6 @@ const unzip = ({ zipFilePath, installDir, progress }) => { debug('zipFile entries count', total) - const started = new Date() let percent = 0 @@ -59,7 +59,9 @@ const unzip = ({ zipFilePath, installDir, progress }) => { const unzipWithNode = () => { const endFn = (err) => { - if (err) { return reject(err) } + if (err) { + return reject(err) + } return resolve() } @@ -81,10 +83,9 @@ const unzip = ({ zipFilePath, installDir, progress }) => { const copyingFileRe = /^copying file/ const sp = cp.spawn('ditto', ['-xkV', zipFilePath, installDir]) - sp.on('error', () => + // f-it just unzip with node - unzipWithNode() - ) + sp.on('error', unzipWithNode) sp.on('close', (code) => { if (code === 0) { @@ -125,12 +126,17 @@ const unzip = ({ zipFilePath, installDir, progress }) => { const start = ({ zipFilePath, installDir, progress }) => { la(is.unemptyString(installDir), 'missing installDir') - if (!progress) progress = { onProgress: () => ({}) } + if (!progress) { + progress = { onProgress: () => { + return {} + } } + } return fs.pathExists(installDir) .then((exists) => { if (exists) { debug('removing existing unzipped binary', installDir) + return fs.removeAsync(installDir) } }) diff --git a/cli/lib/tasks/verify.js b/cli/lib/tasks/verify.js index bf1f52ed41..ac6341731e 100644 --- a/cli/lib/tasks/verify.js +++ b/cli/lib/tasks/verify.js @@ -7,7 +7,6 @@ const { stripIndent } = require('common-tags') const Promise = require('bluebird') const logSymbols = require('log-symbols') - const { throwFormErrorText, errors } = require('../errors') const util = require('../util') const logger = require('../logger') @@ -16,7 +15,9 @@ const state = require('./state') const checkExecutable = (binaryDir) => { const executable = state.getPathToExecutable(binaryDir) + debug('checking if executable exists', executable) + return util.isExecutableAsync(executable) .then((isExecutable) => { debug('Binary is executable? :', isExecutable) @@ -28,6 +29,7 @@ const checkExecutable = (binaryDir) => { if (util.isCi()) { return throwFormErrorText(errors.notInstalledCI(executable))() } + return throwFormErrorText(errors.missingApp(binaryDir))(stripIndent` Cypress executable not found at: ${chalk.cyan(executable)} `) @@ -37,31 +39,37 @@ const checkExecutable = (binaryDir) => { const runSmokeTest = (binaryDir) => { debug('running smoke test') const cypressExecPath = state.getPathToExecutable(binaryDir) + debug('using Cypress executable %s', cypressExecPath) const onXvfbError = (err) => { debug('caught xvfb error %s', err.message) + return throwFormErrorText(errors.missingXvfb)(`Caught error trying to run XVFB: "${err.message}"`) } const onSmokeTestError = (err) => { debug('Smoke test failed:', err) + return throwFormErrorText(errors.missingDependency)(err.stderr || err.message) } const needsXvfb = xvfb.isNeeded() + debug('needs XVFB?', needsXvfb) const spawn = () => { const random = _.random(0, 1000) const args = ['--smoke-test', `--ping=${random}`] const smokeTestCommand = `${cypressExecPath} ${args.join(' ')}` + debug('smoke test command:', smokeTestCommand) return Promise.resolve(util.exec(cypressExecPath, args)) .catch(onSmokeTestError) .then((result) => { const smokeTestReturned = result.stdout + debug('smoke test stdout "%s"', smokeTestReturned) if (!util.stdoutLineMatches(String(random), smokeTestReturned)) { @@ -85,15 +93,15 @@ const runSmokeTest = (binaryDir) => { return xvfb.stop() .catch(onXvfbError) }) - } else { - return spawn() } + + return spawn() + } function testBinary (version, binaryDir) { debug('running binary verification check', version) - logger.log(stripIndent` It looks like this is your first time using Cypress: ${chalk.cyan(version)} `) @@ -104,18 +112,19 @@ function testBinary (version, binaryDir) { // the verbose renderer else use // the default let renderer = util.isCi() ? verbose : 'default' + if (logger.logLevel() === 'silent') renderer = 'silent' const rendererOptions = { renderer, } - const tasks = new Listr([ { title: util.titleize('Verifying Cypress can run', chalk.gray(binaryDir)), task: (ctx, task) => { debug('clearing out the verified version') + return state.clearBinaryStateAsync(binaryDir) .then(() => { return Promise.all([ @@ -125,6 +134,7 @@ function testBinary (version, binaryDir) { }) .then(() => { debug('write verified: true') + return state.writeBinaryVerifiedAsync(true, binaryDir) }) .then(() => { @@ -151,6 +161,7 @@ const maybeVerify = (installedVersion, binaryDir, options = {}) => { debug('is Verified ?', isVerified) let shouldVerify = !isVerified + // force verify if options.force if (options.force) { debug('force verify') @@ -182,6 +193,7 @@ const start = (options = {}) => { const parseBinaryEnvVar = () => { const envBinaryPath = util.getEnv('CYPRESS_RUN_BINARY') + debug('CYPRESS_RUN_BINARY exists, =', envBinaryPath) logger.log(stripIndent` ${chalk.yellow('Note:')} You have set the environment variable: ${chalk.white('CYPRESS_RUN_BINARY=')}${chalk.cyan(envBinaryPath)}: @@ -199,11 +211,14 @@ const start = (options = {}) => { `) } }) - .then(() => state.parseRealPlatformBinaryFolderAsync(envBinaryPath)) + .then(() => { + return state.parseRealPlatformBinaryFolderAsync(envBinaryPath) + }) .then((envBinaryDir) => { if (!envBinaryDir) { return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))() } + debug('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir) binaryDir = envBinaryDir @@ -213,20 +228,26 @@ const start = (options = {}) => { }) } - return Promise.try(() => { debug('checking environment variables') if (util.getEnv('CYPRESS_RUN_BINARY')) { return parseBinaryEnvVar() } }) - .then(() => checkExecutable(binaryDir)) - .tap(() => debug('binaryDir is ', binaryDir)) - .then(() => state.getBinaryPkgVersionAsync(binaryDir)) + .then(() => { + return checkExecutable(binaryDir) + }) + .tap(() => { + return debug('binaryDir is ', binaryDir) + }) + .then(() => { + return state.getBinaryPkgVersionAsync(binaryDir) + }) .then((binaryVersion) => { if (!binaryVersion) { debug('no Cypress binary found for cli version ', packageVersion) + return throwFormErrorText(errors.missingApp(binaryDir))(` Cannot read binary version from: ${chalk.cyan(state.getBinaryPkgPath(binaryDir))} `) @@ -247,7 +268,6 @@ const start = (options = {}) => { These versions may not work properly together. `) - logger.log() } diff --git a/cli/lib/util.js b/cli/lib/util.js index d342c20917..634bb4a854 100644 --- a/cli/lib/util.js +++ b/cli/lib/util.js @@ -29,6 +29,7 @@ function normalizeModuleOptions (options = {}) { function stdoutLineMatches (expectedLine, stdout) { const lines = stdout.split('\n').map(R.trim) const lineMatches = R.equals(expectedLine) + return lines.some(lineMatches) } @@ -179,11 +180,16 @@ const util = { return Promise.try(() => { if (os.platform() === 'linux') { return getosAsync() - .then((osInfo) => [osInfo.dist, osInfo.release].join(' - ')) - .catch(() => os.release()) - } else { - return os.release() + .then((osInfo) => { + return [osInfo.dist, osInfo.release].join(' - ') + }) + .catch(() => { + return os.release() + }) } + + return os.release() + }) }, @@ -195,6 +201,7 @@ const util = { if (path.isAbsolute(filename)) { return filename } + return path.join(process.cwd(), '..', '..', filename) }, @@ -202,18 +209,25 @@ const util = { const envVar = process.env[varName] const configVar = process.env[`npm_config_${varName}`] const packageConfigVar = process.env[`npm_package_config_${varName}`] + if (envVar) { debug(`Using ${varName} from environment variable`) + return envVar } + if (configVar) { debug(`Using ${varName} from npm config`) + return configVar } + if (packageConfigVar) { debug(`Using ${varName} from package.json config`) + return packageConfigVar } + return undefined }, diff --git a/cli/scripts/build.js b/cli/scripts/build.js index cd2f566e74..7f593faf36 100644 --- a/cli/scripts/build.js +++ b/cli/scripts/build.js @@ -51,7 +51,8 @@ function makeUserPackageFile () { .then((json) => { return fs.outputJsonAsync(packageJsonDest, json, { spaces: 2, - }).then(() => json) // returning package json object makes it easy to test + }) + .return(json) // returning package json object makes it easy to test }) } diff --git a/cli/test/lib/build_spec.js b/cli/test/lib/build_spec.js index f2b3367748..c7516861ca 100644 --- a/cli/test/lib/build_spec.js +++ b/cli/test/lib/build_spec.js @@ -7,11 +7,13 @@ const la = require('lazy-ass') const is = require('check-more-types') const R = require('ramda') -const hasVersion = (json) => - la(is.semver(json.version), 'cannot find version', json) +const hasVersion = (json) => { + return la(is.semver(json.version), 'cannot find version', json) +} -const hasAuthor = (json) => - la(json.author === 'Brian Mann', 'wrong author name', json) +const hasAuthor = (json) => { + return la(json.author === 'Brian Mann', 'wrong author name', json) +} const changeVersion = R.assoc('version', 'x.y.z') diff --git a/cli/test/lib/cli_spec.js b/cli/test/lib/cli_spec.js index 4f22b68659..c88e179307 100644 --- a/cli/test/lib/cli_spec.js +++ b/cli/test/lib/cli_spec.js @@ -19,46 +19,55 @@ describe('cli', function () { sinon.stub(process, 'exit') sinon.stub(util, 'exit') sinon.stub(util, 'logErrorExit1') - this.exec = (args) => cli.init(`node test ${args}`.split(' ')) + this.exec = (args) => { + return cli.init(`node test ${args}`.split(' ')) + } }) context('unknown option', () => { // note it shows help for that specific command - it('shows help', () => - execa('bin/cypress', ['open', '--foo']).then((result) => { + it('shows help', () => { + return execa('bin/cypress', ['open', '--foo']).then((result) => { snapshot('shows help for open --foo', result) }) + } ) - it('shows help for run command', () => - execa('bin/cypress', ['run', '--foo']).then((result) => { + it('shows help for run command', () => { + return execa('bin/cypress', ['run', '--foo']).then((result) => { snapshot('shows help for run --foo', result) }) + } ) }) context('help command', () => { - it('shows help', () => - execa('bin/cypress', ['help']).then(snapshot) + it('shows help', () => { + return execa('bin/cypress', ['help']).then(snapshot) + } ) - it('shows help for -h', () => - execa('bin/cypress', ['-h']).then(snapshot) + it('shows help for -h', () => { + return execa('bin/cypress', ['-h']).then(snapshot) + } ) - it('shows help for --help', () => - execa('bin/cypress', ['--help']).then(snapshot) + it('shows help for --help', () => { + return execa('bin/cypress', ['--help']).then(snapshot) + } ) }) context('unknown command', () => { - it('shows usage and exits', () => - execa('bin/cypress', ['foo']).then(snapshot) + it('shows usage and exits', () => { + return execa('bin/cypress', ['foo']).then(snapshot) + } ) }) context('cypress version', function () { const binaryDir = '/binary/dir' + beforeEach(function () { sinon.stub(state, 'getBinaryDir').returns(binaryDir) }) @@ -136,6 +145,7 @@ describe('cli', function () { it('run.start with options + catches errors', function (done) { const err = new Error('foo') + run.start.rejects(err) this.exec('run') @@ -261,7 +271,6 @@ describe('cli', function () { }) }) - it('install calls install.start without forcing', function () { sinon.stub(install, 'start').resolves() this.exec('install') @@ -287,7 +296,6 @@ describe('cli', function () { }) context('cypress verify', function () { - it('verify calls verify.start with force: true', function () { sinon.stub(verify, 'start').resolves() this.exec('verify') diff --git a/cli/test/lib/cypress_spec.js b/cli/test/lib/cypress_spec.js index b60cd50075..391f3dfe94 100644 --- a/cli/test/lib/cypress_spec.js +++ b/cli/test/lib/cypress_spec.js @@ -21,6 +21,7 @@ describe('cypress', function () { const getCallArgs = R.path(['lastCall', 'args', 0]) const getStartArgs = () => { expect(open.start).to.be.called + return getCallArgs(open.start) } @@ -48,10 +49,12 @@ describe('cypress', function () { context('.run', function () { let outputPath + beforeEach(function () { outputPath = path.join(os.tmpdir(), 'cypress/monorepo/cypress_spec/output.json') sinon.stub(tmp, 'fileAsync').resolves(outputPath) sinon.stub(run, 'start').resolves() + return fs.outputJsonAsync(outputPath, { code: 0, failingTests: [], @@ -62,10 +65,12 @@ describe('cypress', function () { const normalizeCallArgs = (args) => { expect(args.outputPath).to.equal(outputPath) delete args.outputPath + return args } const getStartArgs = () => { expect(run.start).to.be.called + return normalizeCallArgs(getCallArgs(run.start)) } diff --git a/cli/test/lib/errors_spec.js b/cli/test/lib/errors_spec.js index f12aa57068..9daa55d3ca 100644 --- a/cli/test/lib/errors_spec.js +++ b/cli/test/lib/errors_spec.js @@ -14,14 +14,16 @@ describe('errors', function () { }) describe('individual', () => { - it('has the following errors', () => - snapshot(Object.keys(errors)) + it('has the following errors', () => { + return snapshot(Object.keys(errors)) + } ) }) context('.errors.formErrorText', function () { - it('returns fully formed text message', () => - snapshot(formErrorText(missingXvfb)) + it('returns fully formed text message', () => { + return snapshot(formErrorText(missingXvfb)) + } ) }) }) diff --git a/cli/test/lib/util_spec.js b/cli/test/lib/util_spec.js index 1cd58ee38c..1687d5c9c2 100644 --- a/cli/test/lib/util_spec.js +++ b/cli/test/lib/util_spec.js @@ -24,24 +24,28 @@ describe('util', () => { it('matches entire output', () => { const line = '444' + expect(stdoutLineMatches(line, line)).to.be.true }) it('matches a line in output', () => { const line = '444' const stdout = ['start', line, 'something else'].join('\n') + expect(stdoutLineMatches(line, stdout)).to.be.true }) it('matches a trimmed line in output', () => { const line = '444' const stdout = ['start', ` ${line} `, 'something else'].join('\n') + expect(stdoutLineMatches(line, stdout)).to.be.true }) it('does not find match', () => { const line = '445' const stdout = ['start', '444', 'something else'].join('\n') + expect(stdoutLineMatches(line, stdout)).to.be.false }) }) @@ -53,6 +57,7 @@ describe('util', () => { const options = { foo: 'bar', } + snapshot('others_unchanged', normalizeModuleOptions(options)) }) @@ -60,6 +65,7 @@ describe('util', () => { const options = { env: 'foo=bar', } + snapshot('env_as_string', normalizeModuleOptions(options)) }) @@ -71,6 +77,7 @@ describe('util', () => { host: 'kevin.dev.local', }, } + snapshot('env_as_object', normalizeModuleOptions(options)) }) @@ -81,6 +88,7 @@ describe('util', () => { watchForFileChanges: false, }, } + snapshot('config_as_object', normalizeModuleOptions(options)) }) @@ -91,6 +99,7 @@ describe('util', () => { toConsole: true, }, } + snapshot('reporter_options_as_object', normalizeModuleOptions(options)) }) @@ -100,6 +109,7 @@ describe('util', () => { 'a', 'b', 'c', ], } + snapshot('spec_as_array', normalizeModuleOptions(options)) }) @@ -107,6 +117,7 @@ describe('util', () => { const options = { spec: 'x,y,z', } + snapshot('spec_as_string', normalizeModuleOptions(options)) }) }) @@ -250,6 +261,7 @@ describe('util', () => { it('does nothing if debug is not enabled', () => { const log = sinon.spy() + log.enabled = false util.printNodeOptions(log) expect(log).not.have.been.called @@ -257,6 +269,7 @@ describe('util', () => { it('prints message when debug is enabled', () => { const log = sinon.spy() + log.enabled = true util.printNodeOptions(log) expect(log).to.be.calledWith('NODE_OPTIONS is not set') @@ -270,6 +283,7 @@ describe('util', () => { it('does nothing if debug is not enabled', () => { const log = sinon.spy() + log.enabled = false util.printNodeOptions(log) expect(log).not.have.been.called @@ -277,6 +291,7 @@ describe('util', () => { it('prints value when debug is enabled', () => { const log = sinon.spy() + log.enabled = true util.printNodeOptions(log) expect(log).to.be.calledWith('NODE_OPTIONS=%s', 'foo') @@ -287,6 +302,7 @@ describe('util', () => { describe('.getOsVersionAsync', () => { let util let getos = sinon.stub().resolves(['distro-release']) + beforeEach(() => { util = proxyquire(`${lib}/util`, { getos }) }) diff --git a/cli/test/spec_helper.js b/cli/test/spec_helper.js index 45289f98db..7c3c6ef509 100644 --- a/cli/test/spec_helper.js +++ b/cli/test/spec_helper.js @@ -43,7 +43,9 @@ function throwIfFnNotStubbed (stub, method) { err.stack = _ .chain(err.stack) .split('\n') - .reject((str) => _.includes(str, 'sinon')) + .reject((str) => { + return _.includes(str, 'sinon') + }) .join('\n') .value() @@ -52,6 +54,7 @@ function throwIfFnNotStubbed (stub, method) { } const $stub = sinon.stub + sinon.stub = function (obj, method) { /* eslint-disable prefer-rest-params */ const stub = $stub.apply(this, arguments) diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000000..5f354257ac --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,15 @@ +{ + "exclude": [ + "**/.git/**", + "**/.cache/**", + "**/.history/**", + "**/.projects/**", + "**/.publish/**", + "**/node_modules/**", + "**/app/**", + "**/build/**", + "**/dist/**", + "**/dist-test/**", + "**/.cy/**" + ] +} diff --git a/package.json b/package.json index eab1d91f23..16081e36ee 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,15 @@ "start": "node ./cli/bin/cypress open --dev --global", "cypress:open": "node ./cli/bin/cypress open --dev --global", "cypress:run": "node ./cli/bin/cypress run --dev", + "cypress:open:debug": "node ./scripts/debug.js cypress:open", + "cypress:run:debug": "node ./scripts/debug.js cypress:run", "dev": "node ./scripts/start.js", + "dev-debug": "node ./scripts/debug.js dev", "watch": "npm run all watch", + "test-debug-package": "node ./scripts/test-debug-package.js", + "jscodeshift": "jscodeshift -t ./node_modules/js-codemod/transforms/arrow-function-arguments.js", + "decaffeinate": "decaffeinate --use-cs2 --loose", + "decaffeinate-bulk": "bulk-decaffeinate", "check-deps": "node ./scripts/check-deps.js --verbose", "check-deps-pre": "node ./scripts/check-deps.js --verbose --prescript", "prebuild": "npm run check-deps-pre", @@ -23,7 +30,7 @@ "link": "node ./scripts/link-packages.js", "install-filtered": "npm run all install -- --package $(node ./scripts/check-deps.js --list)", "postinstall": "echo 'root postinstall' && npm run link && npm run all install && npm run build", - "clean-deps": "npm run all clean-deps", + "clean-deps": "npm run all clean-deps && rm -rf node_modules", "docker": "./scripts/run-docker-local.sh", "lint-js": "eslint --fix scripts/*.js packages/ts/*.js cli/*.js cli/**/*.js", "lint-coffee": "coffeelint scripts/**/*.coffee", @@ -59,9 +66,10 @@ "@cypress/questions-remain": "^1.0.1", "ansi-styles": "^3.1.0", "ascii-table": "0.0.9", - "babel-eslint": "^7.2.3", + "babel-eslint": "^10.0.1", "bluebird": "^3.4.5", "bluebird-retry": "^0.11.0", + "bulk-decaffeinate": "^3.3.1", "chai": "^4.0.2", "chalk": "^2.0.1", "check-dependencies": "1.1.0", @@ -72,9 +80,10 @@ "common-tags": "^1.8.0", "console.table": "^0.9.1", "debug": "3.1.0", + "decaffeinate": "^4.8.8", "del": "^3.0.0", "electron-osx-sign": "^0.4.6", - "eslint": "4.13.1", + "eslint": "4.19.1", "eslint-plugin-cypress": "^2.0.1", "eslint-plugin-cypress-dev": "^1.1.1", "eslint-plugin-mocha": "^4.11.0", @@ -82,6 +91,7 @@ "execa": "^0.8.0", "execa-wrap": "^1.1.0", "filesize": "^3.5.10", + "find-package-json": "^1.1.0", "fs-extra": "^7.0.0", "gift": "^0.10.0", "gulp": "^3.9.1", @@ -93,6 +103,9 @@ "human-interval": "^0.1.6", "husky": "^0.14.3", "inquirer": "^3.1.1", + "js-codemod": "cpojer/js-codemod#29dafed", + "jscodemods": "cypress-io/jscodemods#01b546e", + "jscodeshift": "^0.5.1", "konfig": "^0.2.1", "lazy-ass": "^1.6.0", "lint-staged": "^4.1.3", diff --git a/packages/coffee/package.json b/packages/coffee/package.json index 9dec5d10c1..9f95f0791e 100644 --- a/packages/coffee/package.json +++ b/packages/coffee/package.json @@ -9,6 +9,7 @@ "register.js" ], "scripts": { - "check-deps": "node ../../scripts/check-deps.js --verbose" + "check-deps": "node ../../scripts/check-deps.js --verbose", + "clean-deps": "rm -rf node_modules" } } diff --git a/packages/desktop-gui/src/app/intro.jsx b/packages/desktop-gui/src/app/intro.jsx index 58b3191a25..e9cf2fa2e2 100644 --- a/packages/desktop-gui/src/app/intro.jsx +++ b/packages/desktop-gui/src/app/intro.jsx @@ -92,11 +92,13 @@ class Default extends Component { _dragover = () => { this._setDragging(true) + return false } _dragleave = () => { this._setDragging(false) + return false } @@ -105,6 +107,7 @@ class Default extends Component { this._setDragging(false) const file = _.get(e, 'dataTransfer.files[0]') + if (!file) return false this._addProject(file.path) @@ -118,6 +121,7 @@ class Default extends Component { _nope (e) { e.preventDefault() + return false } diff --git a/packages/desktop-gui/src/app/nav.jsx b/packages/desktop-gui/src/app/nav.jsx index 918e688143..817e231ff2 100644 --- a/packages/desktop-gui/src/app/nav.jsx +++ b/packages/desktop-gui/src/app/nav.jsx @@ -122,14 +122,15 @@ export default class Nav extends Component { {' '}{authStore.user.displayName} ) - } else { - return ( - - {' '} - Log Out - - ) } + + return ( + + {' '} + Log Out + + ) + } _select = (item) => { diff --git a/packages/desktop-gui/src/auth/auth-api.js b/packages/desktop-gui/src/auth/auth-api.js index e91c050d29..aa0396b932 100644 --- a/packages/desktop-gui/src/auth/auth-api.js +++ b/packages/desktop-gui/src/auth/auth-api.js @@ -7,6 +7,7 @@ class AuthApi { ipc.getCurrentUser() .then((user) => { authStore.setUser(user) + // mobx can trigger a synchronous re-render, which executes // componentDidMount, etc in other components, making bluebird // think another promise was created but not returned @@ -35,6 +36,7 @@ class AuthApi { }) .then((user) => { authStore.setUser(user) + return null }) .catch({ alreadyOpen: true }, () => {}) diff --git a/packages/desktop-gui/src/auth/auth-store.js b/packages/desktop-gui/src/auth/auth-store.js index 8d9b1e0c86..f3bf345d49 100644 --- a/packages/desktop-gui/src/auth/auth-store.js +++ b/packages/desktop-gui/src/auth/auth-store.js @@ -20,6 +20,7 @@ class AuthStore { @action setUser (user) { const isValid = user && user.authToken + this.user = isValid ? new User(user) : null } } diff --git a/packages/desktop-gui/src/auth/login-form.jsx b/packages/desktop-gui/src/auth/login-form.jsx index 6856820bb4..eda3e90441 100644 --- a/packages/desktop-gui/src/auth/login-form.jsx +++ b/packages/desktop-gui/src/auth/login-form.jsx @@ -40,18 +40,20 @@ class LoginForm extends Component { Logging in... ) - } else { - return ( - - {' '} - Log In with GitHub - - ) } + + return ( + + {' '} + Log In with GitHub + + ) + } _error () { const error = this.state.error + if (!error) return null return ( diff --git a/packages/desktop-gui/src/dropdown/dropdown.jsx b/packages/desktop-gui/src/dropdown/dropdown.jsx index b0a6ad2e99..fdab8d9152 100644 --- a/packages/desktop-gui/src/dropdown/dropdown.jsx +++ b/packages/desktop-gui/src/dropdown/dropdown.jsx @@ -52,13 +52,14 @@ class Dropdown extends Component { {this._buttonContent()} ) - } else { - return ( - - {this._buttonContent()} - - ) } + + return ( + + {this._buttonContent()} + + ) + } _buttonContent () { diff --git a/packages/desktop-gui/src/duration-timer/duration-timer-store.js b/packages/desktop-gui/src/duration-timer/duration-timer-store.js index 82e8ab8d80..e51a6628f6 100644 --- a/packages/desktop-gui/src/duration-timer/duration-timer-store.js +++ b/packages/desktop-gui/src/duration-timer/duration-timer-store.js @@ -22,11 +22,14 @@ class DurationTimer { this.timer.milliseconds = moment().diff(this.startTime) - this.timerId = setTimeout(() => this.measure(), 10) + this.timerId = setTimeout(() => { + return this.measure() + }, 10) } @action startTimer () { if (this.isRunning) return + this.isRunning = true this.measure() } diff --git a/packages/desktop-gui/src/footer/footer.jsx b/packages/desktop-gui/src/footer/footer.jsx index 90751e7a6e..367d4da182 100644 --- a/packages/desktop-gui/src/footer/footer.jsx +++ b/packages/desktop-gui/src/footer/footer.jsx @@ -19,6 +19,7 @@ export default class Footer extends Component { _openChangelog (e) { e.preventDefault() + return ipc.externalOpen('https://on.cypress.io/changelog') } } diff --git a/packages/desktop-gui/src/lib/app-store.js b/packages/desktop-gui/src/lib/app-store.js index 529c4178ea..547067b562 100644 --- a/packages/desktop-gui/src/lib/app-store.js +++ b/packages/desktop-gui/src/lib/app-store.js @@ -28,8 +28,11 @@ class AppStore { @action set (props) { if (props.cypressEnv != null) this.cypressEnv = props.cypressEnv + if (props.os != null) this.os = props.os + if (props.projectRoot != null) this.projectRoot = props.projectRoot + if (props.version != null) this.version = this.newVersion = props.version } diff --git a/packages/desktop-gui/src/lib/handle-global-errors.js b/packages/desktop-gui/src/lib/handle-global-errors.js index c468f76363..8c27a9f400 100644 --- a/packages/desktop-gui/src/lib/handle-global-errors.js +++ b/packages/desktop-gui/src/lib/handle-global-errors.js @@ -19,6 +19,7 @@ const handleGlobalErrors = () => { window.onunhandledrejection = (event) => { const reason = event && event.reason + sendErr(reason || event) } } diff --git a/packages/desktop-gui/src/lib/ipc-bus.js b/packages/desktop-gui/src/lib/ipc-bus.js index fbdbad52a9..d92ed8039e 100644 --- a/packages/desktop-gui/src/lib/ipc-bus.js +++ b/packages/desktop-gui/src/lib/ipc-bus.js @@ -11,7 +11,9 @@ const addMsg = (id, event, fn) => { } const removeMsgsByEvent = (event) => { - msgs = _.omitBy(msgs, (msg) => msg.event === event) + msgs = _.omitBy(msgs, (msg) => { + return msg.event === event + }) } const removeMsgById = (id) => { @@ -20,6 +22,7 @@ const removeMsgById = (id) => { const createIpc = () => { console.warn('Missing "ipc". Polyfilling in development mode.') // eslint-disable-line no-console + return { on () {}, send () {}, @@ -39,7 +42,9 @@ ipc.on('response', (event, obj = {}) => { }) const ipcBus = (...args) => { - if (args.length === 0) { return msgs } + if (args.length === 0) { + return msgs + } // our ipc interface can either be a standard // node callback or a promise interface @@ -58,12 +63,15 @@ const ipcBus = (...args) => { const lastArg = args.pop() let fn + // enable the last arg to be a function // which changes this interface from being // a promise to just calling the callback // function directly if (lastArg && _.isFunction(lastArg)) { - fn = () => addMsg(id, event, lastArg) + fn = () => { + return addMsg(id, event, lastArg) + } } else { // push it back onto the array args.push(lastArg) diff --git a/packages/desktop-gui/src/lib/ipc.js b/packages/desktop-gui/src/lib/ipc.js index 210c9f1b3a..6194e6e507 100644 --- a/packages/desktop-gui/src/lib/ipc.js +++ b/packages/desktop-gui/src/lib/ipc.js @@ -22,7 +22,9 @@ const register = (eventName, isPromiseApi = true) => { return ipcBus(eventName, ...args) } if (!isPromiseApi) { - ipc[_.camelCase(`off:${eventName}`)] = () => ipcBus.off(eventName) + ipc[_.camelCase(`off:${eventName}`)] = () => { + return ipcBus.off(eventName) + } } } diff --git a/packages/desktop-gui/src/lib/local-data.js b/packages/desktop-gui/src/lib/local-data.js index 4027b6f12d..f0d2f705ef 100644 --- a/packages/desktop-gui/src/lib/local-data.js +++ b/packages/desktop-gui/src/lib/local-data.js @@ -1,6 +1,7 @@ export default { get (key) { const value = localStorage[key] + return value && JSON.parse(value) }, diff --git a/packages/desktop-gui/src/lib/routing.jsx b/packages/desktop-gui/src/lib/routing.jsx index 963e3e42d9..522050382e 100644 --- a/packages/desktop-gui/src/lib/routing.jsx +++ b/packages/desktop-gui/src/lib/routing.jsx @@ -6,6 +6,7 @@ const Link = ({ children, to, onClick }) => { const navigate = (e) => { e.preventDefault() if (onClick) onClick() + to.navigate() } diff --git a/packages/desktop-gui/src/lib/utils.js b/packages/desktop-gui/src/lib/utils.js index 01ef6ea7bf..7035bfd55d 100644 --- a/packages/desktop-gui/src/lib/utils.js +++ b/packages/desktop-gui/src/lib/utils.js @@ -48,7 +48,9 @@ module.exports = { gravatarUrl: (email) => { let opts = { size: '13', default: 'mm' } - if (!email) { opts.forcedefault = 'y' } + if (!email) { + opts.forcedefault = 'y' + } return gravatar.url(email, opts, true) }, @@ -78,6 +80,7 @@ module.exports = { stripLeadingCyDirs (spec) { if (!spec) return null + // remove leading 'cypress/integration' from spec return spec.replace(cyDirRegex, '') }, diff --git a/packages/desktop-gui/src/organizations/organizations-api.js b/packages/desktop-gui/src/organizations/organizations-api.js index 6987b815bd..6264a02c83 100644 --- a/packages/desktop-gui/src/organizations/organizations-api.js +++ b/packages/desktop-gui/src/organizations/organizations-api.js @@ -7,11 +7,13 @@ const getOrgs = () => { ipc.getOrgs() .then((orgs = []) => { orgsStore.setOrgs(orgs) + return null }) .catch(ipc.isUnauthed, ipc.handleUnauthed) .catch((err) => { orgsStore.setError(err) + return null }) diff --git a/packages/desktop-gui/src/organizations/organizations-store.js b/packages/desktop-gui/src/organizations/organizations-store.js index 9d2c237e58..5e8667e73c 100644 --- a/packages/desktop-gui/src/organizations/organizations-store.js +++ b/packages/desktop-gui/src/organizations/organizations-store.js @@ -10,9 +10,11 @@ export class Orgs { @observable isLoaded = false @action setOrgs (orgs) { - this.orgs = _.map(orgs, (org) => ( - new Org(org) - )) + this.orgs = _.map(orgs, (org) => { + return ( + new Org(org) + ) + }) this.isLoading = false this.isLoaded = true diff --git a/packages/desktop-gui/src/project/onboarding.jsx b/packages/desktop-gui/src/project/onboarding.jsx index d1ecd60e4d..992f0131fa 100644 --- a/packages/desktop-gui/src/project/onboarding.jsx +++ b/packages/desktop-gui/src/project/onboarding.jsx @@ -86,8 +86,10 @@ class OnBoarding extends Component { files = _.sortBy(files, 'name') const notFolders = _.every(files, (file) => !file.children) + if (notFolders && files.length > 3) { const numHidden = files.length - 2 + files = files.slice(0, 2).concat({ name: `... ${numHidden} more files ...`, more: true }) } @@ -104,16 +106,17 @@ class OnBoarding extends Component { ) - } else { - return ( -
  • - - {' '} - {file.name} - -
  • - ) } + + return ( +
  • + + {' '} + {file.name} + +
  • + ) + }) } diff --git a/packages/desktop-gui/src/project/project-model.js b/packages/desktop-gui/src/project/project-model.js index e8b3865a23..85c012cbfb 100644 --- a/packages/desktop-gui/src/project/project-model.js +++ b/packages/desktop-gui/src/project/project-model.js @@ -81,9 +81,11 @@ export default class Project { @computed get displayPath () { const maxPathLength = 45 + if (this.path.length <= maxPathLength) return this.path const truncatedPath = this.path.slice((this.path.length - 1) - maxPathLength, this.path.length) + return '...'.concat(truncatedPath) } @@ -221,6 +223,7 @@ export default class Project { @action setChosenBrowserByName (name) { const browser = _.find(this.browsers, { name }) || this.defaultBrowser + this.setChosenBrowser(browser) } diff --git a/packages/desktop-gui/src/projects/projects-api.js b/packages/desktop-gui/src/projects/projects-api.js index 2f88caf3e5..b5bd22847f 100644 --- a/packages/desktop-gui/src/projects/projects-api.js +++ b/packages/desktop-gui/src/projects/projects-api.js @@ -35,6 +35,7 @@ const loadProjects = (shouldLoad = true) => { .then((projectsWithStatuses) => { projectsStore.updateProjectsWithStatuses(projectsWithStatuses) saveToLocalStorage() + return null }) .catch(ipc.isUnauthed, ipc.handleUnauthed) @@ -46,6 +47,7 @@ const loadProjects = (shouldLoad = true) => { const addProject = (path) => { const project = projectsStore.addProject(path) + project.setLoading(true) return ipc.addProject(path) @@ -169,11 +171,13 @@ const openProject = (project) => { .then((config = {}) => { updateConfig(config) const projectIdAndPath = { id: config.projectId, path: project.path } + specsStore.setFilter(projectIdAndPath, localData.get(specsStore.getSpecsFilterId(projectIdAndPath))) project.setLoading(false) getSpecs(setProjectError) projectPollingId = setInterval(updateProjectStatus, 10000) + return updateProjectStatus() }) .catch(setProjectError) @@ -184,7 +188,9 @@ const reopenProject = (project) => { project.clearWarning() return closeProject(project) - .then(() => openProject(project)) + .then(() => { + return openProject(project) + }) } const removeProject = (project) => { @@ -202,7 +208,9 @@ const getRecordKeys = () => { return ipc.getRecordKeys() .catch(ipc.isUnauthed, ipc.handleUnauthed) // ignore error, settle for no keys - .catch(() => []) + .catch(() => { + return [] + }) } export default { diff --git a/packages/desktop-gui/src/projects/projects-store.js b/packages/desktop-gui/src/projects/projects-store.js index e211f9c9f6..c54ea8b4e5 100644 --- a/packages/desktop-gui/src/projects/projects-store.js +++ b/packages/desktop-gui/src/projects/projects-store.js @@ -13,11 +13,15 @@ class ProjectsStore { } @computed get other () { - return _.filter(this.projects, (project) => !project.isChosen) + return _.filter(this.projects, (project) => { + return !project.isChosen + }) } @computed get clientProjects () { - return _.map(this.projects, (project) => _.pick(project, ['path', 'id'])) + return _.map(this.projects, (project) => { + return _.pick(project, ['path', 'id']) + }) } @action getProjectByPath (path) { @@ -33,6 +37,7 @@ class ProjectsStore { // or move it to the start if it already exists const existingIndex = _.findIndex(this.projects, { path }) let project + if (existingIndex > -1) { project = this.projects[existingIndex] this.projects.splice(existingIndex, 1) @@ -50,7 +55,9 @@ class ProjectsStore { } @action setProjects (projects) { - this.projects = _.map(projects, (project) => new Project(project)) + this.projects = _.map(projects, (project) => { + return new Project(project) + }) } @action updateProjectsWithStatuses (projectsWithStatuses = []) { @@ -82,7 +89,9 @@ class ProjectsStore { } serializeProjects () { - return _.map(this.projects, (project) => project.serialize()) + return _.map(this.projects, (project) => { + return project.serialize() + }) } membershipRequested (id) { diff --git a/packages/desktop-gui/src/runs/error-message.jsx b/packages/desktop-gui/src/runs/error-message.jsx index 94fa480c62..7bbdb52967 100644 --- a/packages/desktop-gui/src/runs/error-message.jsx +++ b/packages/desktop-gui/src/runs/error-message.jsx @@ -5,6 +5,7 @@ import errors from '../lib/errors' const ErrorMessage = observer(({ error }) => { let errorMessage + if (errors.isTimedOut(error)) { errorMessage = (

    The request for runs timed out.

    diff --git a/packages/desktop-gui/src/runs/permission-message.jsx b/packages/desktop-gui/src/runs/permission-message.jsx index c79a7fd8c8..2b4264889d 100644 --- a/packages/desktop-gui/src/runs/permission-message.jsx +++ b/packages/desktop-gui/src/runs/permission-message.jsx @@ -38,11 +38,14 @@ class PermissionMessage extends Component { if (this.state.result === SUCCESS || membershipRequested) { return this._success() - } else if (this.state.result === FAILURE) { - return this._failure() - } else { - return this._noResult() } + + if (this.state.result === FAILURE) { + return this._failure() + } + + return this._noResult() + } _button () { @@ -82,22 +85,23 @@ class PermissionMessage extends Component { // tell them it's all good if (errors.isDenied(error) || errors.isAlreadyRequested(error)) { return this._success() - } else { - return ( -
    -

    - {' '} - Request Failed -

    -

    An unexpected error occurred while requesting access:

    -
    -            {this.state.error.message}
    -          
    -

    Try again.

    - {this._button()} -
    - ) } + + return ( +
    +

    + {' '} + Request Failed +

    +

    An unexpected error occurred while requesting access:

    +
    +          {this.state.error.message}
    +        
    +

    Try again.

    + {this._button()} +
    + ) + } _noResult () { @@ -135,6 +139,7 @@ class PermissionMessage extends Component { _setResult (error) { if (errors.isAlreadyMember(error)) { this.props.onRetry() + return } diff --git a/packages/desktop-gui/src/runs/runs-api.js b/packages/desktop-gui/src/runs/runs-api.js index efdc77d923..89ec6d8bf9 100644 --- a/packages/desktop-gui/src/runs/runs-api.js +++ b/packages/desktop-gui/src/runs/runs-api.js @@ -12,11 +12,13 @@ const loadRuns = (runsStore) => { ipc.getRuns() .then((runs) => { runsStore.setRuns(runs) + return null }) .catch(ipc.isUnauthed, ipc.handleUnauthed) .catch((err) => { runsStore.setError(err) + return null }) diff --git a/packages/desktop-gui/src/runs/runs-list-item.jsx b/packages/desktop-gui/src/runs/runs-list-item.jsx index fdbbf9db39..4479c41da9 100644 --- a/packages/desktop-gui/src/runs/runs-list-item.jsx +++ b/packages/desktop-gui/src/runs/runs-list-item.jsx @@ -193,9 +193,10 @@ export default class RunsListItem extends Component { _osIcon () { if (!this._moreThanOneInstance() && this.props.run.instances.length) { return (osIcon(this.props.run.instances[0].platform.osName)) - } else { - return 'desktop' } + + return 'desktop' + } _getUniqOs () { diff --git a/packages/desktop-gui/src/runs/runs-list.jsx b/packages/desktop-gui/src/runs/runs-list.jsx index 12808f2241..5476326ff2 100644 --- a/packages/desktop-gui/src/runs/runs-list.jsx +++ b/packages/desktop-gui/src/runs/runs-list.jsx @@ -161,19 +161,27 @@ class RunsList extends Component { return this._projectNotSetup() // the project is invalid - } else if (errors.isNotFound(this.runsStore.error)) { + } + + if (errors.isNotFound(this.runsStore.error)) { return this._projectNotSetup(false) // they have been logged out - } else if (errors.isUnauthenticated(this.runsStore.error)) { + } + + if (errors.isUnauthenticated(this.runsStore.error)) { return this._loginMessage() // they are not authorized to see runs - } else if (errors.isUnauthorized(this.runsStore.error)) { + } + + if (errors.isUnauthorized(this.runsStore.error)) { return this._permissionMessage() // other error, but only show if we don't already have runs - } else if (!this.runsStore.isLoaded) { + } + + if (!this.runsStore.isLoaded) { return } } @@ -189,9 +197,10 @@ class RunsList extends Component { return this._projectNotSetup() // OR they have setup CI - } else { - return this._empty() } + + return this._empty() + } //--------End Run States----------// diff --git a/packages/desktop-gui/src/runs/runs-store.js b/packages/desktop-gui/src/runs/runs-store.js index 361e41bd1e..3590391f89 100644 --- a/packages/desktop-gui/src/runs/runs-store.js +++ b/packages/desktop-gui/src/runs/runs-store.js @@ -17,7 +17,9 @@ export class RunsStore { } @action setRuns (runs) { - this.runs = _.map(runs, (run) => new Run(run)) + this.runs = _.map(runs, (run) => { + return new Run(run) + }) this.lastUpdated = moment().format('h:mm:ssa') this.error = null diff --git a/packages/desktop-gui/src/runs/setup-project-modal.jsx b/packages/desktop-gui/src/runs/setup-project-modal.jsx index 576748777e..4a730aef25 100644 --- a/packages/desktop-gui/src/runs/setup-project-modal.jsx +++ b/packages/desktop-gui/src/runs/setup-project-modal.jsx @@ -334,6 +334,7 @@ class SetupProject extends Component { _error () { const error = this.state.error + if (!error) return null return ( @@ -426,6 +427,7 @@ class SetupProject extends Component { isSubmitting: false, }) this.props.onSetup(projectDetails) + return null }) .catch(ipc.isUnauthed, ipc.handleUnauthed) diff --git a/packages/desktop-gui/src/settings/configuration.jsx b/packages/desktop-gui/src/settings/configuration.jsx index 744d68dc04..16a2479560 100644 --- a/packages/desktop-gui/src/settings/configuration.jsx +++ b/packages/desktop-gui/src/settings/configuration.jsx @@ -11,6 +11,7 @@ const display = (obj) => { return _.map(obj, (value, key) => { const hasComma = lastKey !== key + if (value.from == null) { return displayNestedObj(key, value, hasComma) } diff --git a/packages/desktop-gui/src/settings/record-key.jsx b/packages/desktop-gui/src/settings/record-key.jsx index 6028eee8f3..bf286b6305 100644 --- a/packages/desktop-gui/src/settings/record-key.jsx +++ b/packages/desktop-gui/src/settings/record-key.jsx @@ -45,6 +45,7 @@ class RecordKey extends Component { ) { this._loadKeys() } + this.wasAuthenticated = authStore.isAuthenticated } diff --git a/packages/desktop-gui/src/specs/specs-list.jsx b/packages/desktop-gui/src/specs/specs-list.jsx index f62d930393..07f20d8db8 100644 --- a/packages/desktop-gui/src/specs/specs-list.jsx +++ b/packages/desktop-gui/src/specs/specs-list.jsx @@ -74,11 +74,13 @@ class SpecsList extends Component { _clearFilter = () => { const { id, path } = this.props.project + specsStore.clearFilter({ id, path }) } _updateFilter = (e) => { const { id, path } = this.props.project + specsStore.setFilter({ id, path }, e.target.value) } diff --git a/packages/desktop-gui/src/specs/specs-store.js b/packages/desktop-gui/src/specs/specs-store.js index fc262dd492..e83b887db3 100644 --- a/packages/desktop-gui/src/specs/specs-store.js +++ b/packages/desktop-gui/src/specs/specs-store.js @@ -82,6 +82,7 @@ export class SpecsStore { getSpecsFilterId ({ id = '', path = '' }) { const shortenedPath = path.replace(/.*cypress/, 'cypress') + return `specsFilter-${id}-${shortenedPath}` } @@ -104,7 +105,9 @@ export class SpecsStore { const isCurrentAFile = i === segments.length - 1 const props = { path: currentPath, displayName: segment } - let existing = _.find(placeholder, (file) => pathsEqual(file.path, currentPath)) + let existing = _.find(placeholder, (file) => { + return pathsEqual(file.path, currentPath) + }) if (!existing) { existing = isCurrentAFile ? new Spec(_.extend(file, props)) : new Folder(props) diff --git a/packages/desktop-gui/src/update/update-banner.jsx b/packages/desktop-gui/src/update/update-banner.jsx index d7e3d9e1f2..c18416e4a1 100644 --- a/packages/desktop-gui/src/update/update-banner.jsx +++ b/packages/desktop-gui/src/update/update-banner.jsx @@ -76,21 +76,22 @@ class UpdateBanner extends Component { ) - } else { - return ( -
      -
    1. - Quit this app. -
    2. -
    3. - Run npm install --save-dev cypress@{appStore.newVersion} -
    4. -
    5. - Run node_modules/.bin/cypress open to open the new version. -
    6. -
    - ) } + + return ( +
      +
    1. + Quit this app. +
    2. +
    3. + Run npm install --save-dev cypress@{appStore.newVersion} +
    4. +
    5. + Run node_modules/.bin/cypress open to open the new version. +
    6. +
    + ) + } _checkForUpdate () { diff --git a/packages/driver/test/cypress/fixtures/sinon.html b/packages/driver/test/cypress/fixtures/sinon.html index 326d6a34ba..c149f8ad21 100644 --- a/packages/driver/test/cypress/fixtures/sinon.html +++ b/packages/driver/test/cypress/fixtures/sinon.html @@ -10,7 +10,7 @@ - \ No newline at end of file + diff --git a/packages/driver/test/cypress/fixtures/sync_error.html b/packages/driver/test/cypress/fixtures/sync_error.html index 21fed0db48..045ce07347 100644 --- a/packages/driver/test/cypress/fixtures/sync_error.html +++ b/packages/driver/test/cypress/fixtures/sync_error.html @@ -12,7 +12,7 @@ - " +const { oneLine } = require('common-tags') - full: (domain) -> - " +module.exports = { + partial (domain) { + return oneLine` + ` + }, + + full (domain) { + return oneLine` + - " - + ` + }, } diff --git a/packages/server/lib/util/network_failures.coffee b/packages/server/lib/util/network_failures.coffee deleted file mode 100644 index 1e97754951..0000000000 --- a/packages/server/lib/util/network_failures.coffee +++ /dev/null @@ -1,48 +0,0 @@ -convertNewLinesToBr = (text) -> - text.split("\n").join("
    ") - -module.exports = { - http: (err, url) -> - """ - Cypress errored attempting to make an http request to this url: - - #{url} - - - The error was: - - #{err.message} - - - The stack trace was: - - #{err.stack} - """ - - file: (url, status) -> - """ - Cypress errored trying to serve this file from your system: - - #{url} - - #{if status is 404 then "The file was not found." else ""} - """ - - wrap: (contents) -> - """ - - - - #{convertNewLinesToBr(contents)} - - - """ - - get: (err, url, status, strategy) -> - contents = - switch strategy - when "http" then @http(err, url) - when "file" then @file(url, status) - - @wrap(contents) -} \ No newline at end of file diff --git a/packages/server/lib/util/network_failures.js b/packages/server/lib/util/network_failures.js new file mode 100644 index 0000000000..08646490d8 --- /dev/null +++ b/packages/server/lib/util/network_failures.js @@ -0,0 +1,67 @@ +/* eslint-disable + default-case, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const convertNewLinesToBr = (text) => { + return text.split('\n').join('
    ') +} + +module.exports = { + http (err, url) { + return `\ +Cypress errored attempting to make an http request to this url: + +${url} + + +The error was: + +${err.message} + + +The stack trace was: + +${err.stack}\ +` + }, + + file (url, status) { + return `\ +Cypress errored trying to serve this file from your system: + +${url} + +${status === 404 ? 'The file was not found.' : ''}\ +` + }, + + wrap (contents) { + return `\ + + + + ${convertNewLinesToBr(contents)} + +\ +` + }, + + get (err, url, status, strategy) { + const contents = + (() => { + switch (strategy) { + case 'http': return this.http(err, url) + case 'file': return this.file(url, status) + } + })() + + return this.wrap(contents) + }, +} diff --git a/packages/server/lib/util/open.coffee b/packages/server/lib/util/open.coffee deleted file mode 100644 index a96d927e5c..0000000000 --- a/packages/server/lib/util/open.coffee +++ /dev/null @@ -1,11 +0,0 @@ -## wrapper around opn due to issues with proxyquire + istanbul -os = require("os") -opn = require("opn") - -module.exports = { - opn: (arg, opts = {}) -> - if os.platform() is "darwin" - opts.args = "-R" - - opn(arg, opts) -} \ No newline at end of file diff --git a/packages/server/lib/util/open.js b/packages/server/lib/util/open.js new file mode 100644 index 0000000000..331827f61c --- /dev/null +++ b/packages/server/lib/util/open.js @@ -0,0 +1,20 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +//# wrapper around opn due to issues with proxyquire + istanbul +const os = require('os') +const opn = require('opn') + +module.exports = { + opn (arg, opts = {}) { + if (os.platform() === 'darwin') { + opts.args = '-R' + } + + return opn(arg, opts) + }, +} diff --git a/packages/server/lib/util/origin.coffee b/packages/server/lib/util/origin.coffee deleted file mode 100644 index 9dcca57c26..0000000000 --- a/packages/server/lib/util/origin.coffee +++ /dev/null @@ -1,12 +0,0 @@ -url = require("url") - -module.exports = (urlStr) -> - parsed = url.parse(urlStr) - - parsed.hash = null - parsed.search = null - parsed.query = null - parsed.path = null - parsed.pathname = null - - url.format(parsed) diff --git a/packages/server/lib/util/origin.js b/packages/server/lib/util/origin.js new file mode 100644 index 0000000000..2cb4d243d6 --- /dev/null +++ b/packages/server/lib/util/origin.js @@ -0,0 +1,20 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const url = require('url') + +module.exports = function (urlStr) { + const parsed = url.parse(urlStr) + + parsed.hash = null + parsed.search = null + parsed.query = null + parsed.path = null + parsed.pathname = null + + return url.format(parsed) +} diff --git a/packages/server/lib/util/path_helpers.coffee b/packages/server/lib/util/path_helpers.coffee deleted file mode 100644 index fd069386d7..0000000000 --- a/packages/server/lib/util/path_helpers.coffee +++ /dev/null @@ -1,76 +0,0 @@ -path = require("path") -Promise = require("bluebird") -fs = require("./fs") - -isIntegrationTestRe = /^integration/ -isUnitTestRe = /^unit/ - -# require.resolve walks the symlinks, which can really change -# the results. For example -# /tmp/foo is symlink to /private/tmp/foo on Mac -# thus resolving /tmp/foo to find /tmp/foo/index.js -# can return /private/tmp/foo/index.js -# which can really confuse the rest of the code. -# Detect this switch by checking if the resolution of absolute -# paths moved the prefix -# -# Good case: no switcheroo, return false -# /foo/bar -> /foo/bar/index.js -# Bad case: return true -# /tmp/foo/bar -> /private/tmp/foo/bar/index.js -checkIfResolveChangedRootFolder = (resolved, initial) -> - path.isAbsolute(resolved) && - path.isAbsolute(initial) && - !resolved.startsWith(initial) - -# real folder path found could be different due to symlinks -# For example, folder /tmp/foo on Mac is really /private/tmp/foo -getRealFolderPath = (folder) -> - # TODO check if folder is a non-empty string - throw new Error("Expected folder") if not folder - - fs.realpathAsync(folder) - -getRelativePathToSpec = (spec) -> - switch - ## if our file is an integration test - ## then figure out the absolute path - ## to it - when isIntegrationTestRe.test(spec) - ## strip off the integration part - path.relative("integration", spec) - else - spec - -module.exports = { - checkIfResolveChangedRootFolder - - getRealFolderPath - - getRelativePathToSpec - - getAbsolutePathToSpec: (spec, config) -> - switch - ## if our file is an integration test - ## then figure out the absolute path - ## to it - when isIntegrationTestRe.test(spec) - spec = getRelativePathToSpec(spec) - - ## now simply join this with our integrationFolder - ## which makes it an absolute path - path.join(config.integrationFolder, spec) - - # ## commented out until we implement unit testing - # when isUnitTestRe.test(spec) - - ## strip off the unit part - # spec = path.relative("unit", spec) - - # ## now simply resolve this with our unitFolder - # ## which makes it an absolute path - # path.join(config.unitFolder, spec) - - else - spec -} diff --git a/packages/server/lib/util/path_helpers.js b/packages/server/lib/util/path_helpers.js new file mode 100644 index 0000000000..f83161e81f --- /dev/null +++ b/packages/server/lib/util/path_helpers.js @@ -0,0 +1,95 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const path = require('path') +const Promise = require('bluebird') +const fs = require('./fs') + +const isIntegrationTestRe = /^integration/ +const isUnitTestRe = /^unit/ + +// require.resolve walks the symlinks, which can really change +// the results. For example +// /tmp/foo is symlink to /private/tmp/foo on Mac +// thus resolving /tmp/foo to find /tmp/foo/index.js +// can return /private/tmp/foo/index.js +// which can really confuse the rest of the code. +// Detect this switch by checking if the resolution of absolute +// paths moved the prefix +// +// Good case: no switcheroo, return false +// /foo/bar -> /foo/bar/index.js +// Bad case: return true +// /tmp/foo/bar -> /private/tmp/foo/bar/index.js +const checkIfResolveChangedRootFolder = (resolved, initial) => { + return path.isAbsolute(resolved) && + path.isAbsolute(initial) && + !resolved.startsWith(initial) +} + + +// real folder path found could be different due to symlinks +// For example, folder /tmp/foo on Mac is really /private/tmp/foo +const getRealFolderPath = function (folder) { + // TODO check if folder is a non-empty string + if (!folder) { + throw new Error('Expected folder') + } + + return fs.realpathAsync(folder) +} + +const getRelativePathToSpec = function (spec) { + switch (false) { + //# if our file is an integration test + //# then figure out the absolute path + //# to it + case !isIntegrationTestRe.test(spec): + //# strip off the integration part + return path.relative('integration', spec) + default: + return spec + } +} + +module.exports = { + checkIfResolveChangedRootFolder, + + getRealFolderPath, + + getRelativePathToSpec, + + getAbsolutePathToSpec (spec, config) { + switch (false) { + //# if our file is an integration test + //# then figure out the absolute path + //# to it + case !isIntegrationTestRe.test(spec): + spec = getRelativePathToSpec(spec) + + //# now simply join this with our integrationFolder + //# which makes it an absolute path + return path.join(config.integrationFolder, spec) + + // ## commented out until we implement unit testing + // when isUnitTestRe.test(spec) + + //# strip off the unit part + // spec = path.relative("unit", spec) + + // ## now simply resolve this with our unitFolder + // ## which makes it an absolute path + // path.join(config.unitFolder, spec) + + default: + return spec + } + }, +} diff --git a/packages/server/lib/util/progress_bar.coffee b/packages/server/lib/util/progress_bar.coffee deleted file mode 100644 index ee64ae9156..0000000000 --- a/packages/server/lib/util/progress_bar.coffee +++ /dev/null @@ -1,61 +0,0 @@ -_ = require("lodash") -chalk = require("chalk") -ProgressBar = require("progress") - -module.exports = { - create: (message, options = {}) -> - _.defaults(options, { - total: 100 - width: 30 - }) - - ascii = [ - chalk.white(" -") - chalk.blue(message) - chalk.yellow("[:bar]") - chalk.white(":percent") - chalk.gray(":etas") - ] - - bar = new ProgressBar(ascii.join(" "), { - total: options.total - width: options.width - }) - - ticked = 0 - total = options.total - - clear = -> - bar.clear = true - bar.terminate() - - tick = (num) -> - ticked += num - - bar.tick(num) - - tickTotal = (float) -> - ## calculate the overall progress - ## of how full the bar should now be - ## taking into account the total and - ## the current ticks - - ## return us the absolute total - ## we need to tick up to to fill - ## our progress bar - abs = total * float - - ## now subtract what we've already - ## ticked to get the difference - ## of what we need to tick up to - diff = abs - ticked - - tick(diff) - - return { - bar: bar - tick: tick - clear: clear - tickTotal: tickTotal - } -} \ No newline at end of file diff --git a/packages/server/lib/util/progress_bar.js b/packages/server/lib/util/progress_bar.js new file mode 100644 index 0000000000..0e56887b7b --- /dev/null +++ b/packages/server/lib/util/progress_bar.js @@ -0,0 +1,73 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const _ = require('lodash') +const chalk = require('chalk') +const ProgressBar = require('progress') + +module.exports = { + create (message, options = {}) { + _.defaults(options, { + total: 100, + width: 30, + }) + + const ascii = [ + chalk.white(' -'), + chalk.blue(message), + chalk.yellow('[:bar]'), + chalk.white(':percent'), + chalk.gray(':etas'), + ] + + const bar = new ProgressBar(ascii.join(' '), { + total: options.total, + width: options.width, + }) + + let ticked = 0 + const { total } = options + + const clear = function () { + bar.clear = true + + return bar.terminate() + } + + const tick = function (num) { + ticked += num + + return bar.tick(num) + } + + const tickTotal = function (float) { + //# calculate the overall progress + //# of how full the bar should now be + //# taking into account the total and + //# the current ticks + + //# return us the absolute total + //# we need to tick up to to fill + //# our progress bar + const abs = total * float + + //# now subtract what we've already + //# ticked to get the difference + //# of what we need to tick up to + const diff = abs - ticked + + return tick(diff) + } + + return { + bar, + tick, + clear, + tickTotal, + } + }, +} diff --git a/packages/server/lib/util/random.coffee b/packages/server/lib/util/random.coffee deleted file mode 100644 index 7a7e9e972d..0000000000 --- a/packages/server/lib/util/random.coffee +++ /dev/null @@ -1,12 +0,0 @@ -random = require("randomstring") - -id = -> - ## return a random id - random.generate({ - length: 5 - capitalization: "lowercase" - }) - -module.exports = { - id -} diff --git a/packages/server/lib/util/random.js b/packages/server/lib/util/random.js new file mode 100644 index 0000000000..16f62fe199 --- /dev/null +++ b/packages/server/lib/util/random.js @@ -0,0 +1,25 @@ +/* eslint-disable + brace-style, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const random = require('randomstring') + +const id = () => +//# return a random id +{ + return random.generate({ + length: 5, + capitalization: 'lowercase', + }) +} + + +module.exports = { + id, +} diff --git a/packages/server/lib/util/rewriter.coffee b/packages/server/lib/util/rewriter.coffee deleted file mode 100644 index a9ca7700ee..0000000000 --- a/packages/server/lib/util/rewriter.coffee +++ /dev/null @@ -1,41 +0,0 @@ -inject = require("./inject") -security = require("./security") - -headRe = /()/i -bodyRe = /()/i -htmlRe = /()/i - -rewriteHtml = (html, domainName, wantsInjection, wantsSecurityRemoved) -> - replace = (re, str) -> - html.replace(re, str) - - htmlToInject = do => - switch wantsInjection - when "full" - inject.full(domainName) - when "partial" - inject.partial(domainName) - - ## strip clickjacking and framebusting - ## from the HTML if we've been told to - if wantsSecurityRemoved - html = security.strip(html) - - switch - when headRe.test(html) - replace(headRe, "$1 #{htmlToInject}") - - when bodyRe.test(html) - replace(bodyRe, " #{htmlToInject} $1") - - when htmlRe.test(html) - replace(htmlRe, "$1 #{htmlToInject} ") - - else - " #{htmlToInject} " + html - -module.exports = { - html: rewriteHtml - - security: security.stripStream -} diff --git a/packages/server/lib/util/rewriter.js b/packages/server/lib/util/rewriter.js new file mode 100644 index 0000000000..fa44488fcc --- /dev/null +++ b/packages/server/lib/util/rewriter.js @@ -0,0 +1,57 @@ +/* eslint-disable + default-case, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const inject = require('./inject') +const security = require('./security') + +const headRe = /()/i +const bodyRe = /()/i +const htmlRe = /()/i + +const rewriteHtml = function (html, domainName, wantsInjection, wantsSecurityRemoved) { + const replace = (re, str) => { + return html.replace(re, str) + } + + const htmlToInject = (() => { + switch (wantsInjection) { + case 'full': + return inject.full(domainName) + case 'partial': + return inject.partial(domainName) + } + })() + + //# strip clickjacking and framebusting + //# from the HTML if we've been told to + if (wantsSecurityRemoved) { + html = security.strip(html) + } + + switch (false) { + case !headRe.test(html): + return replace(headRe, `$1 ${htmlToInject}`) + + case !bodyRe.test(html): + return replace(bodyRe, ` ${htmlToInject} $1`) + + case !htmlRe.test(html): + return replace(htmlRe, `$1 ${htmlToInject} `) + + default: + return ` ${htmlToInject} ${html}` + } +} + +module.exports = { + html: rewriteHtml, + + security: security.stripStream, +} diff --git a/packages/server/lib/util/routes.coffee b/packages/server/lib/util/routes.coffee deleted file mode 100644 index 60c7e4dd97..0000000000 --- a/packages/server/lib/util/routes.coffee +++ /dev/null @@ -1,47 +0,0 @@ -_ = require("lodash") -UrlParse = require("url-parse") -konfig = require("../konfig") - -api_url = konfig("api_url") - -routes = { - api: "" - auth: "auth" - ping: "ping" - signin: "signin" - signout: "signout" - runs: "runs" - instances: "runs/:id/instances" - instance: "instances/:id" - instanceStdout:"instances/:id/stdout" - orgs: "organizations" - projects: "projects" - project: "projects/:id" - projectToken: "projects/:id/token" - projectRuns: "projects/:id/runs" - projectRecordKeys: "projects/:id/keys" - exceptions: "exceptions" - membershipRequests: "projects/:id/membership_requests" -} - -parseArgs = (url, args = []) -> - _.each args, (value) -> - switch - when _.isObject(value) - url.set("query", _.extend(url.query, value)) - - when _.isString(value) or _.isNumber(value) - url.set("pathname", url.pathname.replace(":id", value)) - - return url - -routes = _.reduce routes, (memo, value, key) -> - memo[key] = (args...) -> - url = new UrlParse(api_url, true) - url.set("pathname", value) if value - url = parseArgs(url, args) if args.length - url.toString() - memo -, {} - -module.exports = routes diff --git a/packages/server/lib/util/routes.js b/packages/server/lib/util/routes.js new file mode 100644 index 0000000000..551c3590c2 --- /dev/null +++ b/packages/server/lib/util/routes.js @@ -0,0 +1,70 @@ +/* eslint-disable + default-case, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const _ = require('lodash') +const UrlParse = require('url-parse') +const konfig = require('../konfig') + +const api_url = konfig('api_url') + +let routes = { + api: '', + auth: 'auth', + ping: 'ping', + signin: 'signin', + signout: 'signout', + runs: 'runs', + instances: 'runs/:id/instances', + instance: 'instances/:id', + instanceStdout: 'instances/:id/stdout', + orgs: 'organizations', + projects: 'projects', + project: 'projects/:id', + projectToken: 'projects/:id/token', + projectRuns: 'projects/:id/runs', + projectRecordKeys: 'projects/:id/keys', + exceptions: 'exceptions', + membershipRequests: 'projects/:id/membership_requests', +} + +const parseArgs = function (url, args = []) { + _.each(args, (value) => { + switch (false) { + case !_.isObject(value): + return url.set('query', _.extend(url.query, value)) + + case !_.isString(value) && !_.isNumber(value): + return url.set('pathname', url.pathname.replace(':id', value)) + } + }) + + return url +} + +routes = _.reduce(routes, (memo, value, key) => { + memo[key] = function (...args) { + let url = new UrlParse(api_url, true) + + if (value) { + url.set('pathname', value) + } + + if (args.length) { + url = parseArgs(url, args) + } + + return url.toString() + } + + return memo +} + , {}) + +module.exports = routes diff --git a/packages/server/lib/util/saved_state.coffee b/packages/server/lib/util/saved_state.coffee deleted file mode 100644 index 5e67006f5d..0000000000 --- a/packages/server/lib/util/saved_state.coffee +++ /dev/null @@ -1,53 +0,0 @@ -md5 = require("md5") -path = require("path") -debug = require("debug")("cypress:server:saved_state") -Promise = require("bluebird") -sanitize = require("sanitize-filename") -cwd = require("../cwd") -fs = require("../util/fs") - -toHashName = (projectRoot) -> - if not projectRoot - throw new Error("Missing project path") - - if not path.isAbsolute(projectRoot) - throw new Error("Expected project absolute path, not just a name #{projectRoot}") - - name = sanitize(path.basename(projectRoot)) - hash = md5(projectRoot) - "#{name}-#{hash}" - -# async promise-returning method -formStatePath = (projectRoot) -> - Promise.try -> - debug("making saved state from %s", cwd()) - - if projectRoot - debug("for project path %s", projectRoot) - return projectRoot - - debug("missing project path, looking for project here") - - cypressJsonPath = cwd("cypress.json") - fs.pathExistsAsync(cypressJsonPath) - .then (found) -> - if found - debug("found cypress file %s", cypressJsonPath) - projectRoot = cwd() - - return projectRoot - - .then (projectRoot) -> - fileName = "state.json" - - if projectRoot - debug("state path for project #{projectRoot}") - return path.join(toHashName(projectRoot), fileName) - - debug("state path for global mode") - return path.join("__global__", fileName) - -module.exports = { - toHashName - formStatePath -} diff --git a/packages/server/lib/util/saved_state.js b/packages/server/lib/util/saved_state.js new file mode 100644 index 0000000000..c8131c8f62 --- /dev/null +++ b/packages/server/lib/util/saved_state.js @@ -0,0 +1,74 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const md5 = require('md5') +const path = require('path') +const debug = require('debug')('cypress:server:saved_state') +const Promise = require('bluebird') +const sanitize = require('sanitize-filename') +const cwd = require('../cwd') +const fs = require('../util/fs') + +const toHashName = function (projectRoot) { + if (!projectRoot) { + throw new Error('Missing project path') + } + + if (!path.isAbsolute(projectRoot)) { + throw new Error(`Expected project absolute path, not just a name ${projectRoot}`) + } + + const name = sanitize(path.basename(projectRoot)) + const hash = md5(projectRoot) + + return `${name}-${hash}` +} + +// async promise-returning method +const formStatePath = (projectRoot) => { + return Promise.try(() => { + debug('making saved state from %s', cwd()) + + if (projectRoot) { + debug('for project path %s', projectRoot) + + return projectRoot + } + + debug('missing project path, looking for project here') + + const cypressJsonPath = cwd('cypress.json') + + return fs.pathExistsAsync(cypressJsonPath) + .then((found) => { + if (found) { + debug('found cypress file %s', cypressJsonPath) + projectRoot = cwd() + } + + return projectRoot + }) + }).then((projectRoot) => { + const fileName = 'state.json' + + if (projectRoot) { + debug(`state path for project ${projectRoot}`) + + return path.join(toHashName(projectRoot), fileName) + } + + debug('state path for global mode') + + return path.join('__global__', fileName) + }) +} + + +module.exports = { + toHashName, + formStatePath, +} diff --git a/packages/server/lib/util/security.coffee b/packages/server/lib/util/security.coffee deleted file mode 100644 index c9086e3f35..0000000000 --- a/packages/server/lib/util/security.coffee +++ /dev/null @@ -1,29 +0,0 @@ -stream = require("stream") -pumpify = require("pumpify") -replacestream = require("replacestream") - -topOrParentEqualityBeforeRe = /((?:window|self)(?:\.|\[['"](?:top|self)['"]\])?\s*[!=]==?\s*(?:(?:window|self)(?:\.|\[['"]))?)(top|parent)/g -topOrParentEqualityAfterRe = /(top|parent)((?:["']\])?\s*[!=]==?\s*(?:window|self))/g -topOrParentLocationOrFramesRe = /([^\da-zA-Z])(top|parent)([.])(location|frames)/g -jiraTopWindowGetterRe = /(!function\s*\((\w{1})\)\s*{\s*return\s*\w{1}\s*(?:={2,})\s*\w{1}\.parent)(\s*}\(\w{1}\))/g - -strip = (html) -> - html - .replace(topOrParentEqualityBeforeRe, "$1self") - .replace(topOrParentEqualityAfterRe, "self$2") - .replace(topOrParentLocationOrFramesRe, "$1self$3$4") - .replace(jiraTopWindowGetterRe, "$1 || $2.parent.__Cypress__$3") - -stripStream = -> - pumpify( - replacestream(topOrParentEqualityBeforeRe, "$1self") - replacestream(topOrParentEqualityAfterRe, "self$2") - replacestream(topOrParentLocationOrFramesRe, "$1self$3$4") - replacestream(jiraTopWindowGetterRe, "$1 || $2.parent.__Cypress__$3") - ) - -module.exports = { - strip - - stripStream -} diff --git a/packages/server/lib/util/security.js b/packages/server/lib/util/security.js new file mode 100644 index 0000000000..e6be56584d --- /dev/null +++ b/packages/server/lib/util/security.js @@ -0,0 +1,43 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const stream = require('stream') +const pumpify = require('pumpify') +const replacestream = require('replacestream') + +const topOrParentEqualityBeforeRe = /((?:window|self)(?:\.|\[['"](?:top|self)['"]\])?\s*[!=]==?\s*(?:(?:window|self)(?:\.|\[['"]))?)(top|parent)/g +const topOrParentEqualityAfterRe = /(top|parent)((?:["']\])?\s*[!=]==?\s*(?:window|self))/g +const topOrParentLocationOrFramesRe = /([^\da-zA-Z])(top|parent)([.])(location|frames)/g +const jiraTopWindowGetterRe = /(!function\s*\((\w{1})\)\s*{\s*return\s*\w{1}\s*(?:={2,})\s*\w{1}\.parent)(\s*}\(\w{1}\))/g + +const strip = (html) => { + return html + .replace(topOrParentEqualityBeforeRe, '$1self') + .replace(topOrParentEqualityAfterRe, 'self$2') + .replace(topOrParentLocationOrFramesRe, '$1self$3$4') + .replace(jiraTopWindowGetterRe, '$1 || $2.parent.__Cypress__$3') +} + + +const stripStream = () => { + return pumpify( + replacestream(topOrParentEqualityBeforeRe, '$1self'), + replacestream(topOrParentEqualityAfterRe, 'self$2'), + replacestream(topOrParentLocationOrFramesRe, '$1self$3$4'), + replacestream(jiraTopWindowGetterRe, '$1 || $2.parent.__Cypress__$3') + ) +} + + +module.exports = { + strip, + + stripStream, +} diff --git a/packages/server/lib/util/server_destroy.coffee b/packages/server/lib/util/server_destroy.coffee deleted file mode 100644 index 8e201272eb..0000000000 --- a/packages/server/lib/util/server_destroy.coffee +++ /dev/null @@ -1,10 +0,0 @@ -Promise = require("bluebird") -allowDestroy = require("server-destroy") - -module.exports = (server) -> - allowDestroy(server) - - server.destroyAsync = -> - Promise.promisify(server.destroy)() - .catch -> - ## dont catch any errors diff --git a/packages/server/lib/util/server_destroy.js b/packages/server/lib/util/server_destroy.js new file mode 100644 index 0000000000..802539e376 --- /dev/null +++ b/packages/server/lib/util/server_destroy.js @@ -0,0 +1,19 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Promise = require('bluebird') +const allowDestroy = require('server-destroy') + +module.exports = function (server) { + allowDestroy(server) + + server.destroyAsync = () => { + return Promise.promisify(server.destroy)() + .catch(() => {}) + } +} +//# dont catch any errors diff --git a/packages/server/lib/util/settings.coffee b/packages/server/lib/util/settings.coffee deleted file mode 100644 index 748dee9122..0000000000 --- a/packages/server/lib/util/settings.coffee +++ /dev/null @@ -1,142 +0,0 @@ -_ = require("lodash") -Promise = require("bluebird") -path = require("path") -errors = require("../errors") -log = require("../log") -fs = require("../util/fs") - -## TODO: -## think about adding another PSemaphore -## here since we can read + write the -## settings at the same time something else -## is potentially reading it - -flattenCypress = (obj) -> - if cypress = obj.cypress - return cypress - -renameVisitToPageLoad = (obj) -> - if v = obj.visitTimeout - obj = _.omit(obj, "visitTimeout") - obj.pageLoadTimeout = v - obj - -renameCommandTimeout = (obj) -> - if c = obj.commandTimeout - obj = _.omit(obj, "commandTimeout") - obj.defaultCommandTimeout = c - obj - -renameSupportFolder = (obj) -> - if sf = obj.supportFolder - obj = _.omit(obj, "supportFolder") - obj.supportFile = sf - obj - -module.exports = - _pathToFile: (projectRoot, file) -> - path.join(projectRoot, file) - - _err: (type, file, err) -> - e = errors.get(type, file, err) - e.code = err.code - e.errno = err.errno - throw e - - _logReadErr: (file, err) -> - @_err("ERROR_READING_FILE", file, err) - - _logWriteErr: (file, err) -> - @_err("ERROR_WRITING_FILE", file, err) - - _write: (file, obj = {}) -> - fs.outputJsonAsync(file, obj, {spaces: 2}) - .return(obj) - .catch (err) => - @_logWriteErr(file, err) - - _applyRewriteRules: (obj = {}) -> - _.reduce [flattenCypress, renameVisitToPageLoad, renameCommandTimeout, renameSupportFolder], (memo, fn) -> - if ret = fn(memo) - return ret - else - return memo - , _.cloneDeep(obj) - - id: (projectRoot) -> - file = @_pathToFile(projectRoot, "cypress.json") - - fs.readJsonAsync(file) - .get("projectId") - .catch -> - null - - exists: (projectRoot) -> - file = @_pathToFile(projectRoot, "cypress.json") - - ## first check if cypress.json exists - fs.statAsync(file) - .then -> - ## if it does also check that the projectRoot - ## directory is writable - fs.accessAsync(projectRoot, fs.W_OK) - .catch {code: "ENOENT"}, (err) => - ## cypress.json does not exist, we missing project - log("cannot find file %s", file) - @_err("PROJECT_DOES_NOT_EXIST", projectRoot, err) - .catch (err) => - throw err if errors.isCypressErr(err) - - ## else we cannot read due to folder permissions - @_logReadErr(file, err) - - read: (projectRoot) -> - file = @_pathToFile(projectRoot, "cypress.json") - - fs.readJsonAsync(file) - .catch {code: "ENOENT"}, => - @_write(file, {}) - .then (json = {}) => - changed = @_applyRewriteRules(json) - - ## if our object is unchanged - ## then just return it - if _.isEqual(json, changed) - return json - else - ## else write the new reduced obj - @_write(file, changed) - - .catch (err) => - throw err if errors.isCypressErr(err) - - @_logReadErr(file, err) - - readEnv: (projectRoot) -> - file = @_pathToFile(projectRoot, "cypress.env.json") - - fs.readJsonAsync(file) - .catch {code: "ENOENT"}, -> - return {} - .catch (err) => - throw err if errors.isCypressErr(err) - - @_logReadErr(file, err) - - write: (projectRoot, obj = {}) -> - @read(projectRoot) - .then (settings) => - _.extend settings, obj - - file = @_pathToFile(projectRoot, "cypress.json") - - @_write(file, settings) - - remove: (projectRoot) -> - fs.unlinkSync @_pathToFile(projectRoot, "cypress.json") - - pathToCypressJson: (projectRoot) -> - @_pathToFile(projectRoot, "cypress.json") - - pathToCypressEnvJson: (projectRoot) -> - @_pathToFile(projectRoot, "cypress.env.json") diff --git a/packages/server/lib/util/settings.js b/packages/server/lib/util/settings.js new file mode 100644 index 0000000000..b4042502cb --- /dev/null +++ b/packages/server/lib/util/settings.js @@ -0,0 +1,210 @@ +/* eslint-disable + brace-style, + no-cond-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const _ = require('lodash') +const Promise = require('bluebird') +const path = require('path') +const errors = require('../errors') +const log = require('../log') +const fs = require('../util/fs') + +//# TODO: +//# think about adding another PSemaphore +//# here since we can read + write the +//# settings at the same time something else +//# is potentially reading it + +const flattenCypress = function (obj) { + let cypress + + if (cypress = obj.cypress) { + return cypress + } +} + +const renameVisitToPageLoad = function (obj) { + let v + + if (v = obj.visitTimeout) { + obj = _.omit(obj, 'visitTimeout') + obj.pageLoadTimeout = v + + return obj + } +} + +const renameCommandTimeout = function (obj) { + let c + + if (c = obj.commandTimeout) { + obj = _.omit(obj, 'commandTimeout') + obj.defaultCommandTimeout = c + + return obj + } +} + +const renameSupportFolder = function (obj) { + let sf + + if (sf = obj.supportFolder) { + obj = _.omit(obj, 'supportFolder') + obj.supportFile = sf + + return obj + } +} + +module.exports = { + _pathToFile (projectRoot, file) { + return path.join(projectRoot, file) + }, + + _err (type, file, err) { + const e = errors.get(type, file, err) + + e.code = err.code + e.errno = err.errno + throw e + }, + + _logReadErr (file, err) { + return this._err('ERROR_READING_FILE', file, err) + }, + + _logWriteErr (file, err) { + return this._err('ERROR_WRITING_FILE', file, err) + }, + + _write (file, obj = {}) { + return fs.outputJsonAsync(file, obj, { spaces: 2 }) + .return(obj) + .catch((err) => { + return this._logWriteErr(file, err) + }) + }, + + _applyRewriteRules (obj = {}) { + return _.reduce([flattenCypress, renameVisitToPageLoad, renameCommandTimeout, renameSupportFolder], (memo, fn) => { + let ret + + if (ret = fn(memo)) { + return ret + } + + return memo + + } + , _.cloneDeep(obj)) + }, + + id (projectRoot) { + const file = this._pathToFile(projectRoot, 'cypress.json') + + return fs.readJsonAsync(file) + .get('projectId') + .catch(() => { + return null + }) + }, + + exists (projectRoot) { + const file = this._pathToFile(projectRoot, 'cypress.json') + + //# first check if cypress.json exists + return fs.statAsync(file) + .then(() => + //# if it does also check that the projectRoot + //# directory is writable + { + return fs.accessAsync(projectRoot, fs.W_OK) + }).catch({ code: 'ENOENT' }, (err) => { + //# cypress.json does not exist, we missing project + log('cannot find file %s', file) + + return this._err('PROJECT_DOES_NOT_EXIST', projectRoot, err) + }).catch((err) => { + if (errors.isCypressErr(err)) { + throw err + } + + //# else we cannot read due to folder permissions + return this._logReadErr(file, err) + }) + }, + + read (projectRoot) { + const file = this._pathToFile(projectRoot, 'cypress.json') + + return fs.readJsonAsync(file) + .catch({ code: 'ENOENT' }, () => { + return this._write(file, {}) + }).then((json = {}) => { + const changed = this._applyRewriteRules(json) + + //# if our object is unchanged + //# then just return it + if (_.isEqual(json, changed)) { + return json + } + + //# else write the new reduced obj + return this._write(file, changed) + + }).catch((err) => { + if (errors.isCypressErr(err)) { + throw err + } + + return this._logReadErr(file, err) + }) + }, + + readEnv (projectRoot) { + const file = this._pathToFile(projectRoot, 'cypress.env.json') + + return fs.readJsonAsync(file) + .catch({ code: 'ENOENT' }, () => { + return {} + }) + .catch((err) => { + if (errors.isCypressErr(err)) { + throw err + } + + return this._logReadErr(file, err) + }) + }, + + write (projectRoot, obj = {}) { + return this.read(projectRoot) + .then((settings) => { + _.extend(settings, obj) + + const file = this._pathToFile(projectRoot, 'cypress.json') + + return this._write(file, settings) + }) + }, + + remove (projectRoot) { + return fs.unlinkSync(this._pathToFile(projectRoot, 'cypress.json')) + }, + + pathToCypressJson (projectRoot) { + return this._pathToFile(projectRoot, 'cypress.json') + }, + + pathToCypressEnvJson (projectRoot) { + return this._pathToFile(projectRoot, 'cypress.env.json') + }, +} diff --git a/packages/server/lib/util/shell.coffee b/packages/server/lib/util/shell.coffee deleted file mode 100644 index 00616595c5..0000000000 --- a/packages/server/lib/util/shell.coffee +++ /dev/null @@ -1,83 +0,0 @@ -Promise = require("bluebird") -execa = require("execa") -R = require("ramda") -os = require("os") -log = require("../log") - -isWindows = -> - os.platform() == "win32" - -profiles = { - "~/.profile": /\/sh$/ - "~/.bash_profile": /\/bash$/ - "~/.cshrc": /\/csh$/ - "~/.profile": /\/ksh$/ - "~/.zshrc": /\/zsh$/ - "~/.config/fish/config.fish": /\/fish$/ -} - -sourcedProfiles = [] - -## returns true if Cypress application has been started from -## the terminal shell. -## returns false if Cypress application has been started -## from the Finder / Windows Explorer list -## by double clicking its icon -startedNormally = -> - Boolean(process.env._) - -getProfilePath = (shellPath) -> - for profilePath, regex of profiles - return profilePath if regex.test(shellPath) - -sourceShellCommand = (cmd, shell) -> - if not shell - return cmd - profilePath = getProfilePath(shell) - log("shell %s profile %s", shell, profilePath) - if sourcedProfiles.includes(profilePath) - log "profile has already been sourced" - cmd - else - haveShell = startedNormally() - if haveShell - ## we only need to source once - ## IF THE APP HAS NOT BEEN STARTED BY - ## DOUBLE CLICKING IT FROM FINDER / WINDOWS EXPLORER - ## OTHERWISE NEED TO SOURCE EVERY COMMAND - sourcedProfiles.push(profilePath) - ## sourcing the profile can output un-needed garbage, - ## so suppress it by sending it to /dev/null and ignore - ## any failures with this - "source #{profilePath} > /dev/null 2>&1; #{cmd}" - -findBash = -> - execa.shell("which bash") - .then(R.prop("stdout")) - -getShell = (shell) -> - return Promise.resolve(shell) if shell - - ## if we didn't get a shell such - ## as when we're in docker - if s = process.env.SHELL - return Promise.resolve(s) - - if isWindows() - log("use default shell on Windows") - return Promise.resolve() - - findBash() - -# for testing -reset = -> - sourcedProfiles = [] - -module.exports = { - reset, - findBash, - getShell, - getProfilePath, - sourceShellCommand, - startedNormally -} diff --git a/packages/server/lib/util/shell.js b/packages/server/lib/util/shell.js new file mode 100644 index 0000000000..69ab538318 --- /dev/null +++ b/packages/server/lib/util/shell.js @@ -0,0 +1,123 @@ +/* eslint-disable + no-cond-assign, + no-dupe-keys, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Promise = require('bluebird') +const execa = require('execa') +const R = require('ramda') +const os = require('os') +const log = require('../log') + +const isWindows = () => { + return os.platform() === 'win32' +} + +const profiles = { + '~/.profile': /\/sh$/, + '~/.bash_profile': /\/bash$/, + '~/.cshrc': /\/csh$/, + '~/.profile': /\/ksh$/, + '~/.zshrc': /\/zsh$/, + '~/.config/fish/config.fish': /\/fish$/, +} + +let sourcedProfiles = [] + +//# returns true if Cypress application has been started from +//# the terminal shell. +//# returns false if Cypress application has been started +//# from the Finder / Windows Explorer list +//# by double clicking its icon +const startedNormally = () => { + return Boolean(process.env._) +} + +const getProfilePath = function (shellPath) { + for (let profilePath in profiles) { + const regex = profiles[profilePath] + + if (regex.test(shellPath)) { + return profilePath + } + } +} + +const sourceShellCommand = function (cmd, shell) { + if (!shell) { + return cmd + } + + const profilePath = getProfilePath(shell) + + log('shell %s profile %s', shell, profilePath) + if (sourcedProfiles.includes(profilePath)) { + log('profile has already been sourced') + + return cmd + } + + const haveShell = startedNormally() + + if (haveShell) { + //# we only need to source once + //# IF THE APP HAS NOT BEEN STARTED BY + //# DOUBLE CLICKING IT FROM FINDER / WINDOWS EXPLORER + //# OTHERWISE NEED TO SOURCE EVERY COMMAND + sourcedProfiles.push(profilePath) + } + + //# sourcing the profile can output un-needed garbage, + //# so suppress it by sending it to /dev/null and ignore + //# any failures with this + return `source ${profilePath} > /dev/null 2>&1; ${cmd}` + +} + +const findBash = () => { + return execa.shell('which bash') + .then(R.prop('stdout')) +} + + +const getShell = function (shell) { + let s + + if (shell) { + return Promise.resolve(shell) + } + + //# if we didn't get a shell such + //# as when we're in docker + if (s = process.env.SHELL) { + return Promise.resolve(s) + } + + if (isWindows()) { + log('use default shell on Windows') + + return Promise.resolve() + } + + return findBash() +} + +// for testing +const reset = () => { + return sourcedProfiles = [] +} + +module.exports = { + reset, + findBash, + getShell, + getProfilePath, + sourceShellCommand, + startedNormally, +} diff --git a/packages/server/lib/util/specs.coffee b/packages/server/lib/util/specs.coffee deleted file mode 100644 index 3bfb5742c0..0000000000 --- a/packages/server/lib/util/specs.coffee +++ /dev/null @@ -1,124 +0,0 @@ -_ = require("lodash") -la = require("lazy-ass") -path = require("path") -check = require("check-more-types") -debug = require("debug")("cypress:server:specs") -minimatch = require("minimatch") -glob = require("./glob") - -MINIMATCH_OPTIONS = { dot: true, matchBase: true } - -getPatternRelativeToProjectRoot = (specPattern, projectRoot) -> - _.map specPattern, (p) -> - path.relative(projectRoot, p) - -find = (config, specPattern) -> - la(check.maybe.strings(specPattern), "invalid spec pattern", specPattern) - - integrationFolderPath = config.integrationFolder - - debug( - "looking for test specs in the folder:", - integrationFolderPath - ) - - ## support files are not automatically - ## ignored because only _fixtures are hard - ## coded. the rest is simply whatever is in - ## the javascripts array - - if config.fixturesFolder - fixturesFolderPath = path.join( - config.fixturesFolder, - "**", - "*" - ) - - supportFilePath = config.supportFile or [] - - ## map all of the javascripts to the project root - ## TODO: think about moving this into config - ## and mapping each of the javascripts into an - ## absolute path - javascriptsPaths = _.map config.javascripts, (js) -> - path.join(config.projectRoot, js) - - ## ignore fixtures + javascripts - options = { - sort: true - absolute: true - nodir: true - cwd: integrationFolderPath - ignore: _.compact(_.flatten([ - javascriptsPaths - supportFilePath - fixturesFolderPath - ])) - } - - ## filePath = /Users/bmann/Dev/my-project/cypress/integration/foo.coffee - ## integrationFolderPath = /Users/bmann/Dev/my-project/cypress/integration - ## relativePathFromIntegrationFolder = foo.coffee - ## relativePathFromProjectRoot = cypress/integration/foo.coffee - - relativePathFromIntegrationFolder = (file) -> - path.relative(integrationFolderPath, file) - - relativePathFromProjectRoot = (file) -> - path.relative(config.projectRoot, file) - - setNameParts = (file) -> - debug("found spec file %s", file) - - if not path.isAbsolute(file) - throw new Error("Cannot set parts of file from non-absolute path #{file}") - - { - name: relativePathFromIntegrationFolder(file) - relative: relativePathFromProjectRoot(file) - absolute: file - } - - ignorePatterns = [].concat(config.ignoreTestFiles) - - ## a function which returns true if the file does NOT match - ## all of our ignored patterns - doesNotMatchAllIgnoredPatterns = (file) -> - ## using {dot: true} here so that folders with a '.' in them are matched - ## as regular characters without needing an '.' in the - ## using {matchBase: true} here so that patterns without a globstar ** - ## match against the basename of the file - _.every ignorePatterns, (pattern) -> - not minimatch(file, pattern, MINIMATCH_OPTIONS) - - matchesSpecPattern = (file) -> - if not specPattern - return true - - matchesPattern = (pattern) -> - minimatch(file, pattern, MINIMATCH_OPTIONS) - - ## check to see if the file matches - ## any of the spec patterns array - return _ - .chain([]) - .concat(specPattern) - .some(matchesPattern) - .value() - - ## grab all the files - glob(config.testFiles, options) - - ## filter out anything that matches our - ## ignored test files glob - .filter(doesNotMatchAllIgnoredPatterns) - .filter(matchesSpecPattern) - .map(setNameParts) - .tap (files) -> - debug("found %d spec files: %o", files.length, files) - -module.exports = { - find - - getPatternRelativeToProjectRoot -} diff --git a/packages/server/lib/util/specs.js b/packages/server/lib/util/specs.js new file mode 100644 index 0000000000..4514809bad --- /dev/null +++ b/packages/server/lib/util/specs.js @@ -0,0 +1,154 @@ +/* eslint-disable + brace-style, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const _ = require('lodash') +const la = require('lazy-ass') +const path = require('path') +const check = require('check-more-types') +const debug = require('debug')('cypress:server:specs') +const minimatch = require('minimatch') +const glob = require('./glob') + +const MINIMATCH_OPTIONS = { dot: true, matchBase: true } + +const getPatternRelativeToProjectRoot = (specPattern, projectRoot) => { + return _.map(specPattern, (p) => { + return path.relative(projectRoot, p) + }) +} + + +const find = function (config, specPattern) { + let fixturesFolderPath + + la(check.maybe.strings(specPattern), 'invalid spec pattern', specPattern) + + const integrationFolderPath = config.integrationFolder + + debug( + 'looking for test specs in the folder:', + integrationFolderPath + ) + + //# support files are not automatically + //# ignored because only _fixtures are hard + //# coded. the rest is simply whatever is in + //# the javascripts array + + if (config.fixturesFolder) { + fixturesFolderPath = path.join( + config.fixturesFolder, + '**', + '*' + ) + } + + const supportFilePath = config.supportFile || [] + + //# map all of the javascripts to the project root + //# TODO: think about moving this into config + //# and mapping each of the javascripts into an + //# absolute path + const javascriptsPaths = _.map(config.javascripts, (js) => { + return path.join(config.projectRoot, js) + }) + + //# ignore fixtures + javascripts + const options = { + sort: true, + absolute: true, + nodir: true, + cwd: integrationFolderPath, + ignore: _.compact(_.flatten([ + javascriptsPaths, + supportFilePath, + fixturesFolderPath, + ])), + } + + //# filePath = /Users/bmann/Dev/my-project/cypress/integration/foo.coffee + //# integrationFolderPath = /Users/bmann/Dev/my-project/cypress/integration + //# relativePathFromIntegrationFolder = foo.coffee + //# relativePathFromProjectRoot = cypress/integration/foo.coffee + + const relativePathFromIntegrationFolder = (file) => { + return path.relative(integrationFolderPath, file) + } + + const relativePathFromProjectRoot = (file) => { + return path.relative(config.projectRoot, file) + } + + const setNameParts = function (file) { + debug('found spec file %s', file) + + if (!path.isAbsolute(file)) { + throw new Error(`Cannot set parts of file from non-absolute path ${file}`) + } + + return { + name: relativePathFromIntegrationFolder(file), + relative: relativePathFromProjectRoot(file), + absolute: file, + } + } + + const ignorePatterns = [].concat(config.ignoreTestFiles) + + //# a function which returns true if the file does NOT match + //# all of our ignored patterns + const doesNotMatchAllIgnoredPatterns = (file) => + //# using {dot: true} here so that folders with a '.' in them are matched + //# as regular characters without needing an '.' in the + //# using {matchBase: true} here so that patterns without a globstar ** + //# match against the basename of the file + { + return _.every(ignorePatterns, (pattern) => { + return !minimatch(file, pattern, MINIMATCH_OPTIONS) + }) + } + + + const matchesSpecPattern = function (file) { + if (!specPattern) { + return true + } + + const matchesPattern = (pattern) => { + return minimatch(file, pattern, MINIMATCH_OPTIONS) + } + + //# check to see if the file matches + //# any of the spec patterns array + return _ + .chain([]) + .concat(specPattern) + .some(matchesPattern) + .value() + } + + //# grab all the files + return glob(config.testFiles, options) + + //# filter out anything that matches our + //# ignored test files glob + .filter(doesNotMatchAllIgnoredPatterns) + .filter(matchesSpecPattern) + .map(setNameParts) + .tap((files) => { + return debug('found %d spec files: %o', files.length, files) + }) +} + +module.exports = { + find, + + getPatternRelativeToProjectRoot, +} diff --git a/packages/server/lib/util/status_code.coffee b/packages/server/lib/util/status_code.coffee deleted file mode 100644 index 0577dda510..0000000000 --- a/packages/server/lib/util/status_code.coffee +++ /dev/null @@ -1,15 +0,0 @@ -statuses = require("http-status-codes") - -isOkStatusCodeRe = /^[2|3]\d+$/ - -module.exports = { - isOk: (code) -> - code and isOkStatusCodeRe.test(code) - - ## TODO: test this method - getText: (code) -> - try - statuses.getStatusText(code) - catch e - "Unknown Status Code" -} \ No newline at end of file diff --git a/packages/server/lib/util/status_code.js b/packages/server/lib/util/status_code.js new file mode 100644 index 0000000000..cb9f9e3242 --- /dev/null +++ b/packages/server/lib/util/status_code.js @@ -0,0 +1,25 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const statuses = require('http-status-codes') + +const isOkStatusCodeRe = /^[2|3]\d+$/ + +module.exports = { + isOk (code) { + return code && isOkStatusCodeRe.test(code) + }, + + //# TODO: test this method + getText (code) { + try { + return statuses.getStatusText(code) + } catch (e) { + return 'Unknown Status Code' + } + }, +} diff --git a/packages/server/lib/util/system.coffee b/packages/server/lib/util/system.coffee deleted file mode 100644 index 36442e7cd6..0000000000 --- a/packages/server/lib/util/system.coffee +++ /dev/null @@ -1,29 +0,0 @@ -os = require("os") -Promise = require("bluebird") -getos = Promise.promisify(require("getos")) - -getOsVersion = -> - Promise.try -> - if os.platform() is "linux" - getos() - .then (obj) -> - [obj.dist, obj.release].join(" - ") - .catch (err) -> - os.release() - else - os.release() - -module.exports = { - info: -> - getOsVersion() - .then (osVersion) -> - { - osName: os.platform() - osVersion: osVersion - osCpus: os.cpus() - osMemory: { - free: os.freemem() - total: os.totalmem() - } - } -} diff --git a/packages/server/lib/util/system.js b/packages/server/lib/util/system.js new file mode 100644 index 0000000000..54335fa32c --- /dev/null +++ b/packages/server/lib/util/system.js @@ -0,0 +1,47 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const os = require('os') +const Promise = require('bluebird') +const getos = Promise.promisify(require('getos')) + +const getOsVersion = () => { + return Promise.try(() => { + if (os.platform() === 'linux') { + return getos() + .then((obj) => { + return [obj.dist, obj.release].join(' - ') + }).catch((err) => { + return os.release() + }) + } + + return os.release() + + }) +} + + +module.exports = { + info () { + return getOsVersion() + .then((osVersion) => { + return { + osName: os.platform(), + osVersion, + osCpus: os.cpus(), + osMemory: { + free: os.freemem(), + total: os.totalmem(), + }, + } + }) + }, +} diff --git a/packages/server/lib/util/terminal.coffee b/packages/server/lib/util/terminal.coffee deleted file mode 100644 index 6d94093d50..0000000000 --- a/packages/server/lib/util/terminal.coffee +++ /dev/null @@ -1,195 +0,0 @@ -_ = require("lodash") -chalk = require("chalk") -Table = require("cli-table2") -utils = require("cli-table2/src/utils") -widestLine = require("widest-line") -terminalSize = require("./terminal-size") - -MAXIMUM_SIZE = 100 -EXPECTED_SUM = 100 - -getMaximumColumns = -> - ## get the maximum amount of columns - ## that can fit in the terminal - Math.min(MAXIMUM_SIZE, terminalSize.get().columns) - -getBordersLength = (left, right) -> - _ - .chain([left, right]) - .compact() - .map(widestLine) - .sum() - .value() - -convertDecimalsToNumber = (colWidths, cols) -> - sum = _.sum(colWidths) - - if sum isnt EXPECTED_SUM - throw new Error("Expected colWidths array to sum to: #{EXPECTED_SUM}, instead got: #{sum}") - - [50, 10, 25] - - widths = _.map colWidths, (width) -> - ## easier to deal with numbers than floats... - num = (cols * width) / EXPECTED_SUM - - Math.floor(num) - - total = _.sum(widths) - - ## if we got a sum less than the total - ## columns, then add the difference to - ## the first element in the array of widths - if (diff = cols - total) > 0 - first = widths[0] - widths[0] += diff - - widths - -renderTables = (tables...) -> - _ - .chain([]) - .concat(tables) - .invokeMap("toString") - .join("\n") - .value() - -getChars = (type) -> - switch type - when "border" - return { - "top-mid": "" - "top-left": " ┌" - "left": " │" - "left-mid": " ├" - "middle": "" - "mid-mid": "" - "bottom-mid": "" - "bottom-left": " └" - } - when "noBorder" - return { - "top": "" - "top-mid": "" - "top-left": "" - "top-right": "" - "left": " " - "left-mid": "" - "middle": "" - "mid": "" - "mid-mid": "" - "right": "" - "right-mid": "" - "bottom": "" - "bottom-left": "" - "bottom-mid": "" - "bottom-right": "" - } - when "outsideBorder" - return { - # "top": "" - "top-left": " ┌" - "top-mid": "" - "left": " │" - "left-mid": "" - "middle": "" - "mid": "" - "mid-mid": "" - "right-mid": "" - "bottom-mid": "" - "bottom-left": " └" - } - when "pageDivider" - return { - "top": "─" - "top-mid": "" - "top-left": "" - "top-right": "" - "bottom": "" - "bottom-mid": "" - "bottom-left": "" - "bottom-right": "" - "left": "" - "left-mid": "" - "mid": "" - "mid-mid": "" - "right": "" - "right-mid": "" - "middle": "" - } - -wrapBordersInGray = (chars) -> - _.mapValues chars, (char) -> - if char - chalk.gray(char) - else - char - -table = (options = {}) -> - { colWidths, type } = options - - defaults = utils.mergeOptions({}) - - chars = _.defaults(getChars(type), defaults.chars) - - _.defaultsDeep(options, { - chars - style: { - head: [] - border: [] - 'padding-left': 1 - 'padding-right': 1 - } - }) - - { chars } = options - - borders = getBordersLength(chars.left, chars.right) - - options.chars = wrapBordersInGray(chars) - - if colWidths - ## subtract borders to get the actual size - ## so it displaces a maximum number of columns - cols = getMaximumColumns() - borders - options.colWidths = convertDecimalsToNumber(colWidths, cols) - - new Table(options) - -header = (message, options = {}) -> - _.defaults(options, { - color: null - }) - - message = " (" + chalk.underline.bold(message) + ")" - - if c = options.color - colors = [].concat(c) - - message = _.reduce colors, (memo, color) -> - chalk[color](memo) - , message - - console.log(message) - -divider = (symbol, color = "gray") -> - cols = getMaximumColumns() - - str = symbol.repeat(cols) - - console.log(chalk[color](str)) - -module.exports = { - table - - header - - divider - - renderTables - - getMaximumColumns - - convertDecimalsToNumber - -} diff --git a/packages/server/lib/util/terminal.js b/packages/server/lib/util/terminal.js new file mode 100644 index 0000000000..dc571efad3 --- /dev/null +++ b/packages/server/lib/util/terminal.js @@ -0,0 +1,238 @@ +/* eslint-disable + brace-style, + default-case, + no-cond-assign, + no-console, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const _ = require('lodash') +const chalk = require('chalk') +const Table = require('cli-table2') +const utils = require('cli-table2/src/utils') +const widestLine = require('widest-line') +const terminalSize = require('./terminal-size') + +const MAXIMUM_SIZE = 100 +const EXPECTED_SUM = 100 + +const getMaximumColumns = () => +//# get the maximum amount of columns +//# that can fit in the terminal +{ + return Math.min(MAXIMUM_SIZE, terminalSize.get().columns) +} + + +const getBordersLength = (left, right) => { + return _ + .chain([left, right]) + .compact() + .map(widestLine) + .sum() + .value() +} + + +const convertDecimalsToNumber = function (colWidths, cols) { + let diff + const sum = _.sum(colWidths) + + if (sum !== EXPECTED_SUM) { + throw new Error(`Expected colWidths array to sum to: ${EXPECTED_SUM}, instead got: ${sum}`) + } + + [50, 10, 25] + + const widths = _.map(colWidths, (width) => { + //# easier to deal with numbers than floats... + const num = (cols * width) / EXPECTED_SUM + + return Math.floor(num) + }) + + const total = _.sum(widths) + + //# if we got a sum less than the total + //# columns, then add the difference to + //# the first element in the array of widths + if ((diff = cols - total) > 0) { + const first = widths[0] + + widths[0] += diff + } + + return widths +} + +const renderTables = (...tables) => { + return _ + .chain([]) + .concat(tables) + .invokeMap('toString') + .join('\n') + .value() +} + + +const getChars = function (type) { + switch (type) { + case 'border': + return { + 'top-mid': '', + 'top-left': ' ┌', + 'left': ' │', + 'left-mid': ' ├', + 'middle': '', + 'mid-mid': '', + 'bottom-mid': '', + 'bottom-left': ' └', + } + case 'noBorder': + return { + 'top': '', + 'top-mid': '', + 'top-left': '', + 'top-right': '', + 'left': ' ', + 'left-mid': '', + 'middle': '', + 'mid': '', + 'mid-mid': '', + 'right': '', + 'right-mid': '', + 'bottom': '', + 'bottom-left': '', + 'bottom-mid': '', + 'bottom-right': '', + } + case 'outsideBorder': + return { + // "top": "" + 'top-left': ' ┌', + 'top-mid': '', + 'left': ' │', + 'left-mid': '', + 'middle': '', + 'mid': '', + 'mid-mid': '', + 'right-mid': '', + 'bottom-mid': '', + 'bottom-left': ' └', + } + case 'pageDivider': + return { + 'top': '─', + 'top-mid': '', + 'top-left': '', + 'top-right': '', + 'bottom': '', + 'bottom-mid': '', + 'bottom-left': '', + 'bottom-right': '', + 'left': '', + 'left-mid': '', + 'mid': '', + 'mid-mid': '', + 'right': '', + 'right-mid': '', + 'middle': '', + } + } +} + +const wrapBordersInGray = (chars) => { + return _.mapValues(chars, (char) => { + if (char) { + return chalk.gray(char) + } + + return char + + }) +} + + +const table = function (options = {}) { + const { colWidths, type } = options + + const defaults = utils.mergeOptions({}) + + let chars = _.defaults(getChars(type), defaults.chars) + + _.defaultsDeep(options, { + chars, + style: { + head: [], + border: [], + 'padding-left': 1, + 'padding-right': 1, + }, + }); + + ({ chars } = options) + + const borders = getBordersLength(chars.left, chars.right) + + options.chars = wrapBordersInGray(chars) + + if (colWidths) { + //# subtract borders to get the actual size + //# so it displaces a maximum number of columns + const cols = getMaximumColumns() - borders + + options.colWidths = convertDecimalsToNumber(colWidths, cols) + } + + return new Table(options) +} + +const header = function (message, options = {}) { + let c + + _.defaults(options, { + color: null, + }) + + message = ` (${chalk.underline.bold(message)})` + + if (c = options.color) { + const colors = [].concat(c) + + message = _.reduce(colors, (memo, color) => { + return chalk[color](memo) + } + , message) + } + + return console.log(message) +} + +const divider = function (symbol, color = 'gray') { + const cols = getMaximumColumns() + + const str = symbol.repeat(cols) + + return console.log(chalk[color](str)) +} + +module.exports = { + table, + + header, + + divider, + + renderTables, + + getMaximumColumns, + + convertDecimalsToNumber, + +} diff --git a/packages/server/lib/util/trash.coffee b/packages/server/lib/util/trash.coffee deleted file mode 100644 index 56d72ab078..0000000000 --- a/packages/server/lib/util/trash.coffee +++ /dev/null @@ -1,14 +0,0 @@ -fs = require("./fs") -path = require("path") -trash = require("trash") -Promise = require("bluebird") - -module.exports = { - folder: (pathToFolder) -> - fs.statAsync(pathToFolder) - .then -> - Promise.map(fs.readdirAsync(pathToFolder), (item) -> - return trash([path.join(pathToFolder, item)])) - .catch {code: "ENOENT"}, -> - return -} diff --git a/packages/server/lib/util/trash.js b/packages/server/lib/util/trash.js new file mode 100644 index 0000000000..57bc72bff5 --- /dev/null +++ b/packages/server/lib/util/trash.js @@ -0,0 +1,23 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const fs = require('./fs') +const path = require('path') +const trash = require('trash') +const Promise = require('bluebird') + +module.exports = { + folder (pathToFolder) { + return fs.statAsync(pathToFolder) + .then(() => { + return Promise.map(fs.readdirAsync(pathToFolder), (item) => { + return trash([path.join(pathToFolder, item)]) + }) + }).catch({ code: 'ENOENT' }, () => { + }) + }, +} diff --git a/packages/server/lib/util/uri.coffee b/packages/server/lib/util/uri.coffee deleted file mode 100644 index 3489285cb2..0000000000 --- a/packages/server/lib/util/uri.coffee +++ /dev/null @@ -1,35 +0,0 @@ -url = require("url") - -DEFAULT_PORTS = ["443", "80"] - -stripProtocolAndDefaultPorts = (urlToCheck) -> - ## grab host which is 'hostname:port' only - { host, hostname, port } = url.parse(urlToCheck) - - ## if we have a default port for 80 or 443 - ## then just return the hostname - if port in DEFAULT_PORTS - return hostname - - ## else return the host - return host - -removeDefaultPort = (urlToCheck) -> - parsed = url.parse(urlToCheck) - - if parsed.port in DEFAULT_PORTS - parsed.host = null - parsed.port = null - - url.format(parsed) - -getPath = (urlToCheck) -> - url.parse(urlToCheck).path - -module.exports = { - getPath - - removeDefaultPort - - stripProtocolAndDefaultPorts -} diff --git a/packages/server/lib/util/uri.js b/packages/server/lib/util/uri.js new file mode 100644 index 0000000000..e742e385b5 --- /dev/null +++ b/packages/server/lib/util/uri.js @@ -0,0 +1,47 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const url = require('url') + +const DEFAULT_PORTS = ['443', '80'] + +const stripProtocolAndDefaultPorts = function (urlToCheck) { + //# grab host which is 'hostname:port' only + const { host, hostname, port } = url.parse(urlToCheck) + + //# if we have a default port for 80 or 443 + //# then just return the hostname + if (DEFAULT_PORTS.includes(port)) { + return hostname + } + + //# else return the host + return host +} + +const removeDefaultPort = function (urlToCheck) { + const parsed = url.parse(urlToCheck) + + if (DEFAULT_PORTS.includes(parsed.port)) { + parsed.host = null + parsed.port = null + } + + return url.format(parsed) +} + +const getPath = (urlToCheck) => { + return url.parse(urlToCheck).path +} + +module.exports = { + getPath, + + removeDefaultPort, + + stripProtocolAndDefaultPorts, +} diff --git a/packages/server/lib/util/validation.coffee b/packages/server/lib/util/validation.coffee deleted file mode 100644 index 0a9fdb18d4..0000000000 --- a/packages/server/lib/util/validation.coffee +++ /dev/null @@ -1,78 +0,0 @@ -_ = require("lodash") -errors = require("../errors") - -## validation functions take a key and a value and should: -## - return true if it passes validation -## - return a error message if it fails validation - -errMsg = (key, value, type) -> - "Expected '#{key}' to be #{type}. Instead the value was: #{JSON.stringify(value)}" - -isFullyQualifiedUrl = (value) -> - isString(value) and /^https?\:\/\//.test(value) - -isArrayOfStrings = (value) -> - isArray(value) and _.every(value, isString) - -isFalse = (value) -> - value is false - -isArray = _.isArray -isNumber = _.isFinite -isString = _.isString - -module.exports = { - isNumber: (key, value) -> - if not value? or isNumber(value) - true - else - errMsg(key, value, "a number") - - isNumberOrFalse: (key, value) -> - if isNumber(value) or isFalse(value) - true - else - errMsg(key, value, "a number or false") - - isFullyQualifiedUrl: (key, value) -> - if not value? or isFullyQualifiedUrl(value) - return true - else - errMsg(key, value, "a fully qualified URL (starting with http:// or https://)") - - isBoolean: (key, value) -> - if not value? or _.isBoolean(value) - true - else - errMsg(key, value, "a boolean") - - isPlainObject: (key, value) -> - if not value? or _.isPlainObject(value) - true - else - errMsg(key, value, "a plain object") - - isString: (key, value) -> - if not value? or isString(value) - true - else - errMsg(key, value, "a string") - - isArray: (key, value) -> - if not value? or isArray(value) - true - else - errMsg(key, value, "an array") - - isStringOrFalse: (key, value) -> - if isString(value) or isFalse(value) - true - else - errMsg(key, value, "a string or false") - - isStringOrArrayOfStrings: (key, value) -> - if isString(value) or isArrayOfStrings(value) - true - else - errMsg(key, value, "a string or an array of strings") -} diff --git a/packages/server/lib/util/validation.js b/packages/server/lib/util/validation.js new file mode 100644 index 0000000000..425e12dd63 --- /dev/null +++ b/packages/server/lib/util/validation.js @@ -0,0 +1,120 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const _ = require('lodash') +const errors = require('../errors') + +//# validation functions take a key and a value and should: +//# - return true if it passes validation +//# - return a error message if it fails validation + +const errMsg = (key, value, type) => { + return `Expected '${key}' to be ${type}. Instead the value was: ${JSON.stringify(value)}` +} + +const isFullyQualifiedUrl = (value) => { + return isString(value) && /^https?\:\/\//.test(value) +} + +const isArrayOfStrings = (value) => { + return isArray(value) && _.every(value, isString) +} + +const isFalse = (value) => { + return value === false +} + +const { isArray } = _ +const isNumber = _.isFinite +const { isString } = _ + +module.exports = { + isNumber (key, value) { + if ((value == null) || isNumber(value)) { + return true + } + + return errMsg(key, value, 'a number') + + }, + + isNumberOrFalse (key, value) { + if (isNumber(value) || isFalse(value)) { + return true + } + + return errMsg(key, value, 'a number or false') + + }, + + isFullyQualifiedUrl (key, value) { + if ((value == null) || isFullyQualifiedUrl(value)) { + return true + } + + return errMsg(key, value, 'a fully qualified URL (starting with http:// or https://)') + + }, + + isBoolean (key, value) { + if ((value == null) || _.isBoolean(value)) { + return true + } + + return errMsg(key, value, 'a boolean') + + }, + + isPlainObject (key, value) { + if ((value == null) || _.isPlainObject(value)) { + return true + } + + return errMsg(key, value, 'a plain object') + + }, + + isString (key, value) { + if ((value == null) || isString(value)) { + return true + } + + return errMsg(key, value, 'a string') + + }, + + isArray (key, value) { + if ((value == null) || isArray(value)) { + return true + } + + return errMsg(key, value, 'an array') + + }, + + isStringOrFalse (key, value) { + if (isString(value) || isFalse(value)) { + return true + } + + return errMsg(key, value, 'a string or false') + + }, + + isStringOrArrayOfStrings (key, value) { + if (isString(value) || isArrayOfStrings(value)) { + return true + } + + return errMsg(key, value, 'a string or an array of strings') + + }, +} diff --git a/packages/server/package.json b/packages/server/package.json index b2bdf65495..85bc6d094e 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -21,7 +21,6 @@ "test-e2e-chrome": "node ./test/scripts/run.js test/e2e chrome", "test-cov": "NODE_COVERAGE=true NODE_ENV=test CYPRESS_ENV=test BLUEBIRD_DEBUG=1 xvfb-maybe istanbul cover node_modules/.bin/_mocha -- --opts ./test/support/mocha.opts", "test-cov-process": "NODE_COVERAGE=true NODE_ENV=test CYPRESS_ENV=test BLUEBIRD_DEBUG=1 istanbul cover --include-pid", - "test-debug": "NODE_ENV=test NODE_DEBUG=request CYPRESS_ENV=test BLUEBIRD_DEBUG=1 DEBUG=nock.*,-nock.common,socket.io:* node --inspect --debug-brk ./node_modules/.bin/_mocha --opts test/support/mocha.opts --watch", "codecov": "codecov", "coveralls": "cat ./coverage/lcov.info | coveralls", "lint": "bin-up coffeelint test/*.coffee test/unit/*.coffee test/integration/*.coffee", @@ -38,7 +37,6 @@ "pretest-e2e-chrome": "npm run check-deps-pre", "pretest-cov": "npm run check-deps-pre", "pretest-cov-process": "npm run check-deps-pre", - "pretest-debug": "npm run check-deps-pre", "precodecov": "npm run check-deps-pre", "precoveralls": "npm run check-deps-pre" }, @@ -114,6 +112,7 @@ "clear-module": "^2.1.0", "cli-table2": "^0.2.0", "color-string": "^1.5.2", + "common-tags": "^1.8.0", "compression": "1.7.2", "concat-stream": "^1.5.1", "content-type": "^1.0.2", @@ -188,7 +187,7 @@ "tar-fs": "^1.11.1", "term-size": "^1.2.0", "through": "2.3.6", - "tough-cookie": "2.3.0", + "tough-cookie": "2.4.3", "trash": "4.0.0", "underscore": "^1.8.3", "underscore.string": "3.3.4", diff --git a/packages/server/test/e2e/2_cookies_spec.coffee b/packages/server/test/e2e/2_cookies_spec.coffee index eb0608170d..c2ca114230 100644 --- a/packages/server/test/e2e/2_cookies_spec.coffee +++ b/packages/server/test/e2e/2_cookies_spec.coffee @@ -1,6 +1,7 @@ moment = require("moment") parser = require("cookie-parser") e2e = require("../support/helpers/e2e") +humanInterval = require("human-interval") onServer = (app) -> app.use(parser()) @@ -8,30 +9,40 @@ onServer = (app) -> app.get "/logout", (req, res) -> res.send("logged out") - app.get "/foo", (req, res) -> - console.log "cookies", req.cookies + app.get "/requestCookies", (req, res) -> res.send(req.cookies) app.get "/set", (req, res) -> - res.cookie("shouldExpire", "now") + res.cookie("shouldExpire", "endOfsession") + + res.send("") + + app.get "/setOneHourFromNowAndSecure", (req, res) -> + res.cookie("shouldExpire", "oneHour", { + secure: true + maxAge: humanInterval("1 hour") + }) res.send("") app.get "/expirationRedirect", (req, res) -> res.cookie("shouldExpire", "now", { ## express maxAge is relative to current time - ## in seconds + ## in milliseconds maxAge: 0 }) res.redirect("/logout") app.get "/expirationMaxAge", (req, res) -> - res.cookie("shouldExpire", "now", { - ## express maxAge is relative to current time - ## in seconds - maxAge: 0 - }) + res.header("Set-Cookie", + "shouldExpire=; Max-Age=0; Path=/; Expires=Sun, 24 Jun 1997 20:36:13 GMT" + ) + ## response to set + # auth=p1_2FruNr1entizk9QEGHFYQlWjIK5LULzdDj17lkYhZTz7XA5GOfnVgbbeBDAqnfImkwof2qz0M3yi3AUVusKPqh1BRK6253h0kiBENwdjWDsx3mYQQKpHn6o3XOXX7poSkzrHThnrDlH4w9zoLItwIVNhR2hQrCYhQhtHuw20YM_3D; Domain=.surveymonkey.com;Max-Age=3600; Path=/; expires=Fri, 26-Oct-2018 06:13:48 GMT; secure; HttpOnly' + + ## response to clear + # auth=; Domain=.surveymonkey.com; Max-Age=0; Path=/; expires=Wed, 31-Dec-97 23:59:59 GMT res.send("") @@ -44,9 +55,16 @@ onServer = (app) -> describe "e2e cookies", -> e2e.setup({ - servers: { + servers: [{ + onServer port: 2121 - onServer: onServer + }, { + onServer + port: 2323 + https: true + }] + settings: { + baseUrl: "https://localhost:2323/", } }) diff --git a/packages/server/test/integration/http_requests_spec.coffee b/packages/server/test/integration/http_requests_spec.coffee index 305ed5f604..cb65582c0c 100644 --- a/packages/server/test/integration/http_requests_spec.coffee +++ b/packages/server/test/integration/http_requests_spec.coffee @@ -2199,7 +2199,7 @@ describe "Routes", -> body = removeWhitespace(res.body) expect(body).to.eq contents.replace("localhost", "foobar.com") - it "continues to inject on the same https superdomain but different subdomain", -> + it.only "continues to inject on the same https superdomain but different subdomain", -> contents = removeWhitespace Fixtures.get("server/expected_https_inject.html") @setup("https://www.foobar.com:8443") diff --git a/packages/server/test/scripts/run.js b/packages/server/test/scripts/run.js index c24ec19f5c..5a16920e62 100644 --- a/packages/server/test/scripts/run.js +++ b/packages/server/test/scripts/run.js @@ -8,7 +8,11 @@ const os = require('os') const options = minimist(process.argv.slice(2)) -const run = options._[0] +let run = options._[0] + +if (run.includes('--inspect-brk')) { + run = options._[1] +} function exitErr (msg) { console.error(chalk.red(msg)) @@ -16,8 +20,9 @@ function exitErr (msg) { return process.exit(1) } -const isWindows = () => - os.platform() === 'win32' +const isWindows = () => { + return os.platform() === 'win32' +} if (!run) { return exitErr(` @@ -48,11 +53,22 @@ if (isWindows()) { commandAndArguments.args = [ '--xvfb-run-args ' + '"-as \\"-screen 0 1280x1024x8\\""', - 'mocha', - run, + 'node', ] } +if (options['inspect-brk']) { + commandAndArguments.args.push( + '--inspect', + `--inspect-brk=${options['inspect-brk']}` + ) +} + +commandAndArguments.args.push( + 'node_modules/.bin/_mocha', + run +) + if (options.fgrep) { commandAndArguments.args.push( '--fgrep', @@ -62,7 +78,7 @@ if (options.fgrep) { commandAndArguments.args.push( '--timeout', - '10000', + options['inspect-brk'] ? '40000000' : '10000', '--recursive', '--compilers', 'ts:@packages/ts/register,coffee:@packages/coffee/register', @@ -102,8 +118,10 @@ if (options.browser) { const cmd = `${commandAndArguments.command} ${ commandAndArguments.args.join(' ')}` + console.log('test command:') console.log(cmd) const child = execa.shell(cmd, { env, stdio: 'inherit' }) + child.on('exit', process.exit) diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec.coffee index b156cc5274..3540d8df7c 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec.coffee +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec.coffee @@ -1,31 +1,17 @@ -Cypress.Cookies.defaults({ - whitelist: "foo1" -}) - -context "cookies", -> +describe "cookies", -> beforeEach -> cy.wrap({foo: "bar"}) - it "can get all cookies", -> - cy - .clearCookie("foo1") - .setCookie("foo", "bar").then (c) -> - expect(c.domain).to.eq("localhost") - expect(c.httpOnly).to.eq(false) - expect(c.name).to.eq("foo") - expect(c.value).to.eq("bar") - expect(c.path).to.eq("/") - expect(c.secure).to.eq(false) - expect(c.expiry).to.be.a("number") - - expect(c).to.have.keys( - "domain", "name", "value", "path", "secure", "httpOnly", "expiry" - ) - .getCookies() - .should("have.length", 1) - .then (cookies) -> - c = cookies[0] + context "with whitelist", -> + before -> + Cypress.Cookies.defaults({ + whitelist: "foo1" + }) + it "can get all cookies", -> + cy + .clearCookie("foo1") + .setCookie("foo", "bar").then (c) -> expect(c.domain).to.eq("localhost") expect(c.httpOnly).to.eq(false) expect(c.name).to.eq("foo") @@ -37,68 +23,109 @@ context "cookies", -> expect(c).to.have.keys( "domain", "name", "value", "path", "secure", "httpOnly", "expiry" ) - .clearCookies() - .should("be.null") - .setCookie("wtf", "bob", {httpOnly: true, path: "/foo", secure: true}) - .getCookie("wtf").then (c) -> - expect(c.domain).to.eq("localhost") - expect(c.httpOnly).to.eq(true) - expect(c.name).to.eq("wtf") - expect(c.value).to.eq("bob") - expect(c.path).to.eq("/foo") - expect(c.secure).to.eq(true) - expect(c.expiry).to.be.a("number") + .getCookies() + .should("have.length", 1) + .then (cookies) -> + c = cookies[0] - expect(c).to.have.keys( - "domain", "name", "value", "path", "secure", "httpOnly", "expiry" - ) - .clearCookie("wtf") - .should("be.null") - .getCookie("doesNotExist") - .should("be.null") - .document() - .its("cookie") - .should("be.empty") + expect(c.domain).to.eq("localhost") + expect(c.httpOnly).to.eq(false) + expect(c.name).to.eq("foo") + expect(c.value).to.eq("bar") + expect(c.path).to.eq("/") + expect(c.secure).to.eq(false) + expect(c.expiry).to.be.a("number") - it "resets cookies between tests correctly", -> - Cypress.Cookies.preserveOnce("foo2") + expect(c).to.have.keys( + "domain", "name", "value", "path", "secure", "httpOnly", "expiry" + ) + .clearCookies() + .should("be.null") + .setCookie("wtf", "bob", {httpOnly: true, path: "/foo", secure: true}) + .getCookie("wtf").then (c) -> + expect(c.domain).to.eq("localhost") + expect(c.httpOnly).to.eq(true) + expect(c.name).to.eq("wtf") + expect(c.value).to.eq("bob") + expect(c.path).to.eq("/foo") + expect(c.secure).to.eq(true) + expect(c.expiry).to.be.a("number") - for i in [1..100] - do (i) -> - cy.setCookie("foo" + i, "#{i}") + expect(c).to.have.keys( + "domain", "name", "value", "path", "secure", "httpOnly", "expiry" + ) + .clearCookie("wtf") + .should("be.null") + .getCookie("doesNotExist") + .should("be.null") + .document() + .its("cookie") + .should("be.empty") - cy.getCookies().should("have.length", 100) + it "resets cookies between tests correctly", -> + Cypress.Cookies.preserveOnce("foo2") - it "should be only two left now", -> - cy.getCookies().should("have.length", 2) + for i in [1..100] + do (i) -> + cy.setCookie("foo" + i, "#{i}") - it "sends cookies to localhost:2121", -> - cy - .clearCookies() - .setCookie("asdf", "jkl") - .request("http://localhost:2121/foo") - .its("body").should("deep.eq", {foo1: "1", asdf: "jkl"}) + cy.getCookies().should("have.length", 100) - it "handles expired cookies", -> - cy - .visit("http://localhost:2121/set") - .getCookie("shouldExpire").should("exist") - .visit("http://localhost:2121/expirationMaxAge") - .getCookie("shouldExpire").should("not.exist") - .visit("http://localhost:2121/set") - .getCookie("shouldExpire").should("exist") - .visit("http://localhost:2121/expirationExpires") - .getCookie("shouldExpire").should("not.exist") + it "should be only two left now", -> + cy.getCookies().should("have.length", 2) - it "issue: #224 sets expired cookies between redirects", -> - cy - .visit("http://localhost:2121/set") - .getCookie("shouldExpire").should("exist") - .visit("http://localhost:2121/expirationRedirect") - .url().should("include", "/logout") - .getCookie("shouldExpire").should("not.exist") + context "without whitelist", -> + before -> + Cypress.Cookies.defaults({ + whitelist: [] + }) - .visit("http://localhost:2121/set") - .getCookie("shouldExpire").should("exist") - .request("http://localhost:2121/expirationRedirect") - .getCookie("shouldExpire").should("not.exist") + it "sends cookies to localhost:2121", -> + cy + .clearCookies() + .setCookie("asdf", "jkl") + .request("http://localhost:2121/requestCookies") + .its("body").should("deep.eq", { asdf: "jkl" }) + + it "handles expired cookies secure", -> + cy + .visit("http://localhost:2121/set") + .getCookie("shouldExpire").should("exist") + .visit("http://localhost:2121/expirationMaxAge") + .getCookie("shouldExpire").should("not.exist") + .visit("http://localhost:2121/set") + .getCookie("shouldExpire").should("exist") + .visit("http://localhost:2121/expirationExpires") + .getCookie("shouldExpire").should("not.exist") + + it "issue: #224 sets expired cookies between redirects", -> + cy + .visit("http://localhost:2121/set") + .getCookie("shouldExpire").should("exist") + .visit("http://localhost:2121/expirationRedirect") + .url().should("include", "/logout") + .getCookie("shouldExpire").should("not.exist") + + .visit("http://localhost:2121/set") + .getCookie("shouldExpire").should("exist") + .request("http://localhost:2121/expirationRedirect") + .getCookie("shouldExpire").should("not.exist") + + it "issue: #1321 failing to set or parse cookie", -> + ## this is happening because the original cookie was set + ## with a secure flag, and then expired without the secure + ## flag. + cy + .visit("https://localhost:2323/setOneHourFromNowAndSecure") + .getCookies().should("have.length", 1) + + ## secure cookies should have been attached + .request("https://localhost:2323/requestCookies") + .its("body").should("deep.eq", { shouldExpire: "oneHour" }) + + ## secure cookies should not have been attached + .request("http://localhost:2121/requestCookies") + .its("body").should("deep.eq", {}) + + .visit("https://localhost:2323/expirationMaxAge") + .getCookies().should("be.empty") diff --git a/packages/server/test/support/fixtures/server/absolute_url_expected.html b/packages/server/test/support/fixtures/server/absolute_url_expected.html index 41639cb8a1..2a47de7764 100644 --- a/packages/server/test/support/fixtures/server/absolute_url_expected.html +++ b/packages/server/test/support/fixtures/server/absolute_url_expected.html @@ -5,7 +5,7 @@ var Cypress = window.Cypress = parent.Cypress; - if (!Cypress){ + if (!Cypress) { throw new Error('Something went terribly wrong and we cannot proceed. We expected to find the global Cypress in the parent window but it is missing!. This should never happen and likely is a bug. Please open an issue!'); }; diff --git a/packages/server/test/support/fixtures/server/expected_head_inject.html b/packages/server/test/support/fixtures/server/expected_head_inject.html index f79283c86c..1e2cac8c2d 100644 --- a/packages/server/test/support/fixtures/server/expected_head_inject.html +++ b/packages/server/test/support/fixtures/server/expected_head_inject.html @@ -5,7 +5,7 @@ var Cypress = window.Cypress = parent.Cypress; - if (!Cypress){ + if (!Cypress) { throw new Error('Something went terribly wrong and we cannot proceed. We expected to find the global Cypress in the parent window but it is missing!. This should never happen and likely is a bug. Please open an issue!'); }; diff --git a/packages/server/test/support/fixtures/server/expected_https_inject.html b/packages/server/test/support/fixtures/server/expected_https_inject.html index 6192fff48a..26b5f25aba 100644 --- a/packages/server/test/support/fixtures/server/expected_https_inject.html +++ b/packages/server/test/support/fixtures/server/expected_https_inject.html @@ -3,7 +3,7 @@ document.domain = 'localhost'; var Cypress = window.Cypress = parent.Cypress; - if (!Cypress){ + if (!Cypress) { throw new Error('Something went terribly wrong and we cannot proceed. We expected to find the global Cypress in the parent window but it is missing!. This should never happen and likely is a bug. Please open an issue!'); }; diff --git a/packages/server/test/support/fixtures/server/expected_no_head_tag_inject.html b/packages/server/test/support/fixtures/server/expected_no_head_tag_inject.html index 5f3d772bbd..1c2f09d60f 100644 --- a/packages/server/test/support/fixtures/server/expected_no_head_tag_inject.html +++ b/packages/server/test/support/fixtures/server/expected_no_head_tag_inject.html @@ -5,7 +5,7 @@ var Cypress = window.Cypress = parent.Cypress; - if (!Cypress){ + if (!Cypress) { throw new Error('Something went terribly wrong and we cannot proceed. We expected to find the global Cypress in the parent window but it is missing!. This should never happen and likely is a bug. Please open an issue!'); }; diff --git a/packages/static/package.json b/packages/static/package.json index 05cdd348a2..873be76f34 100644 --- a/packages/static/package.json +++ b/packages/static/package.json @@ -5,6 +5,7 @@ "private": true, "scripts": { "postinstall": "echo '@packages/static needs: npm run build'", + "clean-deps": "rm -rf node_modules", "check-deps": "node ../../scripts/check-deps.js --verbose", "check-deps-pre": "npm run check-deps -- --prescript", "prebuild": "npm run check-deps-pre", diff --git a/packages/ts/package.json b/packages/ts/package.json index 4a24e68ce7..d1b28b4c06 100644 --- a/packages/ts/package.json +++ b/packages/ts/package.json @@ -7,7 +7,8 @@ "register.js" ], "scripts": { - "test": "node test" + "test": "node test", + "clean-deps": "rm -rf node_modules" }, "devDependencies": { "ts-node": "3.3.0", diff --git a/scripts/binary.js b/scripts/binary.js index e57c171c77..361d0a138e 100644 --- a/scripts/binary.js +++ b/scripts/binary.js @@ -2,6 +2,7 @@ require('@packages/coffee/register') const command = process.argv[2] + if (!command) { console.error('Missing deploy command ⛔️') process.exit(1) @@ -9,12 +10,15 @@ if (!command) { const commands = require('./binary/index') const fn = commands[command] + if (!fn) { console.error('Invalid deploy command %s 🚫', command) } fn() -.then(() => console.log('✅ %s completed', command)) +.then(() => { + return console.log('✅ %s completed', command) +}) .catch((err) => { console.error('🔥 deploy error') console.error(err) diff --git a/scripts/check-deps.js b/scripts/check-deps.js index c53c4e15f7..77b89a2bc7 100644 --- a/scripts/check-deps.js +++ b/scripts/check-deps.js @@ -9,18 +9,24 @@ const stripAnsi = require('strip-ansi') const args = require('minimist')(process.argv.slice(2)) const cwd = args.cwd || process.cwd() -const isDirectory = (source) => fs.lstatSync(source).isDirectory() +const isDirectory = (source) => { + return fs.lstatSync(source).isDirectory() +} const getDirectories = (source) => { return fs.readdirSync(source) - .map((name) => path.join(source, name)) + .map((name) => { + return path.join(source, name) + }) .filter(isDirectory) } const isCypressRepo = () => { if (path.basename(cwd) !== 'cypress') return false - const directories = getDirectories(cwd).map((fullPath) => path.basename(fullPath)) + const directories = getDirectories(cwd).map((fullPath) => { + return path.basename(fullPath) + }) return ( directories.includes('packages') && @@ -47,6 +53,7 @@ const logInBox = (boxColor, message) => { if (!args.verbose) return const messageLength = stripAnsi(message).length + console.log(` ${boxColor('-'.repeat(messageLength + 2))}`) console.log(boxColor('|'), message, boxColor('|')) console.log(` ${boxColor('-'.repeat(messageLength + 2))}`) @@ -58,6 +65,7 @@ const epilogue = `Run ${chalk.green('npm install')} to install/update the missin if (!isCypressRepo()) { const result = check.sync() + if (args.prescript && result.error.length) { logInBox(chalk.red, chalk.yellow(preamble)) } @@ -73,7 +81,6 @@ if (!isCypressRepo()) { process.exit(result.status) } - // the following only applies to checking deps for the root + cli + packages const logErrorsForResult = (result) => { @@ -93,10 +100,12 @@ const logResults = (results) => { } const getPackages = () => { - return getDirectories(path.join(cwd, 'packages')).map((dir) => ({ - path: dir, - name: `packages/${path.basename(dir)}`, - })) + return getDirectories(path.join(cwd, 'packages')).map((dir) => { + return { + path: dir, + name: `packages/${path.basename(dir)}`, + } + }) } const getResults = () => { @@ -110,19 +119,25 @@ const getResults = () => { name: package.name, }) }) - .filter(({ error }) => !!error.length) + .filter(({ error }) => { + return !!error.length + }) } const getResultsList = (results) => { if (!results.length) return 'none' - return results.map(({ name }) => path.basename(name)).join(',') + return results.map(({ name }) => { + return path.basename(name) + }).join(',') } const results = getResults() + if (args.prescript && results.length) { logInBox(chalk.red, chalk.yellow(`${preamble} in one or more packages`)) } + logResults(results) if (results.length) { logInBox(chalk.yellow, `${epilogue} in the above package(s)`) @@ -131,7 +146,10 @@ if (results.length) { if (args.list) { process.stdout.write(getResultsList(results)) } else { - const allGood = results.every(({ depsWereOk }) => depsWereOk) + const allGood = results.every(({ depsWereOk }) => { + return depsWereOk + }) + if (allGood && !args.prescript) { logInBox(chalk.green, 'Deps are all good!') } diff --git a/scripts/debug.js b/scripts/debug.js new file mode 100644 index 0000000000..f0a36b790c --- /dev/null +++ b/scripts/debug.js @@ -0,0 +1,34 @@ +const cp = require('child_process') +const chalk = require('chalk') +const pkg = require('../package.json') + +const INSPECT_PORT = 5566 + +const script = process.argv[2] + +if (!script) { + throw new Error('Missing script argument') +} + +const pkgScript = pkg.scripts[script] + +if (!pkgScript) { + throw new Error(`package.json scripts not found for script: ${script}`) +} + +const [cmd, ...args] = pkgScript.split(' ') + +args.unshift(`--inspect-brk=${INSPECT_PORT}`) + +const log = (k, v) => { + // eslint-disable-next-line + console.log(chalk.yellow(k), chalk.cyan(v)) +} + +log('Node:', process.version) +log('Script:', script) +log('Spawning:', pkgScript) + +cp.spawn(cmd, args, { + stdio: 'inherit', +}) diff --git a/scripts/link-packages.js b/scripts/link-packages.js index 97a07abed0..b06fbfcf67 100644 --- a/scripts/link-packages.js +++ b/scripts/link-packages.js @@ -9,7 +9,9 @@ const is = require('check-more-types') const debug = require('debug')('cypress:link') const _ = require('lodash') -const isRelative = (s) => !path.isAbsolute(s) +const isRelative = (s) => { + return !path.isAbsolute(s) +} const fs = Promise.promisifyAll(fse) const glob = Promise.promisify(globber) @@ -18,9 +20,13 @@ const pathToPackages = path.join('node_modules', '@packages') function deleteOutputFolder () { const wildcard = `${pathToPackages}/*` + console.log('deleting all', wildcard) + return glob(wildcard) - .map((filename) => fs.unlinkAsync(filename)) + .map((filename) => { + return fs.unlinkAsync(filename) + }) .catch(_.noop) } @@ -35,6 +41,7 @@ function proxyModule (name, pathToMain, pathToBrowser) { description: `fake proxy module ${name}`, main: pathToMain, } + if (pathToBrowser) { la(isRelative(pathToBrowser), 'path to browser module should be relative', pathToBrowser) @@ -54,28 +61,36 @@ function needsRegister (name) { function makeProxies () { return glob('./packages/*/package.json') - .map((filename) => - fs.readJsonAsync(filename) - .then((json) => ({ filename, json })) + .map((filename) => { + return fs.readJsonAsync(filename) + .then((json) => { + return { filename, json } + }) + } ) .map(({ filename, json }) => { if (!json.main) { throw new Error(`Package ${json.name} is missing main`) } + const dirname = path.dirname(filename) const bareName = json.name.split('/')[1] + debug(json.name, 'bare name', bareName, 'main', json.main) const destinationFolder = path.join(pathToPackages, bareName) const destPackageFilename = path.join(destinationFolder, 'package.json') const registerPath = path.join(destinationFolder, 'register.js') const fullMain = path.resolve(dirname, json.main) + debug('full name', fullMain) const relativePathToMain = path.relative(destinationFolder, fullMain) + debug('relative path to main', relativePathToMain) // for browserify, some packages use "browser" field // need to pass it as well let relativePathToBrowser + if (is.unemptyString(json.browser)) { debug('package has browser field %s', json.browser) relativePathToBrowser = path.relative(destinationFolder, @@ -85,12 +100,14 @@ function makeProxies () { } const proxy = proxyModule(json.name, relativePathToMain, relativePathToBrowser) + console.log(path.dirname(destPackageFilename), '->', relativePathToMain) return fs.outputJsonAsync(destPackageFilename, proxy) .then(() => { if (needsRegister(json.name)) { console.log('adding register file', registerPath) + return fs.outputFileAsync(registerPath, proxyRegister(bareName), 'utf8') } }) diff --git a/scripts/run-all.js b/scripts/run-all.js index 2eb857e88b..2bad2e89cb 100644 --- a/scripts/run-all.js +++ b/scripts/run-all.js @@ -25,16 +25,23 @@ const nonPackageDirs = ['cli/'] const getDirs = () => { const logDirs = (dirs) => { log('found packages\n%s', dirs.join('\n')) + return dirs } + return globAsync('packages/*/') .then(logDirs) - .then((dirs) => dirs.concat(nonPackageDirs)) - .map((dir) => path.join(process.cwd(), dir).replace(/\/$/, '')) + .then((dirs) => { + return dirs.concat(nonPackageDirs) + }) + .map((dir) => { + return path.join(process.cwd(), dir).replace(/\/$/, '') + }) } const packageNameInArray = (dir, packages) => { const packageName = packageNameFromPath(dir) + return _.includes(packages, packageName) } @@ -63,15 +70,19 @@ const filterDirsByCmd = (dirs, cmd) => { default: return dirs.filter((dir) => { const packageJson = require(path.resolve(dir, 'package')) + return !!packageJson.scripts && !!packageJson.scripts[cmd] }) } } const checkDirsLength = (dirs, errMessage) => { - if (dirs.length) { return dirs } + if (dirs.length) { + return dirs + } const err = new Error(errMessage) + err.noPackages = true throw err } @@ -97,6 +108,7 @@ const mapTasks = (cmd, packages) => { return packages.map((dir, index) => { const packageName = packageNameFromPath(dir) + return { command: runCommand, options: { @@ -113,15 +125,22 @@ const mapTasks = (cmd, packages) => { let stderrOutput = '' const collectStderr = through(function (data) { stderrOutput += data.toString() + return this.queue(data) }) collectStderr.pipe(process.stderr) -const noPackagesError = (err) => err.noPackages +const noPackagesError = (err) => { + return err.noPackages +} // only consider printing a list of errors -const resultsError = (err) => Array.isArray(err.results) -const failProcess = () => process.exit(1) +const resultsError = (err) => { + return Array.isArray(err.results) +} +const failProcess = () => { + return process.exit(1) +} const printOtherErrors = (err) => { console.error(err.message) @@ -132,6 +151,7 @@ const printOtherErrors = (err) => { function hasPackageJson (dir) { const packagePath = path.join(dir, 'package.json') + return fs.existsSync(packagePath) } @@ -147,24 +167,39 @@ module.exports = (cmd, options) => { return getDirs() .then(keepDirsWithPackageJson) - .then((dirs) => filterDirsByPackage(dirs, packagesFilter)) - .then((dirs) => rejectDirsByPackage(dirs, packagesReject)) - .then((dirs) => checkDirsLength(dirs, `No packages were found with the filter '${packagesFilter}'`)) - .then((dirs) => filterDirsByCmd(dirs, cmd)) + .then((dirs) => { + return filterDirsByPackage(dirs, packagesFilter) + }) + .then((dirs) => { + return rejectDirsByPackage(dirs, packagesReject) + }) + .then((dirs) => { + return checkDirsLength(dirs, `No packages were found with the filter '${packagesFilter}'`) + }) + .then((dirs) => { + return filterDirsByCmd(dirs, cmd) + }) .then((dirs) => { let errMessage = `No packages were found with the task '${cmd}'` + if (packagesFilter) { errMessage += ` and the filter '${packagesFilter}'` } + return checkDirsLength(dirs, errMessage) }) - .then((dirs) => mapTasks(cmd, dirs)) + .then((dirs) => { + return mapTasks(cmd, dirs) + }) .then((tasks) => { const runSerially = Boolean(options.serial) + if (runSerially) { console.log('⚠️ running jobs serially') } + const parallel = !runSerially + return runAll(tasks, { parallel, printLabel: tasks.length > 1, @@ -179,12 +214,15 @@ module.exports = (cmd, options) => { // http://bluebirdjs.com/docs/api/catch.html#filtered-catch .catch(noPackagesError, (err) => { console.error(chalk.red(`\n${err.message}`)) + return failProcess() }) .catch(resultsError, (err) => { const results = AsciiTable.factory({ heading: ['package', 'exit code'], - rows: err.results.map((result) => [result.name.replace(`:${cmd}`, ''), result.code]), + rows: err.results.map((result) => { + return [result.name.replace(`:${cmd}`, ''), result.code] + }), }).toString() console.error(chalk.red(`\nOne or more tasks failed running 'npm run all ${cmd}'.`)) diff --git a/scripts/run-cypress-tests.js b/scripts/run-cypress-tests.js index 0d08921cdb..d3c05aaa78 100644 --- a/scripts/run-cypress-tests.js +++ b/scripts/run-cypress-tests.js @@ -45,51 +45,64 @@ const prepareArtifactsFolder = () => { if (!isCircle) { return Promise.resolve() } + console.log('Making folder %s', artifactsFolder) + return fs.ensureDirAsync(artifactsFolder) } -const fileExists = (name) => Promise.resolve(existsSync(name)) - -const copyScreenshots = (name) => () => { - la(is.unemptyString(name), 'missing name', name) - - const screenshots = path.join(options.project, 'cypress', 'screenshots') - return fileExists(screenshots) - .then((exists) => { - if (!exists) { - return - } - - console.log('Copying screenshots for %s from %s', name, screenshots) - const destination = path.join(artifactsFolder, name) - - return fs.ensureDirAsync(destination) - .then(() => - fs.copyAsync(screenshots, destination, { - overwrite: true, - }) - ) - }) +const fileExists = (name) => { + return Promise.resolve(existsSync(name)) } -const copyVideos = (name) => () => { - const videos = path.join(options.project, 'cypress', 'videos') - return fileExists(videos) - .then((exists) => { - if (!exists) { - return - } +const copyScreenshots = (name) => { + return () => { + la(is.unemptyString(name), 'missing name', name) - console.log('Copying videos for %s from %s', name, videos) - const destination = path.join(artifactsFolder, name) - return fs.ensureDirAsync(destination) - .then(() => - fs.copyAsync(videos, destination, { - overwrite: true, - }) - ) - }) + const screenshots = path.join(options.project, 'cypress', 'screenshots') + + return fileExists(screenshots) + .then((exists) => { + if (!exists) { + return + } + + console.log('Copying screenshots for %s from %s', name, screenshots) + const destination = path.join(artifactsFolder, name) + + return fs.ensureDirAsync(destination) + .then(() => { + return fs.copyAsync(screenshots, destination, { + overwrite: true, + }) + } + ) + }) + } +} + +const copyVideos = (name) => { + return () => { + const videos = path.join(options.project, 'cypress', 'videos') + + return fileExists(videos) + .then((exists) => { + if (!exists) { + return + } + + console.log('Copying videos for %s from %s', name, videos) + const destination = path.join(artifactsFolder, name) + + return fs.ensureDirAsync(destination) + .then(() => { + return fs.copyAsync(videos, destination, { + overwrite: true, + }) + } + ) + }) + } } /** @@ -97,11 +110,14 @@ const copyVideos = (name) => () => { * * @param {string} name Spec base name */ -const copyArtifacts = (name) => () => { - if (!isCircle) { - return Promise.resolve() +const copyArtifacts = (name) => { + return () => { + if (!isCircle) { + return Promise.resolve() + } + + return copyScreenshots(name)().then(copyVideos(name)) } - return copyScreenshots(name)().then(copyVideos(name)) } function isLoadBalanced (options) { @@ -212,6 +228,7 @@ if (needsXvfb) { return xvfb.start() .then(run) .finally(xvfb.stop) -} else { - return run() } + +return run() + diff --git a/scripts/test-debug-package.js b/scripts/test-debug-package.js new file mode 100644 index 0000000000..d6ba643f4f --- /dev/null +++ b/scripts/test-debug-package.js @@ -0,0 +1,30 @@ +const cp = require('child_process') +const path = require('path') +const chalk = require('chalk') +const finder = require('find-package-json') + +const DEFAULT_SCRIPT = 'npm run test -- --inspect-brk=5566' + +const file = process.argv[2] + +// walks up from the file until it finds the first +// package.json in a parent folder +const { value: pkg, filename } = finder(file).next() + +const script = pkg.scripts['test-debug'] || DEFAULT_SCRIPT + +const [cmd, ...args] = script.split(' ') + +const log = (k, v) => { + // eslint-disable-next-line + console.log(chalk.yellow(k), chalk.cyan(v)) +} + +log('Node version:', process.version) +log('Debug script:', script) +log('Debugging test file:', file) + +cp.spawn(cmd, args.concat(file), { + cwd: path.dirname(filename), + stdio: 'inherit', +}) diff --git a/scripts/test-other-projects.js b/scripts/test-other-projects.js index c2f63a3234..bec9159d3c 100644 --- a/scripts/test-other-projects.js +++ b/scripts/test-other-projects.js @@ -12,6 +12,7 @@ const { getInstallJson } = require('commit-message-install') /* eslint-disable no-console */ const { npm, binary } = getNameAndBinary(process.argv) + la(is.unemptyString(npm), 'missing npm url') la(is.unemptyString(binary), 'missing binary url') const platform = os.platform() @@ -28,13 +29,16 @@ const cliOptions = minimist(process.argv, { }, }) -const shorten = (s) => s.substr(0, 7) +const shorten = (s) => { + return s.substr(0, 7) +} const getShortCommit = () => { const sha = process.env.APPVEYOR_REPO_COMMIT || process.env.CIRCLE_SHA1 || process.env.BUILDKITE_COMMIT + if (sha) { return { sha, @@ -50,6 +54,7 @@ const getShortCommit = () => { const toJsonCodeBlock = (s) => { const start = '```json' const finish = '```' + return `${start}\n${s}\n${finish}\n` } @@ -60,16 +65,19 @@ const toJsonCodeBlock = (s) => { const toMarkdownJsonBlock = (object) => { la(object, 'expected an object to convert to JSON', object) const str = JSON.stringify(object, null, 2) + return toJsonCodeBlock(str) } console.log('starting each test projects') const shortNpmVersion = getJustVersion(npm) + console.log('short NPM version', shortNpmVersion) let subject = `Testing new ${platform} Cypress version ${shortNpmVersion}` const commitInfo = getShortCommit() + if (commitInfo) { subject += ` ${commitInfo.short}` } @@ -89,16 +97,19 @@ const commitMessageInstructions = getInstallJson( const jsonBlock = toMarkdownJsonBlock(commitMessageInstructions) const footer = 'Use tool `commit-message-install` to install from above block' let message = `${subject}\n\n${jsonBlock}\n${footer}\n` + if (process.env.CIRCLE_BUILD_URL) { message += '\n' message += stripIndent` CircleCI job url: ${process.env.CIRCLE_BUILD_URL} ` } + if (process.env.APPVEYOR) { const account = process.env.APPVEYOR_ACCOUNT_NAME const slug = process.env.APPVEYOR_PROJECT_SLUG const build = process.env.APPVEYOR_BUILD_NUMBER + message += '\n' message += stripIndent` AppVeyor: ${account}/${slug} ${build} @@ -113,6 +124,7 @@ const onError = (e) => { console.error(e) process.exit(1) } + bump .runTestProjects(message, cliOptions.provider, shortNpmVersion) .catch(onError) diff --git a/scripts/test-unique-npm-and-binary.js b/scripts/test-unique-npm-and-binary.js index b82bce3270..8ec28ad4a5 100644 --- a/scripts/test-unique-npm-and-binary.js +++ b/scripts/test-unique-npm-and-binary.js @@ -8,12 +8,14 @@ const { getNameAndBinary } = require('./utils') /* eslint-disable no-console */ const { npm, binary } = getNameAndBinary(process.argv) + la(is.unemptyString(npm), 'missing npm url') la(is.unemptyString(binary), 'missing binary url') console.log('testing NPM from', npm) console.log('and binary from', binary) const cwd = options.cwd || process.cwd() + console.log('in', cwd) execa.shell(`npm install ${npm}`, { diff --git a/scripts/utils.js b/scripts/utils.js index 8340c6fa4d..b36e49846c 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -15,6 +15,7 @@ function getNameAndBinary (args = process.argv) { 'missing --binary option', options) let npm = options.npm + if (fs.existsSync(options.npm)) { console.log('loading NPM url from', options.npm) npm = require(path.resolve(options.npm)).url @@ -22,6 +23,7 @@ function getNameAndBinary (args = process.argv) { } let binary = options.binary + if (fs.existsSync(options.binary)) { console.log('loading binary url from', options.binary) binary = require(path.resolve(options.binary)).url @@ -40,11 +42,13 @@ function getJustVersion (npmNameOrUrl) { if (npmNameOrUrl.startsWith('cypress')) { return npmNameOrUrl } + if (is.url(npmNameOrUrl)) { // try finding semver in the url // https://something/0.20.3/something... const re = /\/(\d+\.\d+\.\d+(-\w+)?)\// const matches = re.exec(npmNameOrUrl) + if (matches) { return matches[1] } diff --git a/scripts/win-appveyor-build.js b/scripts/win-appveyor-build.js index 62f7c35d89..6709e2b899 100755 --- a/scripts/win-appveyor-build.js +++ b/scripts/win-appveyor-build.js @@ -14,15 +14,18 @@ shell.set('-e') // any error is fatal // https://www.appveyor.com/docs/environment-variables/ -const isRightBranch = () => - process.env.APPVEYOR_REPO_BRANCH === 'develop' || +const isRightBranch = () => { + return process.env.APPVEYOR_REPO_BRANCH === 'develop' || process.env.APPVEYOR_REPO_BRANCH === 'win-build-shell' +} -const isPullRequest = () => - Boolean(process.env.APPVEYOR_PULL_REQUEST_NUMBER) +const isPullRequest = () => { + return Boolean(process.env.APPVEYOR_PULL_REQUEST_NUMBER) +} -const shouldBuildBinary = () => - isRightBranch() && !isPullRequest() +const shouldBuildBinary = () => { + return isRightBranch() && !isPullRequest() +} if (!shouldBuildBinary()) { console.log('should not build binary') @@ -33,6 +36,7 @@ console.log('building Windows binary') const filename = `cypress-${process.env.NEXT_DEV_VERSION}.tgz` const version = process.env.NEXT_DEV_VERSION + la(is.unemptyString(version), 'missing NEXT_DEV_VERSION') console.log('building version', version)