You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
631 lines
20 KiB
631 lines
20 KiB
version: 2
|
|
jobs:
|
|
build:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/elixir:1.9.4-node-browsers
|
|
environment:
|
|
MIX_ENV: test
|
|
# match POSTGRES_PASSWORD for postgres image below
|
|
PGPASSWORD: postgres
|
|
# match POSTGRES_USER for postgres image below
|
|
PGUSER: postgres
|
|
|
|
working_directory: ~/app
|
|
|
|
steps:
|
|
- run: sudo apt-get update; sudo apt-get -y install autoconf build-essential libgmp3-dev libtool
|
|
|
|
- checkout
|
|
- run:
|
|
command: ./bin/install_chrome_headless.sh
|
|
no_output_timeout: 2400
|
|
|
|
- run: mix local.hex --force
|
|
- run: mix local.rebar --force
|
|
|
|
- run:
|
|
name: "ELIXIR_VERSION.lock"
|
|
command: echo "${ELIXIR_VERSION}" > ELIXIR_VERSION.lock
|
|
- run:
|
|
name: "OTP_VERSION.lock"
|
|
command: echo "${OTP_VERSION}" > OTP_VERSION.lock
|
|
|
|
- restore_cache:
|
|
keys:
|
|
- v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
|
|
- v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
|
|
- v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
|
|
|
|
- run: mix deps.get
|
|
|
|
- restore_cache:
|
|
keys:
|
|
- v7-npm-install-{{ .Branch }}-{{ checksum "apps/block_scout_web/assets/package-lock.json" }}
|
|
- v7-npm-install-{{ .Branch }}
|
|
- v7-npm-install
|
|
|
|
- run:
|
|
command: npm install
|
|
working_directory: "apps/explorer"
|
|
|
|
- save_cache:
|
|
key: v3-npm-install-{{ .Branch }}-{{ checksum "apps/explorer/package-lock.json" }}
|
|
paths: "apps/explorer/node_modules"
|
|
- save_cache:
|
|
key: v3-npm-install-{{ .Branch }}
|
|
paths: "apps/explorer/node_modules"
|
|
- save_cache:
|
|
key: v3-npm-install
|
|
paths: "apps/explorer/node_modules"
|
|
|
|
- run:
|
|
command: npm install
|
|
working_directory: "apps/block_scout_web/assets"
|
|
|
|
- run:
|
|
command: npm rebuild node-sass
|
|
working_directory: "apps/block_scout_web/assets"
|
|
|
|
- save_cache:
|
|
key: v7-npm-install-{{ .Branch }}-{{ checksum "apps/block_scout_web/assets/package-lock.json" }}
|
|
paths: "apps/block_scout_web/assets/node_modules"
|
|
- save_cache:
|
|
key: v7-npm-install-{{ .Branch }}
|
|
paths: "apps/block_scout_web/assets/node_modules"
|
|
- save_cache:
|
|
key: v7-npm-install
|
|
paths: "apps/block_scout_web/assets/node_modules"
|
|
|
|
- run: mix compile
|
|
|
|
# Ensure NIF is compiled for libsecp256k1
|
|
- run:
|
|
command: make
|
|
working_directory: "deps/libsecp256k1"
|
|
|
|
# `deps` needs to be cached with `_build` because `_build` will symlink into `deps`
|
|
|
|
- save_cache:
|
|
key: v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
|
|
paths:
|
|
- deps
|
|
- _build
|
|
- save_cache:
|
|
key: v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
|
|
paths:
|
|
- deps
|
|
- _build
|
|
- save_cache:
|
|
key: v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
|
|
paths:
|
|
- deps
|
|
- _build
|
|
|
|
- run:
|
|
name: Build assets
|
|
command: node node_modules/webpack/bin/webpack.js --mode development
|
|
working_directory: "apps/block_scout_web/assets"
|
|
|
|
- persist_to_workspace:
|
|
root: .
|
|
paths:
|
|
- .circleci
|
|
- .credo.exs
|
|
- .dialyzer-ignore
|
|
- .formatter.exs
|
|
- .git
|
|
- .gitignore
|
|
- ELIXIR_VERSION.lock
|
|
- Gemfile
|
|
- Gemfile.lock
|
|
- OTP_VERSION.lock
|
|
- _build
|
|
- apps
|
|
- bin
|
|
- config
|
|
- deps
|
|
- doc
|
|
- mix.exs
|
|
- mix.lock
|
|
- appspec.yml
|
|
- rel
|
|
check_formatted:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/elixir:1.9.4
|
|
environment:
|
|
MIX_ENV: test
|
|
|
|
working_directory: ~/app
|
|
|
|
steps:
|
|
- attach_workspace:
|
|
at: .
|
|
|
|
- run: mix format --check-formatted
|
|
credo:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/elixir:1.9.4
|
|
environment:
|
|
MIX_ENV: test
|
|
|
|
working_directory: ~/app
|
|
|
|
steps:
|
|
- attach_workspace:
|
|
at: .
|
|
|
|
- run: mix local.hex --force
|
|
|
|
- run: mix credo
|
|
deploy_aws:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/python:2.7-stretch
|
|
|
|
working_directory: ~/app
|
|
|
|
steps:
|
|
- attach_workspace:
|
|
at: .
|
|
|
|
- add_ssh_keys:
|
|
fingerprints:
|
|
- "c4:fd:a8:f8:48:a8:09:e5:3e:be:30:62:4d:6f:6f:36"
|
|
|
|
- run:
|
|
name: Deploy to AWS
|
|
command: bin/deploy
|
|
dialyzer:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/elixir:1.9.4
|
|
environment:
|
|
MIX_ENV: test
|
|
|
|
working_directory: ~/app
|
|
|
|
steps:
|
|
- attach_workspace:
|
|
at: .
|
|
|
|
- run: mix local.hex --force
|
|
|
|
- restore_cache:
|
|
keys:
|
|
- v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
|
|
- v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
|
|
- v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
|
|
|
|
- run:
|
|
name: Unpack PLT cache
|
|
command: |
|
|
mkdir -p _build/test
|
|
cp plts/dialyxir*.plt _build/test/ || true
|
|
mkdir -p ~/.mix
|
|
cp plts/dialyxir*.plt ~/.mix/ || true
|
|
|
|
- run: mix dialyzer --plt
|
|
|
|
- run:
|
|
name: Pack PLT cache
|
|
command: |
|
|
mkdir -p plts
|
|
cp _build/test/dialyxir*.plt plts/
|
|
cp ~/.mix/dialyxir*.plt plts/
|
|
|
|
- save_cache:
|
|
key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
|
|
paths:
|
|
- plts
|
|
- save_cache:
|
|
key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
|
|
paths:
|
|
- plts
|
|
- save_cache:
|
|
key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
|
|
paths:
|
|
- plts
|
|
|
|
- run: mix dialyzer --halt-exit-status
|
|
eslint:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/node:12.15.0-browsers-legacy
|
|
|
|
working_directory: ~/app
|
|
|
|
steps:
|
|
- attach_workspace:
|
|
at: .
|
|
|
|
- run:
|
|
name: ESLint
|
|
command: ./node_modules/.bin/eslint --format=junit --output-file="test/eslint/junit.xml" js/**
|
|
working_directory: apps/block_scout_web/assets
|
|
|
|
- store_test_results:
|
|
path: apps/block_scout_web/assets/test
|
|
gettext:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/elixir:1.9.4
|
|
environment:
|
|
MIX_ENV: test
|
|
|
|
working_directory: ~/app
|
|
|
|
steps:
|
|
- attach_workspace:
|
|
at: .
|
|
|
|
- run: mix local.hex --force
|
|
|
|
- run:
|
|
name: Check for missed translations
|
|
command: |
|
|
mix gettext.extract --merge | tee stdout.txt
|
|
! grep "Wrote " stdout.txt
|
|
working_directory: "apps/block_scout_web"
|
|
|
|
- store_artifacts:
|
|
path: apps/block_scout_web/priv/gettext
|
|
jest:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/node:12.15.0-browsers-legacy
|
|
|
|
working_directory: ~/app
|
|
|
|
steps:
|
|
- attach_workspace:
|
|
at: .
|
|
|
|
- run:
|
|
name: Jest
|
|
command: ./node_modules/.bin/jest
|
|
working_directory: apps/block_scout_web/assets
|
|
release:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/elixir:1.9.4
|
|
environment:
|
|
MIX_ENV: prod
|
|
|
|
working_directory: ~/app
|
|
|
|
steps:
|
|
- attach_workspace:
|
|
at: .
|
|
|
|
- run: mix local.hex --force
|
|
- run: mix local.rebar --force
|
|
- run: MIX_ENV=prod mix release
|
|
- run:
|
|
name: Collecting artifacts
|
|
command: |
|
|
find -name 'blockscout.tar.gz' -exec sh -c 'mkdir -p ci_artifact && cp "$@" ci_artifact/ci_artifact_blockscout.tar.gz' _ {} +
|
|
when: always
|
|
|
|
- store_artifacts:
|
|
name: Uploading CI artifacts
|
|
path: ci_artifact/ci_artifact_blockscout.tar.gz
|
|
destination: ci_artifact_blockscout.tar.gz
|
|
sobelow:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/elixir:1.9.4
|
|
environment:
|
|
MIX_ENV: test
|
|
|
|
working_directory: ~/app
|
|
|
|
steps:
|
|
- attach_workspace:
|
|
at: .
|
|
|
|
- run: mix local.hex --force
|
|
|
|
- run:
|
|
name: Scan explorer for vulnerabilities
|
|
command: mix sobelow --config
|
|
working_directory: "apps/explorer"
|
|
|
|
- run:
|
|
name: Scan block_scout_web for vulnerabilities
|
|
command: mix sobelow --config
|
|
working_directory: "apps/block_scout_web"
|
|
# test_geth_http_websocket:
|
|
# docker:
|
|
# # Ensure .tool-versions matches
|
|
# - image: circleci/elixir:1.9.4-node-browsers
|
|
# environment:
|
|
# MIX_ENV: test
|
|
# # match POSTGRES_PASSWORD for postgres image below
|
|
# PGPASSWORD: postgres
|
|
# # match POSTGRES_USER for postgres image below
|
|
# PGUSER: postgres
|
|
# ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Geth.HTTPWebSocket"
|
|
# ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Geth"
|
|
# - image: circleci/postgres:10.10-alpine
|
|
# environment:
|
|
# # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database
|
|
# POSTGRES_DB: explorer_test
|
|
# # match PGPASSWORD for elixir image above
|
|
# POSTGRES_PASSWORD: postgres
|
|
# # match PGUSER for elixir image above
|
|
# POSTGRES_USER: postgres
|
|
|
|
# working_directory: ~/app
|
|
|
|
# steps:
|
|
# - attach_workspace:
|
|
# at: .
|
|
|
|
# - run:
|
|
# command: ./bin/install_chrome_headless.sh
|
|
# no_output_timeout: 2400
|
|
|
|
# - run: mix local.hex --force
|
|
# - run: mix local.rebar --force
|
|
|
|
# - run:
|
|
# name: Wait for DB
|
|
# command: dockerize -wait tcp://localhost:5432 -timeout 1m
|
|
|
|
# - run:
|
|
# name: mix test --exclude no_geth
|
|
# command: |
|
|
# # Don't submit coverage report for forks, but let the build succeed
|
|
# if [[ -z "$COVERALLS_REPO_TOKEN" ]]; then
|
|
# mix coveralls.html --exclude no_geth --parallel --umbrella
|
|
# else
|
|
# mix coveralls.circle --exclude no_geth --parallel --umbrella ||
|
|
# # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
|
|
# (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval)
|
|
# fi
|
|
|
|
# - store_artifacts:
|
|
# path: cover/excoveralls.html
|
|
# - store_test_results:
|
|
# path: _build/test/junit
|
|
# test_geth_mox:
|
|
# docker:
|
|
# # Ensure .tool-versions matches
|
|
# - image: circleci/elixir:1.9.4-node-browsers
|
|
# environment:
|
|
# MIX_ENV: test
|
|
# # match POSTGRES_PASSWORD for postgres image below
|
|
# PGPASSWORD: postgres
|
|
# # match POSTGRES_USER for postgres image below
|
|
# PGUSER: postgres
|
|
# ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Geth.Mox"
|
|
# ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox"
|
|
# - image: circleci/postgres:10.10-alpine
|
|
# environment:
|
|
# # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database
|
|
# POSTGRES_DB: explorer_test
|
|
# # match PGPASSWORD for elixir image above
|
|
# POSTGRES_PASSWORD: postgres
|
|
# # match PGUSER for elixir image above
|
|
# POSTGRES_USER: postgres
|
|
|
|
# working_directory: ~/app
|
|
|
|
# steps:
|
|
# - attach_workspace:
|
|
# at: .
|
|
|
|
# - run:
|
|
# command: ./bin/install_chrome_headless.sh
|
|
# no_output_timeout: 2400
|
|
|
|
# - run: mix local.hex --force
|
|
# - run: mix local.rebar --force
|
|
|
|
# - run:
|
|
# name: Wait for DB
|
|
# command: dockerize -wait tcp://localhost:5432 -timeout 1m
|
|
|
|
# - run:
|
|
# name: mix test --exclude no_geth
|
|
# command: |
|
|
# # Don't submit coverage report for forks, but let the build succeed
|
|
# if [[ -z "$COVERALLS_REPO_TOKEN" ]]; then
|
|
# mix coveralls.html --exclude no_geth --parallel --umbrella
|
|
# else
|
|
# mix coveralls.circle --exclude no_geth --parallel --umbrella ||
|
|
# # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
|
|
# (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval)
|
|
# fi
|
|
|
|
# - store_artifacts:
|
|
# path: cover/excoveralls.html
|
|
# - store_test_results:
|
|
# path: _build/test/junit
|
|
# test_parity_http_websocket:
|
|
# docker:
|
|
# # Ensure .tool-versions matches
|
|
# - image: circleci/elixir:1.9.4-node-browsers
|
|
# environment:
|
|
# MIX_ENV: test
|
|
# # match POSTGRES_PASSWORD for postgres image below
|
|
# PGPASSWORD: postgres
|
|
# # match POSTGRES_USER for postgres image below
|
|
# PGUSER: postgres
|
|
# ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.HTTPWebSocket"
|
|
# ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Parity"
|
|
# - image: circleci/postgres:10.10-alpine
|
|
# environment:
|
|
# # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database
|
|
# POSTGRES_DB: explorer_test
|
|
# # match PGPASSWORD for elixir image above
|
|
# POSTGRES_PASSWORD: postgres
|
|
# # match PGUSER for elixir image above
|
|
# POSTGRES_USER: postgres
|
|
|
|
# working_directory: ~/app
|
|
|
|
# steps:
|
|
# - attach_workspace:
|
|
# at: .
|
|
|
|
# - run:
|
|
# command: ./bin/install_chrome_headless.sh
|
|
# no_output_timeout: 2400
|
|
|
|
# - run: mix local.hex --force
|
|
# - run: mix local.rebar --force
|
|
|
|
# - run:
|
|
# name: Wait for DB
|
|
# command: dockerize -wait tcp://localhost:5432 -timeout 1m
|
|
|
|
# - run:
|
|
# name: mix test --exclude no_parity
|
|
# command: |
|
|
# # Don't submit coverage report for forks, but let the build succeed
|
|
# if [[ -z "$COVERALLS_REPO_TOKEN" ]]; then
|
|
# mix coveralls.html --exclude no_parity --parallel --umbrella
|
|
# else
|
|
# mix coveralls.circle --exclude no_parity --parallel --umbrella ||
|
|
# # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
|
|
# (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval)
|
|
# fi
|
|
|
|
# - store_artifacts:
|
|
# path: cover/excoveralls.html
|
|
# - store_test_results:
|
|
# path: _build/test/junit
|
|
test_parity_mox:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/elixir:1.9.4-node-browsers
|
|
environment:
|
|
MIX_ENV: test
|
|
# match POSTGRES_PASSWORD for postgres image below
|
|
PGPASSWORD: postgres
|
|
# match POSTGRES_USER for postgres image below
|
|
PGUSER: postgres
|
|
ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.Mox"
|
|
ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox"
|
|
- image: circleci/postgres:10.10-alpine
|
|
environment:
|
|
# Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database
|
|
POSTGRES_DB: explorer_test
|
|
# match PGPASSWORD for elixir image above
|
|
POSTGRES_PASSWORD: postgres
|
|
# match PGUSER for elixir image above
|
|
POSTGRES_USER: postgres
|
|
|
|
working_directory: ~/app
|
|
|
|
steps:
|
|
- attach_workspace:
|
|
at: .
|
|
|
|
- run:
|
|
command: ./bin/install_chrome_headless.sh
|
|
no_output_timeout: 2400
|
|
|
|
- run: mix local.hex --force
|
|
- run: mix local.rebar --force
|
|
|
|
- run:
|
|
name: Wait for DB
|
|
command: dockerize -wait tcp://localhost:5432 -timeout 1m
|
|
|
|
- run:
|
|
name: mix test --exclude no_parity
|
|
command: |
|
|
# Don't submit coverage report for forks, but let the build succeed
|
|
if [[ -z "$COVERALLS_REPO_TOKEN" ]]; then
|
|
mix coveralls.html --exclude no_parity --parallel --umbrella
|
|
else
|
|
mix coveralls.circle --exclude no_parity --parallel --umbrella ||
|
|
# if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
|
|
(retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval)
|
|
fi
|
|
|
|
- store_artifacts:
|
|
path: cover/excoveralls.html
|
|
- store_test_results:
|
|
path: _build/test/junit
|
|
coveralls_merge:
|
|
docker:
|
|
# Ensure .tool-versions matches
|
|
- image: circleci/elixir:1.9.4
|
|
environment:
|
|
MIX_ENV: test
|
|
|
|
steps:
|
|
- run:
|
|
name: Tell coveralls.io build is done
|
|
command: curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done"
|
|
workflows:
|
|
version: 2
|
|
primary:
|
|
jobs:
|
|
- build
|
|
- check_formatted:
|
|
requires:
|
|
- build
|
|
# This unfortunately will only fire if all the tests pass because of how `requires` works
|
|
- coveralls_merge:
|
|
requires:
|
|
# - test_parity_http_websocket
|
|
- test_parity_mox
|
|
# - test_geth_http_websocket
|
|
# - test_geth_mox
|
|
- credo:
|
|
requires:
|
|
- build
|
|
- deploy_aws:
|
|
filters:
|
|
branches:
|
|
only:
|
|
- production
|
|
- staging
|
|
- /deploy-[A-Za-z0-9]+$/
|
|
requires:
|
|
- check_formatted
|
|
- credo
|
|
- eslint
|
|
- jest
|
|
- sobelow
|
|
# - test_parity_http_websocket
|
|
- test_parity_mox
|
|
# - test_geth_http_websocket
|
|
# - test_geth_mox
|
|
- dialyzer:
|
|
requires:
|
|
- build
|
|
- eslint:
|
|
requires:
|
|
- build
|
|
- gettext:
|
|
requires:
|
|
- build
|
|
- jest:
|
|
requires:
|
|
- build
|
|
- release:
|
|
requires:
|
|
- build
|
|
- sobelow:
|
|
requires:
|
|
- build
|
|
# - test_parity_http_websocket:
|
|
# requires:
|
|
# - build
|
|
- test_parity_mox:
|
|
requires:
|
|
- build
|
|
# - test_geth_http_websocket:
|
|
# requires:
|
|
# - build
|
|
# - test_geth_mox:
|
|
# requires:
|
|
# - build
|
|
|