expo-flubber 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f9ca5325df420e5023e9d2a4d0a4b39551a65521c46c29b3af02d71b3c014122
4
+ data.tar.gz: 292c84899d1be9d0aeab512e97fd571a2bfde750e6e077ac7105a5a586e7c8fb
5
+ SHA512:
6
+ metadata.gz: d63da0917757d2ff3f16448059b0fee2eddbbae66b6df84ed1c8fbe4c993897c1e160ce7cf3c1990dc19b3ce19d346141226deae12f9ac737d30eb84c8cc1718
7
+ data.tar.gz: 45b4c088837d516346dcb24daaf72d779913ae1bb91e17335ca32f57ab79b1a8b503e91463f99a579d6e303132631b1654197bf15b55bc180f38bcbebbf946bc
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/cli"
4
+
5
+ Flubber::CLI.start(ARGV)
@@ -0,0 +1,23 @@
1
+ module Flubber
2
+ module ApiToken
3
+ API_TOKEN_LOCATION = "#{ENV["HOME"]}/.flubber"
4
+
5
+ def self.set(api_token)
6
+ File.open(API_TOKEN_LOCATION, "w") do |file|
7
+ file.puts "#{api_token}"
8
+ end
9
+ end
10
+
11
+ def self.get
12
+ return unless api_token_exists?
13
+ file = File.open(API_TOKEN_LOCATION)
14
+ api_token = file.readlines[0].chomp
15
+ file.close
16
+ api_token
17
+ end
18
+
19
+ def self.api_token_exists?
20
+ File.exists?(API_TOKEN_LOCATION)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module Flubber
2
+ API_URL = "https://flubber-api.herokuapp.com"
3
+ end
@@ -0,0 +1,44 @@
1
+ module Flubber
2
+ module App
3
+ S3_BUCKET_NAME = "expo-flubber"
4
+ APP_INFO_LOCATION = ".flubber"
5
+
6
+ def self.set(flubber_app)
7
+ file = File.open(APP_INFO_LOCATION, "w+")
8
+ file.puts "#{flubber_app["id"]}"
9
+ file.puts "#{flubber_app["name"].downcase.gsub(" ", "-").gsub(/\s+/, "")}"
10
+ file.puts "#{flubber_app["uuid"]}"
11
+ file.close
12
+ end
13
+
14
+ def self.set?
15
+ File.exists?(APP_INFO_LOCATION)
16
+ end
17
+
18
+ def self.id
19
+ file = File.open(APP_INFO_LOCATION)
20
+ lines = file.readlines.map(&:chomp)
21
+ file.close
22
+ lines[0]
23
+ end
24
+
25
+ def self.name
26
+ file = File.open(APP_INFO_LOCATION)
27
+ lines = file.readlines.map(&:chomp)
28
+ file.close
29
+ lines[1]
30
+ end
31
+
32
+ def self.uuid
33
+ file = File.open(APP_INFO_LOCATION)
34
+ lines = file.readlines.map(&:chomp)
35
+ file.close
36
+ lines[2]
37
+ end
38
+
39
+ def self.bundles_public_url
40
+ @bundles_public_url ||= \
41
+ "https://#{S3_BUCKET_NAME}.s3.amazonaws.com/#{uuid}/#{name}"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,71 @@
1
+ require "faraday"
2
+ require "JSON"
3
+ require "mimemagic"
4
+
5
+ require "app"
6
+ require "api_url"
7
+
8
+ module Flubber
9
+ class AwsFileUpload
10
+ def self.upload(file_path:, aws_destination:)
11
+ new(file_path, aws_destination).upload
12
+ end
13
+
14
+ def initialize(file_path, aws_destination)
15
+ @file_path = file_path
16
+ @aws_destination = aws_destination
17
+ end
18
+
19
+ def upload
20
+ connection.post("/", payload)
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :file_path, :aws_destination
26
+
27
+ def connection
28
+ @connection ||= Faraday.new(presigned_post["url"]) do |f|
29
+ f.request :multipart
30
+ f.request :url_encoded
31
+ f.adapter :net_http
32
+ end
33
+ end
34
+
35
+ def payload
36
+ @payload ||= presigned_post["fields"].merge(
37
+ :file => Faraday::UploadIO.new(file_path, content_type),
38
+ )
39
+ end
40
+
41
+ def presigned_post
42
+ @presigned_post ||= JSON.parse(Faraday.post(
43
+ "#{API_URL}/presigned_posts",
44
+ {
45
+ "app_uuid": app_uuid,
46
+ "aws_directory": aws_destination,
47
+ "content_type": content_type,
48
+ },
49
+ {
50
+ "Authorization" => "Bearer #{api_token}"
51
+ }
52
+ ).body)
53
+ end
54
+
55
+ def api_token
56
+ @api_token ||= ApiToken.get
57
+ end
58
+
59
+ def app_uuid
60
+ @app_uuid ||= App.uuid
61
+ end
62
+
63
+ def content_type
64
+ @content_type ||= begin
65
+ MimeMagic.by_path(file_path).type
66
+ rescue
67
+ "binary/octet-stream"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,37 @@
1
+ require "aws_file_upload"
2
+
3
+ module Flubber
4
+ class AwsFolderUpload
5
+ IGNORED_FILES = [".DS_Store"]
6
+
7
+ def self.upload(folder_name:, aws_destination:)
8
+ new(folder_name, aws_destination).upload
9
+ end
10
+
11
+ def initialize(folder_name, aws_destination)
12
+ @folder_name = folder_name
13
+ @aws_destination = aws_destination
14
+ end
15
+
16
+ def upload
17
+ file_paths.each do |file_path|
18
+ AwsFileUpload.upload(
19
+ file_path: file_path,
20
+ aws_destination: aws_destination,
21
+ )
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :folder_name, :aws_destination
28
+
29
+ def file_paths
30
+ @file_paths ||= Dir
31
+ .children("./#{folder_name}")
32
+ .filter { |file| !IGNORED_FILES.include?(file) }
33
+ .map { |file| File.expand_path("#{folder_name}/#{file}") }
34
+ .filter { |file| !File.directory?(file) }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ require "thor"
2
+
3
+ require "login"
4
+ require "deploy"
5
+
6
+ module Flubber
7
+ class CLI < Thor
8
+
9
+ desc "login", "login to flubber"
10
+ def login
11
+ Login.login
12
+ end
13
+
14
+ desc "deploy", "deploy an over the air update"
15
+ def deploy
16
+ Login.login
17
+ Deploy.deploy
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,165 @@
1
+ require "git"
2
+ require "tty-spinner"
3
+ require "active_support"
4
+ require "active_support/core_ext"
5
+
6
+ require "app"
7
+ require "api_url"
8
+ require "api_token"
9
+ require "aws_folder_upload"
10
+
11
+ module Flubber
12
+ class Deploy
13
+ FILE_ALL_EXPO_APPS_HAVE = ".expo"
14
+ FOLDERS_TO_UPLOAD = [{
15
+ name: "dist",
16
+ aws_destination: "/",
17
+ }, {
18
+ name: "dist/assets",
19
+ aws_destination: "assets",
20
+ }, {
21
+ name: "dist/bundles",
22
+ aws_destination: "bundles",
23
+ }]
24
+
25
+ def self.deploy
26
+ new.deploy
27
+ end
28
+
29
+ def deploy
30
+ unless working_directory_is_an_expo_project?
31
+ puts "Failed. Run me in an expo project."
32
+ return
33
+ end
34
+ remove_bundle
35
+ set_flubber_app
36
+ puts "Using app #{App.name}"
37
+ generate_bundle
38
+ upload_bundle
39
+ remove_bundle
40
+ log_deploy
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :selection
46
+
47
+ def working_directory_is_an_expo_project?
48
+ @working_directory_is_an_expo_project ||= \
49
+ File.exist?(FILE_ALL_EXPO_APPS_HAVE)
50
+ end
51
+
52
+ def set_flubber_app
53
+ return if flubber_app_is_set?
54
+ ask_for_which_flubber_app_to_use
55
+ end
56
+
57
+ def flubber_app_is_set?
58
+ App.set?
59
+ end
60
+
61
+ def flubber_apps
62
+ @flubber_apps ||= JSON.parse(Faraday.get(
63
+ "#{API_URL}/apps",
64
+ {},
65
+ { "Authorization" => "Bearer #{api_token}" },
66
+ ).body)
67
+ end
68
+
69
+ def print_existing_app_options
70
+ flubber_apps.each_with_index do |app, index|
71
+ puts "#{index + 1}. #{app["name"]}"
72
+ end
73
+ end
74
+
75
+ def print_new_app_option
76
+ puts "#{flubber_apps.count + 1}. Create A New Flubber App"
77
+ end
78
+
79
+ def existing_app_selected?
80
+ selection >= 1 && selection <= flubber_apps.count
81
+ end
82
+
83
+ def new_app_selected?
84
+ selection == flubber_apps.count + 1
85
+ end
86
+
87
+ def use_existing_app_selection
88
+ flubber_app_index = selection - 1
89
+ App.set(flubber_apps[flubber_app_index])
90
+ end
91
+
92
+ def create_new_app
93
+ print "Name your app: "
94
+ name = STDIN.gets.chomp
95
+ app = JSON.parse(Faraday.post(
96
+ "#{API_URL}/apps",
97
+ { "name": name },
98
+ { "Authorization" => "Bearer #{api_token}" },
99
+ ).body)
100
+ App.set(app)
101
+ end
102
+
103
+ def ask_for_which_flubber_app_to_use
104
+ print_existing_app_options
105
+ print_new_app_option
106
+ loop do
107
+ print "Which app? "
108
+ @selection = STDIN.gets.chomp.to_i
109
+ if existing_app_selected?
110
+ use_existing_app_selection
111
+ break
112
+ elsif new_app_selected?
113
+ create_new_app
114
+ break
115
+ end
116
+ puts "Not an option"
117
+ end
118
+ end
119
+
120
+ def generate_bundle
121
+ bundling_spinner = TTY::Spinner.new(
122
+ "[:spinner] Bundling ...",
123
+ format: :dots,
124
+ )
125
+ bundling_spinner.auto_spin
126
+ `expo export --public-url #{App.bundles_public_url}`
127
+ bundling_spinner.stop('Done!')
128
+ end
129
+
130
+ def upload_bundle
131
+ uploading_spinner = TTY::Spinner.new(
132
+ "[:spinner] Uploading ...",
133
+ format: :dots,
134
+ )
135
+ uploading_spinner.auto_spin
136
+ FOLDERS_TO_UPLOAD.each do |folder|
137
+ AwsFolderUpload.upload(
138
+ folder_name: folder[:name],
139
+ aws_destination: folder[:aws_destination],
140
+ )
141
+ end
142
+ uploading_spinner.stop('Done!')
143
+ end
144
+
145
+ def log_deploy
146
+ Faraday.post(
147
+ "#{API_URL}/apps/#{App.id}/updates",
148
+ { "commit_sha": sha },
149
+ { "Authorization" => "Bearer #{api_token}" },
150
+ )
151
+ end
152
+
153
+ def sha
154
+ @sha ||= Git.open(".").log.first.sha
155
+ end
156
+
157
+ def api_token
158
+ @api_token ||= ApiToken.get
159
+ end
160
+
161
+ def remove_bundle
162
+ `rm -rf dist`
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,66 @@
1
+ require "io/console"
2
+ require "faraday"
3
+ require "JSON"
4
+ require "active_support"
5
+ require "active_support/core_ext"
6
+
7
+ require "api_url"
8
+ require "api_token"
9
+
10
+ module Flubber
11
+ class Login
12
+ def self.login
13
+ new.login
14
+ end
15
+
16
+ def login
17
+ if logged_in?
18
+ puts "Already logged in."
19
+ else
20
+ get_credentials
21
+ get_api_token
22
+ save_api_token
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :phone_number, :password, :api_token
29
+
30
+ def get_credentials
31
+ print "Phone Number: "
32
+ @phone_number = STDIN.gets.chomp
33
+ print "Password: "
34
+ @password = IO::console.getpass
35
+ end
36
+
37
+ def get_api_token
38
+ if api_token_response.status == 201
39
+ @api_token = JSON.parse(api_token_response.body)["api_token"]
40
+ end
41
+ end
42
+
43
+ def save_api_token
44
+ if api_token.present?
45
+ ApiToken.set(api_token)
46
+ puts "Success"
47
+ else
48
+ puts "Incorrect credentials"
49
+ end
50
+ end
51
+
52
+ def api_token_response
53
+ @api_token_response ||= Faraday.post(
54
+ "#{API_URL}/user_token",
55
+ {
56
+ "phone_number": phone_number,
57
+ "password": password,
58
+ },
59
+ )
60
+ end
61
+
62
+ def logged_in?
63
+ @logged_in ||= ApiToken.get.present?
64
+ end
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: expo-flubber
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Nicholas Pachulski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-08-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: git
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.7.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.7.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 6.0.3.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 6.0.3.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: tty-spinner
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.9.3
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.9.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: faraday
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: json
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 2.3.1
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 2.3.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: mimemagic
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.3.5
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.3.5
111
+ - !ruby/object:Gem::Dependency
112
+ name: io
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.0.1
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.0.1
125
+ description: Update your react native expo apps Over-The-Air without size restriction
126
+ email: nick@pachulski.me
127
+ executables:
128
+ - flubber
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - bin/flubber
133
+ - lib/api_token.rb
134
+ - lib/api_url.rb
135
+ - lib/app.rb
136
+ - lib/aws_file_upload.rb
137
+ - lib/aws_folder_upload.rb
138
+ - lib/cli.rb
139
+ - lib/deploy.rb
140
+ - lib/login.rb
141
+ homepage: http://pachulski.me
142
+ licenses:
143
+ - MIT
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubygems_version: 3.0.3
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: OTA Updates Without Size Restriction
164
+ test_files: []