version: 2.1

defaults: &defaults
  parallelism: 1
  working_directory: ~/cypress
  parameters:
    executor:
      type: executor
      default: cy-doc
  executor: <<parameters.executor>>
  environment:
    ## set specific timezone
    TZ: "/usr/share/zoneinfo/America/New_York"

    ## store artifacts here
    CIRCLE_ARTIFACTS: /tmp/artifacts

    ## set so that e2e tests are consistent
    COLUMNS: 100
    LINES: 24

executors:
  # the Docker image with Cypress dependencies and Chrome browser
  cy-doc:
    docker:
      - image: cypress/browsers:node8.9.3-chrome73
    environment:
      PLATFORM: linux

  # executor to run on Mac OS
  # https://circleci.com/docs/2.0/executor-types/#using-macos
  # https://circleci.com/docs/2.0/testing-ios/#supported-xcode-versions
  mac:
    macos:
      xcode: "10.1.0"
    environment:
      PLATFORM: mac

jobs:
  ## code checkout and NPM installs
  build:
    <<: *defaults
    steps:
      - checkout
      - run:
          name: Print working folder
          command: echo $PWD
      - run:
          name: print global NPM cache path
          command: echo $(npm -g bin)
      - run:
          name: print Node version
          command: node -v
      - run:
          name: print NPM version
          command: npm -v
      - run: npm run check-node-version

      ## make sure the TERM is set to 'xterm' in node (Linux only)
      ## else colors (and tests) will fail
      ## See the following information
      ##   * http://andykdocs.de/development/Docker/Fixing+the+Docker+TERM+variable+issue
      ##   * https://unix.stackexchange.com/questions/43945/whats-the-difference-between-various-term-variables
      - run: npm run check-terminal

      # need to restore a separate cache for each package.json
      - restore_cache:
          key: v8-{{ arch }}-{{ .Branch }}-cli-deps
      - restore_cache:
          key: v5-{{ arch }}-{{ .Branch }}-root-deps
      - restore_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-coffee
      - restore_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-desktop-gui
      - restore_cache:
          key: v6-{{ arch }}-{{ .Branch }}-deps-driver
      - restore_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-example
      - restore_cache:
          key: v7-{{ arch }}-{{ .Branch }}-deps-electron
      - restore_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-extension
      - restore_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-https-proxy
      - restore_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-launcher
      - restore_cache:
          key: v1-{{ arch }}-{{ .Branch }}-deps-network
      - restore_cache:
          key: v8-{{ arch }}-{{ .Branch }}-deps-reporter
      - restore_cache:
          key: v8-{{ arch }}-{{ .Branch }}-deps-runner
      - restore_cache:
          key: v8-{{ arch }}-{{ .Branch }}-deps-server
      - restore_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-socket
      - restore_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-static
      - restore_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-ts

      # show what is already cached globally
      - run: ls $(npm -g bin)
      - run: ls $(npm -g bin)/../lib/node_modules

      # only installs the root dependencies, without going into packages
      # via postinstall script
      - run: npm install --ignore-scripts
      - run:
          name: Top level packages
          command: npm ls --depth=0 || true

      ## symlink all of our sub packages in node_modules
      - run: npm run link

      ## now install all of the sub packages node_modules
      - run: npm run all install -- --serial
      - run: npm run all prune

      # save each node_modules folder per package
      - save_cache:
          key: v8-{{ arch }}-{{ .Branch }}-cli-deps-{{ checksum "cli/package.json" }}
          paths:
            - cli/node_modules
      - save_cache:
          key: v5-{{ arch }}-{{ .Branch }}-root-deps-{{ checksum "package.json" }}
          paths:
            - node_modules
      - save_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-coffee-{{ checksum "packages/coffee/package.json" }}
          paths:
            - packages/coffee/node_modules
      - save_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-desktop-gui-{{ checksum "packages/desktop-gui/package.json" }}
          paths:
            - packages/desktop-gui/node_modules
      - save_cache:
          key: v6-{{ arch }}-{{ .Branch }}-deps-driver-{{ checksum "packages/driver/package.json" }}
          paths:
            - packages/driver/node_modules
      - save_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-example-{{ checksum "packages/example/package.json" }}
          paths:
            - packages/example/node_modules
      - save_cache:
          key: v7-{{ arch }}-{{ .Branch }}-deps-electron-{{ checksum "packages/electron/package.json" }}
          paths:
            - packages/electron/node_modules
            - ~/.cache/electron
            - ~/.electron
      - save_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-extension-{{ checksum "packages/extension/package.json" }}
          paths:
            - packages/extension/node_modules
      - save_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-https-proxy-{{ checksum "packages/https-proxy/package.json" }}
          paths:
            - packages/https-proxy/node_modules
      - save_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-launcher-{{ checksum "packages/launcher/package.json" }}
          paths:
            - packages/launcher/node_modules
      - save_cache:
          key: v1-{{ arch }}-{{ .Branch }}-deps-network-{{ checksum "packages/network/package.json" }}
          paths:
            - packages/network/node_modules
      - save_cache:
          key: v8-{{ arch }}-{{ .Branch }}-deps-reporter-{{ checksum "packages/reporter/package.json" }}
          paths:
            - packages/reporter/node_modules
      - save_cache:
          key: v8-{{ arch }}-{{ .Branch }}-deps-runner-{{ checksum "packages/runner/package.json" }}
          paths:
            - packages/runner/node_modules
      - save_cache:
          key: v8-{{ arch }}-{{ .Branch }}-deps-server-{{ checksum "packages/server/package.json" }}
          paths:
            - packages/server/node_modules
      - save_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-socket-{{ checksum "packages/socket/package.json" }}
          paths:
            - packages/socket/node_modules
      - save_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-static-{{ checksum "packages/static/package.json" }}
          paths:
            - packages/static/node_modules
      - save_cache:
          key: v5-{{ arch }}-{{ .Branch }}-deps-ts-{{ checksum "packages/ts/package.json" }}
          paths:
            - packages/ts/node_modules

      - run: npm run stop-only
      ## now go build all of subpackages
      - run: npm run prebuild -- --serial
      - run: npm run build -- --serial

      ## save entire folder as artifact for other jobs to run without reinstalling
      - persist_to_workspace:
          root: ~/
          paths:
            - cypress

  lint:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run: npm run lint
      - run: npm run all lint

  unit-tests:
    <<: *defaults
    parallelism: 1
    steps:
      - attach_workspace:
          at: ~/
      # make sure mocha runs
      - run: npm run test-mocha
      # test binary build code
      - run: npm run test-scripts
      # make sure our snapshots are compared correctly
      - run: npm run test-mocha-snapshot
      # make sure packages with TypeScript can be transpiled to JS
      - run: npm run all build-js
      # run unit tests from individual packages
      - run: npm run all test -- --package cli
      - run: npm run all test -- --package electron
      - run: npm run all test -- --package extension
      - run: npm run all test -- --package https-proxy
      - run: npm run all test -- --package launcher
      - run: npm run all test -- --package network
      # how to pass Mocha reporter through zunder?
      - run: npm run all test -- --package reporter
      - run: npm run all test -- --package runner
      - run: npm run all test -- --package socket
      - run: npm run all test -- --package static
      - store_test_results:
          path: /tmp/cypress

  lint-typescript:
    <<: *defaults
    parallelism: 1
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: ls -la types
          working_directory: cli
      - run:
          command: ls -la chai
          working_directory: cli/types
      - run:
          command: npm run dtslint
          working_directory: cli

  "server-unit-tests":
    <<: *defaults
    parallelism: 2
    steps:
      - attach_workspace:
          at: ~/
      - run: npm run all test-unit -- --package server
      - store_test_results:
          path: /tmp/cypress

  "server-integration-tests":
    <<: *defaults
    parallelism: 2
    steps:
      - attach_workspace:
          at: ~/
      - run: npm run all test-integration -- --package server
      - store_test_results:
          path: /tmp/cypress

  "server-performance-tests":
      <<: *defaults
      steps:
      - attach_workspace:
          at: ~/
      - run:
          command: |
            set +e # don't exit on non-0 status code - performance tests can be spurious
            npm run all test-performance -- --package server
            echo "exited with code: $?"
            exit 0
      - store_test_results:
          path: /tmp/cypress
      - store_artifacts:
          path: /tmp/artifacts

  "server-e2e-tests-1":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: npm run test-e2e -- --chunk 1
          working_directory: packages/server
      - store_test_results:
          path: /tmp/cypress

  "server-e2e-tests-2":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: npm run test-e2e -- --chunk 2
          working_directory: packages/server
      - store_test_results:
          path: /tmp/cypress

  "server-e2e-tests-3":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: npm run test-e2e -- --chunk 3
          working_directory: packages/server
      - store_test_results:
          path: /tmp/cypress

  "server-e2e-tests-4":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: npm run test-e2e -- --chunk 4
          working_directory: packages/server
      - store_test_results:
          path: /tmp/cypress

  "server-e2e-tests-5":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: npm run test-e2e -- --chunk 5
          working_directory: packages/server
      - store_test_results:
          path: /tmp/cypress

  "server-e2e-tests-6":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: npm run test-e2e -- --chunk 6
          working_directory: packages/server
      - store_test_results:
          path: /tmp/cypress

  "server-e2e-tests-7":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: npm run test-e2e -- --chunk 7
          working_directory: packages/server
      - store_test_results:
          path: /tmp/cypress

  "server-e2e-tests-8":
      <<: *defaults
      steps:
      - attach_workspace:
          at: ~/
      - run:
          command: npm run test-e2e -- --chunk 8
          working_directory: packages/server
      - store_test_results:
          path: /tmp/cypress

  "driver-integration-tests-3x":
    <<: *defaults
    parallelism: 3
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: npm start
          background: true
          working_directory: packages/driver
      - run:
          command: $(npm bin)/wait-on http://localhost:3500
          working_directory: packages/driver
      - run:
          command: |
            CYPRESS_KONFIG_ENV=production \
            CYPRESS_RECORD_KEY=$PACKAGES_RECORD_KEY \
            npm run cypress:run -- --record --parallel --group 3x-driver-chrome --browser chrome
          working_directory: packages/driver
      - store_test_results:
          path: /tmp/cypress
      - store_artifacts:
          path: /tmp/artifacts

  "desktop-gui-integration-tests-2x":
    <<: *defaults
    parallelism: 2
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: npm run build-prod
          working_directory: packages/desktop-gui
      - run:
          command: |
            CYPRESS_KONFIG_ENV=production \
            CYPRESS_RECORD_KEY=$PACKAGES_RECORD_KEY \
            npm run cypress:run -- --record --parallel --group 2x-desktop-gui
          working_directory: packages/desktop-gui
      - store_test_results:
          path: /tmp/cypress
      - store_artifacts:
          path: /tmp/artifacts

  "reporter-integration-tests":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: npm run build-prod
          working_directory: packages/reporter
      - run:
          command: |
            CYPRESS_KONFIG_ENV=production \
            CYPRESS_RECORD_KEY=$PACKAGES_RECORD_KEY \
            npm run cypress:run -- --record --parallel --group reporter
          working_directory: packages/reporter
      - store_test_results:
          path: /tmp/cypress
      - store_artifacts:
          path: /tmp/artifacts

  "run-launcher":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          command: node index.js
          working_directory: packages/launcher

  build-binary:
    <<: *defaults
    shell: /bin/bash --login
    steps:
      - run:
          name: Check environment variables before code sign (if on Mac)
          # NOTE
          # our Mac code sign works via electron-builder
          # by default, electron-builder will NOT sign app built in a pull request
          # even our internal one (!)
          # Usually this is not a problem, since we only build and test binary
          # built on "develop" and "master" branches
          # but if you need to really build and sign a Mac binary in a PR
          # set variable CSC_FOR_PULL_REQUEST=true
          command: |
            set -e
            if [[ "$OSTYPE" == "darwin"* ]]; then
              if [ -z "$CSC_LINK" ]; then
                echo "Need to provide environment variable CSC_LINK"
                  echo "with base64 encoded certificate .p12 file"
                exit 1
              fi
              if [ -z "$CSC_KEY_PASSWORD" ]; then
                echo "Need to provide environment variable CSC_KEY_PASSWORD"
                  echo "with password for unlocking certificate .p12 file"
                exit 1
              fi
            else
              echo "Not Mac platform, skipping code sign setup"
            fi

      - attach_workspace:
          at: ~/
      - run: $(npm bin)/print-arch
      - run:
          environment:
            DEBUG: electron-builder,electron-osx-sign*
          command: npm run binary-build -- --platform $PLATFORM --version $NEXT_DEV_VERSION
      - run: npm run binary-zip -- --platform $PLATFORM
      - run: ls -l *.zip
      - run:
          name: upload unique binary
          command: |
            node scripts/binary.js upload-unique-binary \
              --file cypress.zip \
              --version $NEXT_DEV_VERSION
      - run: cat binary-url.json
      - run: mkdir /tmp/urls
      - run: cp binary-url.json /tmp/urls
      - run: cp cypress.zip /tmp/urls
      - run: ls /tmp/urls
      - persist_to_workspace:
          root: /tmp/urls
          paths:
            - binary-url.json
            - cypress.zip

  "test-kitchensink-against-staging":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          name: Cloning test project
          command: git clone https://github.com/cypress-io/cypress-example-kitchensink.git /tmp/repo
      - run:
          name: Install prod dependencies
          command: npm install --production
          working_directory: /tmp/repo
      - run:
          name: Example server
          command: npm start
          working_directory: /tmp/repo
          background: true
      - run:
          name: Run Kitchensink example project
          command: |
            CYPRESS_PROJECT_ID=$TEST_KITCHENSINK_PROJECT_ID \
            CYPRESS_RECORD_KEY=$TEST_KITCHENSINK_RECORD_KEY \
            CYPRESS_ENV=staging \
            CYPRESS_video=false \
            npm run cypress:run -- --project /tmp/repo --record

  "test-against-staging":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          name: Cloning test project
          command: git clone https://github.com/cypress-io/cypress-test-tiny.git /tmp/repo
      - run:
          name: Run test project
          command: |
            CYPRESS_PROJECT_ID=$TEST_TINY_PROJECT_ID \
            CYPRESS_RECORD_KEY=$TEST_TINY_RECORD_KEY \
            CYPRESS_ENV=staging \
            npm run cypress:run -- --project /tmp/repo --record

  "build-npm-package":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - run:
          name: bump NPM version
          command: npm --no-git-tag-version version $NEXT_DEV_VERSION
      - run:
          name: build NPM package
          working_directory: cli
          command: npm run build
      - run:
          command: ls -la types
          working_directory: cli/build
      - run:
          name: list NPM package contents
          working_directory: cli/build
          command: npm run size
      - run:
          name: pack NPM package
          working_directory: cli/build
          command: npm pack
      - run:
          name: list created NPM package
          working_directory: cli/build
          command: ls -l
      # created file should have filename cypress-<version>.tgz
      - run:
          name: upload NPM package
          command: |
            node scripts/binary.js upload-npm-package \
              --file cli/build/cypress-$NEXT_DEV_VERSION.tgz \
              --version $NEXT_DEV_VERSION
      - run: cat npm-package-url.json
      - run: mkdir /tmp/urls
      - run: cp cli/build/cypress-$NEXT_DEV_VERSION.tgz /tmp/urls/cypress.tgz
      - run: cp npm-package-url.json /tmp/urls
      - run: ls /tmp/urls
      - persist_to_workspace:
          root: /tmp/urls
          paths:
            - npm-package-url.json
            - cypress.tgz

  "test-binary-and-npm-against-other-projects":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - attach_workspace:
          at: /tmp/urls
      - run: ls -la /tmp/urls
      - run: cat /tmp/urls/*.json
      - run: mkdir /tmp/testing
      - run:
          name: create dummy package
          working_directory: /tmp/testing
          command: npm init -y
      - run:
          # install NPM from unique urls
          name: Install Cypress
          command: |
            node scripts/test-unique-npm-and-binary.js \
              --npm /tmp/urls/npm-package-url.json \
              --binary /tmp/urls/binary-url.json \
              --cwd /tmp/testing
      - run:
          name: Verify Cypress binary
          working_directory: /tmp/testing
          command: $(npm bin)/cypress verify
      - run:
          name: Running other test projects with new NPM package and binary
          command: |
            node scripts/test-other-projects.js \
              --npm /tmp/urls/npm-package-url.json \
              --binary /tmp/urls/binary-url.json \
              --provider circle

  "test-npm-module-and-verify-binary":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - attach_workspace:
          at: /tmp/urls
      # make sure we have cypress.zip received
      - run: ls -l /tmp/urls/cypress.zip
      # build NPM package
      - run:
          command: npm run build
          working_directory: cli
      - run: mkdir test-binary
      - run:
          name: Create new NPM package
          working_directory: test-binary
          command: npm init -y
      - run:
          # install NPM from built NPM package folder
          name: Install Cypress
          working_directory: test-binary
          # force installing the freshly built binary
          command: CYPRESS_INSTALL_BINARY=/tmp/urls/cypress.zip npm i /tmp/urls/cypress.tgz
      - run:
          name: Verify Cypress binary
          working_directory: test-binary
          command: $(npm bin)/cypress verify

  # install NPM + binary zip and run against staging API
  "test-binary-against-staging":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - attach_workspace:
          at: /tmp/urls
      # make sure we have the binary
      - run: ls -l /tmp/urls/cypress.zip
      # make sure we have the NPM package
      - run: ls -l /tmp/urls/cypress.tgz
      - run:
          name: Cloning test project
          command: git clone https://github.com/cypress-io/cypress-test-tiny.git /tmp/cypress-test-tiny
      - run:
          name: Install Cypress
          working_directory: /tmp/cypress-test-tiny
          # force installing the freshly built binary
          command: CYPRESS_INSTALL_BINARY=/tmp/urls/cypress.zip npm i /tmp/urls/cypress.tgz
      - run:
          name: Run test project
          working_directory: /tmp/cypress-test-tiny
          command: |
            CYPRESS_PROJECT_ID=$TEST_TINY_PROJECT_ID \
            CYPRESS_RECORD_KEY=$TEST_TINY_RECORD_KEY \
            CYPRESS_ENV=staging \
            $(npm bin)/cypress run --record

  "test-binary-against-kitchensink":
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/
      - attach_workspace:
          at: /tmp/urls
      - run:
          name: Cloning kitchensink project
          command: git clone --depth 1 https://github.com/cypress-io/cypress-example-kitchensink.git /tmp/kitchensink
      - run:
          command: npm install
          working_directory: /tmp/kitchensink
      - run:
          name: Install Cypress
          working_directory: /tmp/kitchensink
          # force installing the freshly built binary
          command: CYPRESS_INSTALL_BINARY=/tmp/urls/cypress.zip npm i /tmp/urls/cypress.tgz
      - run:
          working_directory: /tmp/kitchensink
          command: npm run build
      - run:
          working_directory: /tmp/kitchensink
          command: npm start
          background: true
      - run:
          working_directory: /tmp/kitchensink
          command: npm run e2e

linux-workflow: &linux-workflow
  jobs:
    - build
    - lint:
        name: Linux lint
        requires:
          - build
    - lint-typescript:
        requires:
          - build
    # unit, integration and e2e tests
    - unit-tests:
        requires:
          - build
    - server-unit-tests:
        requires:
          - build
    - server-integration-tests:
        requires:
          - build
    - server-performance-tests:
        requires:
          - build
    - server-e2e-tests-1:
        requires:
          - build
    - server-e2e-tests-2:
        requires:
          - build
    - server-e2e-tests-3:
        requires:
          - build
    - server-e2e-tests-4:
        requires:
          - build
    - server-e2e-tests-5:
        requires:
          - build
    - server-e2e-tests-6:
        requires:
          - build
    - server-e2e-tests-7:
        requires:
          - build
    - server-e2e-tests-8:
        requires:
          - build
    - driver-integration-tests-3x:
        requires:
          - build
    - desktop-gui-integration-tests-2x:
        requires:
          - build
    - reporter-integration-tests:
        requires:
          - build
    - run-launcher:
        requires:
          - build
    # various testing scenarios, like building full binary
    # and testing it on a real project
    - test-against-staging:
        filters:
          branches:
            only:
              - develop
        requires:
          - build
    - test-kitchensink-against-staging:
        filters:
          branches:
            only:
              - develop
        requires:
          - build
    - build-npm-package:
        filters:
          branches:
            only:
              - develop
              - binary-metadata
        requires:
          - build
    - build-binary:
        filters:
          branches:
            only:
              - develop
              - binary-metadata
        requires:
          - build
    - test-binary-and-npm-against-other-projects:
        filters:
          branches:
            only:
              - develop
              - binary-metadata
        requires:
          - build-npm-package
          - build-binary
    - test-npm-module-and-verify-binary:
        filters:
          branches:
            only:
              - develop
        requires:
          - build-npm-package
          - build-binary
    - test-binary-against-staging:
        filters:
          branches:
            only:
              - develop
        requires:
          - build-npm-package
          - build-binary
    - test-binary-against-kitchensink:
        filters:
          branches:
            only:
              - develop
        requires:
          - build-npm-package
          - build-binary

mac-workflow: &mac-workflow
  jobs:
    - build:
        name: Mac build
        executor: mac
    - lint:
        name: Mac lint
        executor: mac
        requires:
          - Mac build

    # maybe run unit tests?

    - build-npm-package:
        name: Mac NPM package
        executor: mac
        filters:
          branches:
            only:
              - develop
        requires:
          - Mac build

    - build-binary:
        name: Mac binary
        executor: mac
        context: org-global
        filters:
          branches:
            only:
              - develop
              - binary-metadata
        requires:
          - Mac build

    - test-binary-against-kitchensink:
        name: Test Mac binary against kitchensink
        executor: mac
        filters:
          branches:
            only:
              - develop
        requires:
          - Mac NPM package
          - Mac binary

    - test-binary-against-staging:
        name: Test Mac binary against staging
        executor: mac
        filters:
          branches:
            only:
              - develop
        requires:
          - Mac NPM package
          - Mac binary
workflows:
  linux:
    <<: *linux-workflow
  mac:
    <<: *mac-workflow
