Compare commits

...

19 Commits
v0.10 ... v0.11

Author SHA1 Message Date
0deb3596c5 Change config key inherit to inherits 2019-10-31 01:14:51 -04:00
b87ba1fcd0 Add config driven presets for insert, update and upsert 2019-10-30 03:27:11 -04:00
8ae7210e70 Add config driven presets for insert, update and upserta 2019-10-30 02:30:31 -04:00
f8aac8d4d7 Allow config files to inherit from other config files 2019-10-28 01:48:04 -04:00
34867a2733 Add RBAC option to disable functions eg. count 2019-10-27 01:52:48 -04:00
4a8af69dd0 Add fuzz testing to 'serv' for the GQL hash parser 2019-10-26 15:43:40 -04:00
c74226208d Add fuzz testing to 'jsn' and 'qcode' 2019-10-26 03:02:05 -04:00
6d2f334011 Add ability to block queries and mutations by role 2019-10-26 01:34:29 -04:00
ff13f651d6 Merge pull request #11 from dosco/rbac
Role based access control and other fixes
2019-10-25 01:49:37 -04:00
cabd2d81ae Preserve allow.list ordering on save 2019-10-25 01:39:59 -04:00
4edc15eb98 Optimize prepared statement flow for RBAC 2019-10-25 00:01:22 -04:00
6bc66d28bc Get RBAC working for queries and mutations 2019-10-24 02:07:42 -04:00
c797deb4d0 Add built in 'anon' and 'user' roles 2019-10-15 02:30:19 -04:00
deb5b93c81 Add role based access control 2019-10-14 02:51:36 -04:00
85a74ed30c Update filters section in guide 2019-10-10 01:35:35 -04:00
9299855d8a Add ability to set filters per operation / action 2019-10-06 16:28:10 -04:00
450d77ccbd Merge pull request #10 from FourSigma/sm-examples-folder
Move rails-app to examples folder
2019-10-05 22:48:13 -04:00
a1fa1f3e9e Add upsert mutation 2019-10-05 02:17:08 -04:00
e68bdffa25 rails-app moved to examples folder 2019-10-05 00:49:39 -04:00
181 changed files with 3356 additions and 1677 deletions

4
.gitignore vendored
View File

@ -28,3 +28,7 @@ main
.DS_Store .DS_Store
.swp .swp
main main
super-graph
*-fuzz.zip
crashers
suppressions

13
.wtc.yaml Normal file
View File

@ -0,0 +1,13 @@
no_trace: false
debounce: 300 # if rule has no debounce, this will be used instead
ignore: \.git/
trig: [start, run] # will run on start
rules:
- name: start
- name: run
match: \.go$
ignore: web|examples|docs|_test\.go$
command: go run main.go serv
- name: test
match: _test\.go$
command: go test -cover {PKG}

View File

@ -11,9 +11,8 @@ RUN apk update && \
apk add --no-cache git && \ apk add --no-cache git && \
apk add --no-cache upx=3.95-r2 apk add --no-cache upx=3.95-r2
RUN go get -u github.com/shanzi/wu && \ RUN go get -u github.com/rafaelsq/wtc && \
go install github.com/shanzi/wu && \ go get -u github.com/GeertJohan/go.rice/rice
go get github.com/GeertJohan/go.rice/rice
WORKDIR /app WORKDIR /app
COPY . /app COPY . /app

View File

@ -46,6 +46,9 @@ This compiler is what sits at the heart of Super Graph with layers of useful fun
## Contact me ## Contact me
I'm happy to help you deploy Super Graph so feel free to reach out over
Twitter or Discord.
[twitter/dosco](https://twitter.com/dosco) [twitter/dosco](https://twitter.com/dosco)
[chat/super-graph](https://discord.gg/6pSWCTZ) [chat/super-graph](https://discord.gg/6pSWCTZ)

View File

@ -1,62 +1,5 @@
# http://localhost:8080/ # http://localhost:8080/
variables {
"update": {
"name": "Wu-Tang",
"description": "No description needed"
},
"product_id": 1
}
mutation {
products(id: $product_id, update: $update) {
id
name
description
}
}
variables {
"data": {
"email": "gfk@myspace.com",
"full_name": "Ghostface Killah",
"created_at": "now",
"updated_at": "now"
}
}
mutation {
user(insert: $data) {
id
}
}
variables {
"data": {
"product_id": 5
}
}
mutation {
products(id: $product_id, delete: true) {
id
name
}
}
query {
users {
id
email
picture: avatar
products(limit: 2, where: {price: {gt: 10}}) {
id
name
description
}
}
}
variables { variables {
"data": [ "data": [
{ {
@ -79,6 +22,184 @@ mutation {
} }
} }
variables {
"update": {
"name": "Wu-Tang",
"description": "No description needed"
},
"product_id": 1
}
mutation {
products(id: $product_id, update: $update) {
id
name
description
}
}
query {
users {
id
email
picture: avatar
products(limit: 2, where: {price: {gt: 10}}) {
id
name
description
}
}
}
variables {
"data": [
{
"name": "Gumbo1",
"created_at": "now",
"updated_at": "now"
},
{
"name": "Gumbo2",
"created_at": "now",
"updated_at": "now"
}
]
}
mutation {
products(id: 199, delete: true) {
id
name
}
}
variables {
"data": [
{
"name": "Gumbo1",
"created_at": "now",
"updated_at": "now"
},
{
"name": "Gumbo2",
"created_at": "now",
"updated_at": "now"
}
]
}
query {
products {
id
name
}
}
variables {
"data": [
{
"name": "Gumbo1",
"created_at": "now",
"updated_at": "now"
},
{
"name": "Gumbo2",
"created_at": "now",
"updated_at": "now"
}
]
}
query {
products {
id
name
user {
email
}
}
}
variables {
"data": {
"product_id": 5
}
}
mutation {
products(id: $product_id, delete: true) {
id
name
}
}
variables {
"data": [
{
"name": "Gumbo1",
"created_at": "now",
"updated_at": "now"
},
{
"name": "Gumbo2",
"created_at": "now",
"updated_at": "now"
}
]
}
query {
products {
id
name
price
users {
email
}
}
}
variables {
"data": {
"email": "gfk@myspace.com",
"full_name": "Ghostface Killah",
"created_at": "now",
"updated_at": "now"
}
}
mutation {
user(insert: $data) {
id
}
}
variables {
"data": [
{
"name": "Gumbo1",
"created_at": "now",
"updated_at": "now"
},
{
"name": "Gumbo2",
"created_at": "now",
"updated_at": "now"
}
]
}
query {
products {
id
name
users {
email
}
}
}
query { query {
me { me {
id id
@ -102,3 +223,53 @@ mutation {
description description
} }
} }
variables {
"data": [
{
"name": "Gumbo1",
"created_at": "now",
"updated_at": "now"
},
{
"name": "Gumbo2",
"created_at": "now",
"updated_at": "now"
}
]
}
query {
product {
id
name
}
}
variables {
"data": [
{
"name": "Gumbo1",
"created_at": "now",
"updated_at": "now"
},
{
"name": "Gumbo2",
"created_at": "now",
"updated_at": "now"
}
]
}
query {
products {
id
name
description
users {
email
}
}
}

View File

@ -22,7 +22,7 @@ enable_tracing: true
# Watch the config folder and reload Super Graph # Watch the config folder and reload Super Graph
# with the new configs when a change is detected # with the new configs when a change is detected
reload_on_config_change: false reload_on_config_change: true
# File that points to the database seeding script # File that points to the database seeding script
# seed_file: seed.js # seed_file: seed.js
@ -53,7 +53,7 @@ auth:
# Comment this out if you want to disable setting # Comment this out if you want to disable setting
# the user_id via a header. Good for testing # the user_id via a header. Good for testing
header: X-User-ID creds_in_header: true
rails: rails:
# Rails version this is used for reading the # Rails version this is used for reading the
@ -100,7 +100,7 @@ database:
# Define defaults to for the field key and values below # Define defaults to for the field key and values below
defaults: defaults:
# filter: ["{ user_id: { eq: $user_id } }"] # filters: ["{ user_id: { eq: $user_id } }"]
# Field and table names that you wish to block # Field and table names that you wish to block
blocklist: blocklist:
@ -111,42 +111,81 @@ database:
- encrypted - encrypted
- token - token
tables: tables:
- name: users - name: customers
# This filter will overwrite defaults.filter remotes:
# filter: ["{ id: { eq: $user_id } }"] - name: payments
id: stripe_id
url: http://rails_app:3000/stripe/$id
path: data
# debug: true
pass_headers:
- cookie
set_headers:
- name: Host
value: 0.0.0.0
# - name: Authorization
# value: Bearer <stripe_api_key>
# - name: products - # You can create new fields that have a
# # Multiple filters are AND'd together # real db table backing them
# filter: [ name: me
# "{ price: { gt: 0 } }", table: users
# "{ price: { lt: 8 } }"
# ]
- name: customers roles_query: "SELECT * FROM users as usr WHERE id = $user_id"
# No filter is used for this field not
# even defaults.filter
filter: none
remotes: roles:
- name: payments - name: anon
id: stripe_id tables:
url: http://rails_app:3000/stripe/$id - name: products
path: data limit: 10
# debug: true
pass_headers:
- cookie
set_headers:
- name: Host
value: 0.0.0.0
# - name: Authorization
# value: Bearer <stripe_api_key>
- # You can create new fields that have a query:
# real db table backing them columns: ["id", "name", "description" ]
name: me aggregation: false
table: users
filter: ["{ id: { eq: $user_id } }"]
# - name: posts insert:
# filter: ["{ account_id: { _eq: $account_id } }"] block: false
update:
block: false
delete:
block: false
- name: user
tables:
- name: users
query:
filters: ["{ id: { _eq: $user_id } }"]
- name: products
query:
limit: 50
filters: ["{ user_id: { eq: $user_id } }"]
columns: ["id", "name", "description" ]
disable_functions: false
insert:
filters: ["{ user_id: { eq: $user_id } }"]
columns: ["id", "name", "description" ]
presets:
- created_at: "now"
update:
filters: ["{ user_id: { eq: $user_id } }"]
columns:
- id
- name
presets:
- updated_at: "now"
delete:
block: true
- name: admin
match: id = 1
tables:
- name: users
# query:
# filters: ["{ account_id: { _eq: $account_id } }"]

View File

@ -1,3 +1,7 @@
# Inherit config from this other config file
# so I only need to overwrite some values
inherits: dev
app_name: "Super Graph Production" app_name: "Super Graph Production"
host_port: 0.0.0.0:8080 host_port: 0.0.0.0:8080
web_ui: false web_ui: false
@ -47,10 +51,6 @@ auth:
type: rails type: rails
cookie: _app_session cookie: _app_session
# Comment this out if you want to disable setting
# the user_id via a header. Good for testing
header: X-User-ID
rails: rails:
# Rails version this is used for reading the # Rails version this is used for reading the
# various cookies formats. # various cookies formats.
@ -80,65 +80,9 @@ database:
type: postgres type: postgres
host: db host: db
port: 5432 port: 5432
dbname: {{app_name_slug}}_development dbname: app_production
user: postgres user: postgres
password: '' password: ''
#pool_size: 10 #pool_size: 10
#max_retries: 0 #max_retries: 0
#log_level: "debug" #log_level: "debug"
# Define variables here that you want to use in filters
# sub-queries must be wrapped in ()
variables:
account_id: "(select account_id from users where id = $user_id)"
# Define defaults to for the field key and values below
defaults:
filter: ["{ user_id: { eq: $user_id } }"]
# Field and table names that you wish to block
blocklist:
- ar_internal_metadata
- schema_migrations
- secret
- password
- encrypted
- token
tables:
- name: users
# This filter will overwrite defaults.filter
filter: ["{ id: { eq: $user_id } }"]
- name: products
# Multiple filters are AND'd together
filter: [
"{ price: { gt: 0 } }",
"{ price: { lt: 8 } }"
]
- name: customers
# No filter is used for this field not
# even defaults.filter
filter: none
# remotes:
# - name: payments
# id: stripe_id
# url: http://rails_app:3000/stripe/$id
# path: data
# # pass_headers:
# # - cookie
# # - host
# set_headers:
# - name: Authorization
# value: Bearer <stripe_api_key>
- # You can create new fields that have a
# real db table backing them
name: me
table: users
filter: ["{ id: { eq: $user_id } }"]
# - name: posts
# filter: ["{ account_id: { _eq: $account_id } }"]

10
demo
View File

@ -1,13 +1,13 @@
#!/bin/sh #!/bin/bash
if [ "$1" == "start" ]; then if [ "$1" == "start" ]; then
echo "Downloading pre-built docker images" echo "Downloading pre-built docker images"
docker-compose -f rails-app/demo.yml pull docker-compose -f examples/rails-app/demo.yml pull
echo "Setting up and deploying Super Graph and the demo Rails app" echo "Setting up and deploying Super Graph and the demo Rails app"
docker-compose -f rails-app/demo.yml run rails_app rake db:create db:migrate db:seed docker-compose -f examples/rails-app/demo.yml run rails_app rake db:create db:migrate db:seed
docker-compose -f rails-app/demo.yml up docker-compose -f examples/rails-app/demo.yml up
elif [ "$1" == "stop" ]; then elif [ "$1" == "stop" ]; then
docker-compose -f rails-app/demo.yml down docker-compose -f examples/rails-app/demo.yml down
else else
echo "./demo [start|stop]" echo "./demo [start|stop]"
fi fi

View File

@ -12,10 +12,10 @@ services:
# - "6379:6379" # - "6379:6379"
rails_app: rails_app:
build: rails-app/. build: examples/rails-app/.
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes: volumes:
- ./rails-app:/app - ./examples/rails-app:/app
- /app/tmp - /app/tmp
ports: ports:
- "3000:3000" - "3000:3000"
@ -34,7 +34,7 @@ services:
volumes: volumes:
- .:/app - .:/app
working_dir: /app working_dir: /app
command: wu -pattern="*.go" go run main.go serv command: wtc
depends_on: depends_on:
- db - db
- rails_app - rails_app

View File

@ -588,7 +588,7 @@ query {
## Mutations ## Mutations
In GraphQL mutations is the operation type for when you need to modify data. Super Graph supports the `insert`, `update` and `delete` database operations. Here are some examples. In GraphQL mutations is the operation type for when you need to modify data. Super Graph supports the `insert`, `update`, `upsert` and `delete` database operations. Here are some examples.
When using mutations the data must be passed as variables since Super Graphs compiles the query into an prepared statement in the database for maximum speed. Prepared statements are are functions in your code when called they accept arguments and your variables are passed in as those arguments. When using mutations the data must be passed as variables since Super Graphs compiles the query into an prepared statement in the database for maximum speed. Prepared statements are are functions in your code when called they accept arguments and your variables are passed in as those arguments.
@ -721,6 +721,56 @@ mutation {
} }
``` ```
### Upsert
```json
{
"data": {
"id": 5,
"name": "Art of Computer Programming",
"description": "The Art of Computer Programming (TAOCP) is a comprehensive monograph written by computer scientist Donald Knuth",
"price": 30.5
}
}
```
```graphql
mutation {
product(upsert: $data) {
id
name
}
}
```
### Bulk upsert
```json
{
"data": [{
"id": 5,
"name": "Art of Computer Programming",
"description": "The Art of Computer Programming (TAOCP) is a comprehensive monograph written by computer scientist Donald Knuth",
"price": 30.5
},
{
"id": 6,
"name": "Compilers: Principles, Techniques, and Tools",
"description": "Known to professors, students, and developers worldwide as the 'Dragon Book' is available in a new edition",
"price": 93.74
}]
}
```
```graphql
mutation {
product(upsert: $data) {
id
name
}
}
```
### Using variables ### Using variables
Variables (`$product_id`) and their values (`"product_id": 5`) can be passed along side the GraphQL query. Using variables makes for better client side code as well as improved server side SQL query caching. The build-in web-ui also supports setting variables. Not having to manipulate your GraphQL query string to insert values into it makes for cleaner Variables (`$product_id`) and their values (`"product_id": 5`) can be passed along side the GraphQL query. Using variables makes for better client side code as well as improved server side SQL query caching. The build-in web-ui also supports setting variables. Not having to manipulate your GraphQL query string to insert values into it makes for cleaner
@ -990,29 +1040,42 @@ Configuration files can either be in YAML or JSON their names are derived from t
We're tried to ensure that the config file is self documenting and easy to work with. We're tried to ensure that the config file is self documenting and easy to work with.
```yaml ```yaml
# Inherit config from this other config file
# so I only need to overwrite some values
inherits: base
app_name: "Super Graph Development" app_name: "Super Graph Development"
host_port: 0.0.0.0:8080 host_port: 0.0.0.0:8080
web_ui: true web_ui: true
debug_level: 1
# debug, info, warn, error, fatal, panic, disable # debug, info, warn, error, fatal, panic
log_level: "info" log_level: "debug"
# Disable this in development to get a list of # Disable this in development to get a list of
# queries used. When enabled super graph # queries used. When enabled super graph
# will only allow queries from this list # will only allow queries from this list
# List saved to ./config/allow.list # List saved to ./config/allow.list
use_allow_list: true use_allow_list: false
# Throw a 401 on auth failure for queries that need auth # Throw a 401 on auth failure for queries that need auth
# valid values: always, per_query, never # valid values: always, per_query, never
auth_fail_block: always auth_fail_block: never
# Latency tracing for database queries and remote joins # Latency tracing for database queries and remote joins
# the resulting latency information is returned with the # the resulting latency information is returned with the
# response # response
enable_tracing: true enable_tracing: true
# Watch the config folder and reload Super Graph
# with the new configs when a change is detected
reload_on_config_change: true
# File that points to the database seeding script
# seed_file: seed.js
# Path pointing to where the migrations can be found
migrations_path: ./config/migrations
# Postgres related environment Variables # Postgres related environment Variables
# SG_DATABASE_HOST # SG_DATABASE_HOST
# SG_DATABASE_PORT # SG_DATABASE_PORT
@ -1036,7 +1099,7 @@ auth:
# Comment this out if you want to disable setting # Comment this out if you want to disable setting
# the user_id via a header. Good for testing # the user_id via a header. Good for testing
header: X-User-ID creds_in_header: true
rails: rails:
# Rails version this is used for reading the # Rails version this is used for reading the
@ -1047,10 +1110,10 @@ auth:
secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566 secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566
# Remote cookie store. (memcache or redis) # Remote cookie store. (memcache or redis)
# url: redis://127.0.0.1:6379 # url: redis://redis:6379
# password: test # password: ""
# max_idle: 80, # max_idle: 80
# max_active: 12000, # max_active: 12000
# In most cases you don't need these # In most cases you don't need these
# salt: "encrypted cookie" # salt: "encrypted cookie"
@ -1070,20 +1133,23 @@ database:
dbname: app_development dbname: app_development
user: postgres user: postgres
password: '' password: ''
# pool_size: 10
# max_retries: 0 #schema: "public"
# log_level: "debug" #pool_size: 10
#max_retries: 0
#log_level: "debug"
# Define variables here that you want to use in filters # Define variables here that you want to use in filters
# sub-queries must be wrapped in ()
variables: variables:
account_id: "select account_id from users where id = $user_id" account_id: "(select account_id from users where id = $user_id)"
# Define defaults to for the field key and values below # Define defaults to for the field key and values below
defaults: defaults:
filter: ["{ user_id: { eq: $user_id } }"] # filters: ["{ user_id: { eq: $user_id } }"]
# Field and table names that you wish to block # Field and table names that you wish to block
blacklist: blocklist:
- ar_internal_metadata - ar_internal_metadata
- schema_migrations - schema_migrations
- secret - secret
@ -1091,43 +1157,85 @@ database:
- encrypted - encrypted
- token - token
tables: tables:
- name: users - name: customers
# This filter will overwrite defaults.filter remotes:
filter: ["{ id: { eq: $user_id } }"] - name: payments
id: stripe_id
url: http://rails_app:3000/stripe/$id
path: data
# debug: true
pass_headers:
- cookie
set_headers:
- name: Host
value: 0.0.0.0
# - name: Authorization
# value: Bearer <stripe_api_key>
- name: products - # You can create new fields that have a
# Multiple filters are AND'd together # real db table backing them
filter: [ name: me
"{ price: { gt: 0 } }", table: users
"{ price: { lt: 8 } }"
]
- name: customers roles_query: "SELECT * FROM users as usr WHERE id = $user_id"
# No filter is used for this field not
# even defaults.filter
filter: none
remotes: roles:
- name: payments - name: anon
id: stripe_id tables:
url: http://rails_app:3000/stripe/$id - name: products
path: data limit: 10
# pass_headers:
# - cookie
# - host
set_headers:
- name: Authorization
value: Bearer <stripe_api_key>
- # You can create new fields that have a query:
# real db table backing them columns: ["id", "name", "description" ]
name: me aggregation: false
table: users
filter: ["{ id: { eq: $user_id } }"] insert:
allow: false
update:
allow: false
delete:
allow: false
- name: user
tables:
- name: users
query:
filters: ["{ id: { _eq: $user_id } }"]
- name: products
query:
limit: 50
filters: ["{ user_id: { eq: $user_id } }"]
columns: ["id", "name", "description" ]
disable_functions: false
insert:
filters: ["{ user_id: { eq: $user_id } }"]
columns: ["id", "name", "description" ]
set:
- created_at: "now"
update:
filters: ["{ user_id: { eq: $user_id } }"]
columns:
- id
- name
set:
- updated_at: "now"
delete:
deny: true
- name: admin
match: id = 1
tables:
- name: users
# query:
# filters: ["{ account_id: { _eq: $account_id } }"]
# - name: posts
# filter: ["{ account_id: { _eq: $account_id } }"]
``` ```
If deploying into environments like Kubernetes it's useful to be able to configure things like secrets and hosts though environment variables therfore we expose the below environment variables. This is escpecially useful for secrets since they are usually injected in via a secrets management framework ie. Kubernetes Secrets If deploying into environments like Kubernetes it's useful to be able to configure things like secrets and hosts though environment variables therfore we expose the below environment variables. This is escpecially useful for secrets since they are usually injected in via a secrets management framework ie. Kubernetes Secrets

View File

@ -1,10 +1,10 @@
GIT GIT
remote: https://github.com/stympy/faker.git remote: https://github.com/stympy/faker.git
revision: e7c898c05306409cedf5b795a622a326ec87e279 revision: 4e9144825fcc9ba5c83cc0fd037779ab82f3120b
branch: master branch: master
specs: specs:
faker (2.5.0) faker (2.6.0)
i18n (~> 1.6.0) i18n (>= 1.6, < 1.8)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
@ -82,8 +82,7 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (~> 1.5) regexp_parser (~> 1.5)
xpath (~> 3.2) xpath (~> 3.2)
childprocess (2.0.0) childprocess (3.0.0)
rake (< 13.0)
chromedriver-helper (2.1.1) chromedriver-helper (2.1.1)
archive-zip (~> 0.10) archive-zip (~> 0.10)
nokogiri (~> 1.8) nokogiri (~> 1.8)
@ -107,7 +106,7 @@ GEM
ffi (1.11.1) ffi (1.11.1)
globalid (0.4.2) globalid (0.4.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
i18n (1.6.0) i18n (1.7.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
io-like (0.3.0) io-like (0.3.0)
jbuilder (2.9.1) jbuilder (2.9.1)
@ -157,15 +156,15 @@ GEM
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.2.0) rails-html-sanitizer (1.3.0)
loofah (~> 2.2, >= 2.2.2) loofah (~> 2.3)
railties (6.0.0) railties (6.0.0)
actionpack (= 6.0.0) actionpack (= 6.0.0)
activesupport (= 6.0.0) activesupport (= 6.0.0)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0) thor (>= 0.20.3, < 2.0)
rake (12.3.3) rake (13.0.0)
rb-fsevent (0.10.3) rb-fsevent (0.10.3)
rb-inotify (0.10.0) rb-inotify (0.10.0)
ffi (~> 1.0) ffi (~> 1.0)
@ -203,8 +202,8 @@ GEM
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0) sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3) tilt (>= 1.1, < 3)
selenium-webdriver (3.142.5) selenium-webdriver (3.142.6)
childprocess (>= 0.5, < 3.0) childprocess (>= 0.5, < 4.0)
rubyzip (>= 1.2.2) rubyzip (>= 1.2.2)
spring (2.1.0) spring (2.1.0)
spring-watcher-listen (2.0.1) spring-watcher-listen (2.0.1)
@ -239,7 +238,7 @@ GEM
websocket-extensions (0.1.4) websocket-extensions (0.1.4)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.1.10) zeitwerk (2.2.0)
PLATFORMS PLATFORMS
ruby ruby

Some files were not shown because too many files have changed in this diff Show More