ruby_native 0.3.2 → 0.4.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3edb27d0146c40d03a5a5e460d35d8c1790e632d0db30d38ca5d714c436d55ed
4
- data.tar.gz: 1eb68feb7476a73447724a05793b76d112e0e606f1fec83a4a16c36679b224e3
3
+ metadata.gz: e2d194d87a1b21e7364717af329e474650f57137e80daa10c9ed6772e8144003
4
+ data.tar.gz: acd0e6d63d29fbeaa31341e283e10f6e581e07db233d2cf47f7bd4b64cd77299
5
5
  SHA512:
6
- metadata.gz: 9f2ae974c958b92172621e090547ebdeb0eb3e9175d0c622a44e59447c5f328bcda5fddf1f955fc8d18bf8ea6f32626c55cf0ca2e04008c9063e06ef6e163fab
7
- data.tar.gz: f08c4ae514b7df44aecf6fbe2718a66b0b979f6356ff78808eb8c394e7f74f9874d19cc9535111d127320e99d9e12c08d7c6c0cb40b8235b0f81daa6ab964ce4
6
+ metadata.gz: 70c00f3ac29b500054e4921781ea16f23a16b0eb0ccde6a7758ab417f14ca21998bae0ea78d1175b56968d734fb36df5a3073bc619b45bcb71737e4827ec1cfd
7
+ data.tar.gz: 802f1a3e5979e8ea4c4946b555b5df59afa0478cb9e0c8b7807b6dfe538f9b4cda27971addb0dca88a05757dc0a873871007765f2ca9408b5be73c1a2b1c8957
@@ -1,3 +1,7 @@
1
+ [data-native-app] .native-hidden {
2
+ display: none !important;
3
+ }
4
+
1
5
  .native-back-button {
2
6
  display: none;
3
7
  }
@@ -1,6 +1,10 @@
1
1
  import { createElement } from "react"
2
+ import { router } from "@inertiajs/react"
2
3
 
3
- export function NativeTabs() {
4
+ window.__inertiaRouter = router
5
+
6
+ export function NativeTabs({ enabled = true }) {
7
+ if (!enabled) return null
4
8
  return createElement("div", { "data-native-tabs": true, hidden: true })
5
9
  }
6
10
 
@@ -11,3 +15,38 @@ export function NativePush() {
11
15
  export function NativeForm() {
12
16
  return createElement("div", { "data-native-form": true, hidden: true })
13
17
  }
18
+
19
+ export function NativeNavbar({ title, children }) {
20
+ return createElement("div", { "data-native-navbar": title, hidden: true }, children)
21
+ }
22
+
23
+ export function NativeButton({ position = "trailing", icon, title, href, click, selected, children }) {
24
+ const props = { "data-native-button": true }
25
+ if (icon) props["data-native-icon"] = icon
26
+ if (title) props["data-native-title"] = title
27
+ if (href) props["data-native-href"] = href
28
+ if (click) props["data-native-click"] = click
29
+ if (position) props["data-native-position"] = position
30
+ if (selected) props["data-native-selected"] = ""
31
+ return createElement("div", props, children)
32
+ }
33
+
34
+ export function NativeMenuItem({ title, href, click, icon, selected }) {
35
+ const props = { "data-native-menu-item": true }
36
+ if (title) props["data-native-title"] = title
37
+ if (href) props["data-native-href"] = href
38
+ if (click) props["data-native-click"] = click
39
+ if (icon) props["data-native-icon"] = icon
40
+ if (selected) props["data-native-selected"] = ""
41
+ return createElement("div", props)
42
+ }
43
+
44
+ export function NativeSubmitButton({ title = "Save", click = "[type='submit']" }) {
45
+ return createElement("div", {
46
+ "data-native-submit-button": true,
47
+ "data-native-title": title,
48
+ "data-native-click": click,
49
+ hidden: true
50
+ })
51
+ }
52
+
@@ -1,8 +1,15 @@
1
1
  import { defineComponent, h } from "vue"
2
+ import { router } from "@inertiajs/vue3"
3
+
4
+ window.__inertiaRouter = router
2
5
 
3
6
  export const NativeTabs = defineComponent({
4
7
  name: "NativeTabs",
8
+ props: {
9
+ enabled: { type: Boolean, default: true }
10
+ },
5
11
  render() {
12
+ if (!this.enabled) return null
6
13
  return h("div", { "data-native-tabs": true, hidden: true })
7
14
  }
8
15
  })
@@ -20,3 +27,69 @@ export const NativeForm = defineComponent({
20
27
  return h("div", { "data-native-form": true, hidden: true })
21
28
  }
22
29
  })
30
+
31
+ export const NativeNavbar = defineComponent({
32
+ name: "NativeNavbar",
33
+ props: { title: { type: String, required: true } },
34
+ render() {
35
+ return h("div", { "data-native-navbar": this.title, hidden: true }, this.$slots.default?.())
36
+ }
37
+ })
38
+
39
+ export const NativeButton = defineComponent({
40
+ name: "NativeButton",
41
+ props: {
42
+ position: { type: String, default: "trailing" },
43
+ icon: String,
44
+ title: String,
45
+ href: String,
46
+ click: String,
47
+ selected: { type: Boolean, default: undefined }
48
+ },
49
+ render() {
50
+ const attrs = { "data-native-button": true }
51
+ if (this.icon) attrs["data-native-icon"] = this.icon
52
+ if (this.title) attrs["data-native-title"] = this.title
53
+ if (this.href) attrs["data-native-href"] = this.href
54
+ if (this.click) attrs["data-native-click"] = this.click
55
+ if (this.position) attrs["data-native-position"] = this.position
56
+ if (this.selected) attrs["data-native-selected"] = ""
57
+ return h("div", attrs, this.$slots.default?.())
58
+ }
59
+ })
60
+
61
+ export const NativeMenuItem = defineComponent({
62
+ name: "NativeMenuItem",
63
+ props: {
64
+ title: String,
65
+ href: String,
66
+ click: String,
67
+ icon: String,
68
+ selected: { type: Boolean, default: undefined }
69
+ },
70
+ render() {
71
+ const attrs = { "data-native-menu-item": true }
72
+ if (this.title) attrs["data-native-title"] = this.title
73
+ if (this.href) attrs["data-native-href"] = this.href
74
+ if (this.click) attrs["data-native-click"] = this.click
75
+ if (this.icon) attrs["data-native-icon"] = this.icon
76
+ if (this.selected) attrs["data-native-selected"] = ""
77
+ return h("div", attrs)
78
+ }
79
+ })
80
+
81
+ export const NativeSubmitButton = defineComponent({
82
+ name: "NativeSubmitButton",
83
+ props: {
84
+ title: { type: String, default: "Save" },
85
+ click: { type: String, default: "[type='submit']" }
86
+ },
87
+ render() {
88
+ return h("div", {
89
+ "data-native-submit-button": true,
90
+ "data-native-title": this.title,
91
+ "data-native-click": this.click,
92
+ hidden: true
93
+ })
94
+ }
95
+ })
@@ -0,0 +1,253 @@
1
+ require "json"
2
+ require "net/http"
3
+ require "uri"
4
+ require "ruby_native/cli/credentials"
5
+
6
+ module RubyNative
7
+ class CLI
8
+ class Deploy
9
+ CONFIG_PATH = "config/ruby_native.yml"
10
+ HOST = ENV.fetch("RUBY_NATIVE_HOST", "https://rubynative.com")
11
+ POLL_INTERVAL = 5
12
+ POLL_TIMEOUT = 600
13
+
14
+ TokenExpiredError = Class.new(StandardError)
15
+
16
+ def initialize(argv)
17
+ end
18
+
19
+ def run
20
+ load_config!
21
+ ensure_authenticated!
22
+ app_id = resolve_app_id!
23
+ build = trigger_build(app_id)
24
+ poll_build_status(app_id, build)
25
+ end
26
+
27
+ private
28
+
29
+ def ensure_authenticated!
30
+ unless Credentials.token
31
+ puts "Not logged in. Run `ruby_native login` first."
32
+ exit 1
33
+ end
34
+ end
35
+
36
+ def load_config!
37
+ unless File.exist?(CONFIG_PATH)
38
+ puts "config/ruby_native.yml not found. Run `rails generate ruby_native:install` first."
39
+ exit 1
40
+ end
41
+
42
+ require "yaml"
43
+ @config = YAML.load_file(CONFIG_PATH, symbolize_names: true) || {}
44
+ end
45
+
46
+ def resolve_app_id!
47
+ app_id = @config.dig(:ruby_native, :app_id)
48
+ app_id = link_app unless app_id
49
+ unless app_id
50
+ puts "No app selected. Run `ruby_native deploy` again."
51
+ exit 1
52
+ end
53
+ app_id
54
+ end
55
+
56
+ # --- Build ---
57
+
58
+ def trigger_build(app_id)
59
+ puts "Triggering build..."
60
+
61
+ uri = URI("#{HOST}/api/v1/apps/#{app_id}/builds")
62
+ req = Net::HTTP::Post.new(uri)
63
+ req["Authorization"] = "Token #{Credentials.token}"
64
+ req["Content-Type"] = "application/json"
65
+
66
+ response = make_request(uri, req)
67
+
68
+ case response
69
+ when Net::HTTPUnauthorized
70
+ raise TokenExpiredError
71
+ when Net::HTTPCreated
72
+ build = JSON.parse(response.body)
73
+ puts "Build ##{build["number"]} (v#{build["version"]}) queued."
74
+ build
75
+ when Net::HTTPTooManyRequests
76
+ puts "Build limit reached. Try again later."
77
+ exit 1
78
+ when Net::HTTPConflict
79
+ data = JSON.parse(response.body)
80
+ puts data["error"]
81
+ exit 1
82
+ when Net::HTTPUnprocessableEntity
83
+ data = JSON.parse(response.body)
84
+ puts "Cannot build: #{data["error"]}"
85
+ exit 1
86
+ when Net::HTTPNotFound
87
+ puts "App not found. Remove ruby_native.app_id from config/ruby_native.yml and run `ruby_native deploy` again to re-link."
88
+ exit 1
89
+ else
90
+ puts "Failed to trigger build: #{response.code} #{response.message}"
91
+ exit 1
92
+ end
93
+ rescue TokenExpiredError
94
+ puts "Token expired. Run `ruby_native login` again."
95
+ exit 1
96
+ end
97
+
98
+ # --- Polling ---
99
+
100
+ def poll_build_status(app_id, build)
101
+ build_id = build["id"]
102
+ last_status = build["status"]
103
+ puts ""
104
+ puts "Waiting for build to complete. Ctrl+C to exit (your build will continue)."
105
+ print_status(last_status)
106
+
107
+ started_at = Time.now
108
+
109
+ loop do
110
+ sleep POLL_INTERVAL
111
+
112
+ if Time.now - started_at > POLL_TIMEOUT
113
+ puts ""
114
+ puts "Timed out waiting for build. Check the Ruby Native dashboard for status."
115
+ exit 1
116
+ end
117
+
118
+ data = fetch_build_status(app_id, build_id)
119
+ next unless data
120
+
121
+ if data["status"] != last_status
122
+ last_status = data["status"]
123
+ print_status(last_status)
124
+ end
125
+
126
+ case last_status
127
+ when "success", "ready"
128
+ puts ""
129
+ puts "Build succeeded!"
130
+ puts " Version: v#{data["version"]} (#{data["number"]})"
131
+ puts " Ruby Native: #{data["native_version"]}" if data["native_version"]
132
+ puts ""
133
+ puts "Your build is being submitted to TestFlight."
134
+ break
135
+ when "failure", "failed", "cancelled"
136
+ puts ""
137
+ puts "Build failed."
138
+ puts " Error: #{data["error_message"]}" if data["error_message"]
139
+ exit 1
140
+ end
141
+ end
142
+ rescue Interrupt
143
+ puts ""
144
+ puts ""
145
+ puts "Stopped polling. Your build is still running."
146
+ puts "Check the Ruby Native dashboard for status."
147
+ end
148
+
149
+ def fetch_build_status(app_id, build_id)
150
+ uri = URI("#{HOST}/api/v1/apps/#{app_id}/builds/#{build_id}")
151
+ req = Net::HTTP::Get.new(uri)
152
+ req["Authorization"] = "Token #{Credentials.token}"
153
+
154
+ response = make_request(uri, req)
155
+
156
+ case response
157
+ when Net::HTTPSuccess
158
+ JSON.parse(response.body)
159
+ when Net::HTTPUnauthorized
160
+ puts ""
161
+ puts "Token expired. Run `ruby_native login` again."
162
+ exit 1
163
+ end
164
+ end
165
+
166
+ def print_status(status)
167
+ labels = {
168
+ "queued" => "Queued",
169
+ "building" => "Building",
170
+ "processing" => "Submitting to App Store Connect"
171
+ }
172
+ label = labels[status]
173
+ puts " #{label}..." if label
174
+ end
175
+
176
+ # --- App linking ---
177
+
178
+ def link_app
179
+ apps = fetch_apps
180
+ return unless apps
181
+
182
+ if apps.empty?
183
+ puts "No apps found on your account."
184
+ return
185
+ end
186
+
187
+ app = if apps.length == 1
188
+ puts "Using app: #{apps[0]["name"]}"
189
+ apps[0]
190
+ else
191
+ puts "Which app?"
192
+ apps.each_with_index do |a, i|
193
+ puts " #{i + 1}. #{a["name"]}"
194
+ end
195
+ print "> "
196
+ choice = ($stdin.gets&.strip || "").to_i
197
+ unless choice.between?(1, apps.length)
198
+ puts "Invalid choice."
199
+ return
200
+ end
201
+ apps[choice - 1]
202
+ end
203
+
204
+ app_id = app["public_id"]
205
+ write_app_id_to_config(app_id)
206
+ app_id
207
+ end
208
+
209
+ def fetch_apps
210
+ uri = URI("#{HOST}/api/v1/apps")
211
+ req = Net::HTTP::Get.new(uri)
212
+ req["Authorization"] = "Token #{Credentials.token}"
213
+
214
+ response = make_request(uri, req)
215
+
216
+ case response
217
+ when Net::HTTPUnauthorized
218
+ raise TokenExpiredError
219
+ when Net::HTTPSuccess
220
+ JSON.parse(response.body)
221
+ else
222
+ puts "Failed to fetch apps: #{response.code}"
223
+ nil
224
+ end
225
+ end
226
+
227
+ def write_app_id_to_config(app_id)
228
+ raw = File.read(CONFIG_PATH)
229
+
230
+ if raw.match?(/^ruby_native:/)
231
+ raw = raw.gsub(/^(ruby_native:\s*\n)/, "\\1 app_id: #{app_id}\n")
232
+ else
233
+ raw = raw.rstrip + "\n\nruby_native:\n app_id: #{app_id}\n"
234
+ end
235
+
236
+ File.write(CONFIG_PATH, raw)
237
+
238
+ require "yaml"
239
+ @config = YAML.load_file(CONFIG_PATH, symbolize_names: true) || {}
240
+ end
241
+
242
+ # --- HTTP ---
243
+
244
+ def make_request(uri, req)
245
+ http = Net::HTTP.new(uri.host, uri.port)
246
+ http.use_ssl = uri.scheme == "https"
247
+ http.open_timeout = 10
248
+ http.read_timeout = 30
249
+ http.request(req)
250
+ end
251
+ end
252
+ end
253
+ end
@@ -1,4 +1,5 @@
1
1
  require "ruby_native/cli/credentials"
2
+ require "ruby_native/cli/deploy"
2
3
  require "ruby_native/cli/login"
3
4
  require "ruby_native/cli/preview"
4
5
  require "ruby_native/cli/screenshots"
@@ -8,6 +9,8 @@ module RubyNative
8
9
  def self.start(argv)
9
10
  command = argv.shift
10
11
  case command
12
+ when "deploy"
13
+ RubyNative::CLI::Deploy.new(argv).run
11
14
  when "preview"
12
15
  RubyNative::CLI::Preview.new(argv).run
13
16
  when "screenshots"
@@ -21,6 +24,7 @@ module RubyNative
21
24
  puts "Usage: ruby_native <command>"
22
25
  puts ""
23
26
  puts "Commands:"
27
+ puts " deploy Trigger an iOS build"
24
28
  puts " login Authenticate with Ruby Native"
25
29
  puts " logout Remove stored credentials"
26
30
  puts " preview Start a tunnel and display a QR code"
@@ -77,6 +77,13 @@ module RubyNative
77
77
  ])
78
78
  end
79
79
 
80
+ def native_navbar_tag(title, &block)
81
+ builder = NavbarBuilder.new(self)
82
+ capture(builder, &block) if block
83
+
84
+ tag.div(data: { native_navbar: title }, hidden: true) { builder.to_html }
85
+ end
86
+
80
87
  def native_haptic_data(feedback = :success, **data)
81
88
  feedback = feedback.to_s
82
89
  feedback = "success" if feedback.empty?
@@ -112,5 +119,62 @@ module RubyNative
112
119
  @context.safe_join(@items)
113
120
  end
114
121
  end
122
+
123
+ class NavbarBuilder
124
+ def initialize(context)
125
+ @context = context
126
+ @items = []
127
+ end
128
+
129
+ def button(icon: nil, title: nil, href: nil, click: nil, position: :trailing, selected: false, &block)
130
+ data = { native_button: "" }
131
+ data[:native_icon] = icon if icon
132
+ data[:native_title] = title if title
133
+ data[:native_href] = href if href
134
+ data[:native_click] = click if click
135
+ data[:native_position] = position.to_s
136
+ data[:native_selected] = "" if selected
137
+
138
+ if block
139
+ menu = NavbarMenuBuilder.new(@context)
140
+ @context.capture(menu, &block)
141
+ @items << @context.tag.div(data: data) { menu.to_html }
142
+ else
143
+ @items << @context.tag.div(data: data)
144
+ end
145
+ end
146
+
147
+ def submit_button(title: "Save", click: "[type='submit']")
148
+ @items << @context.tag.div(data: {
149
+ native_submit_button: "",
150
+ native_title: title,
151
+ native_click: click
152
+ })
153
+ end
154
+
155
+ def to_html
156
+ @context.safe_join(@items)
157
+ end
158
+ end
159
+
160
+ class NavbarMenuBuilder
161
+ def initialize(context)
162
+ @context = context
163
+ @items = []
164
+ end
165
+
166
+ def item(title, href: nil, click: nil, icon: nil, selected: false)
167
+ data = { native_menu_item: "", native_title: title }
168
+ data[:native_href] = href if href
169
+ data[:native_click] = click if click
170
+ data[:native_icon] = icon if icon
171
+ data[:native_selected] = "" if selected
172
+ @items << @context.tag.div(data: data)
173
+ end
174
+
175
+ def to_html
176
+ @context.safe_join(@items)
177
+ end
178
+ end
115
179
  end
116
180
  end
@@ -8,7 +8,7 @@ module RubyNative
8
8
  end
9
9
 
10
10
  inertia_share do
11
- { native_app: native_app?, native_form: @native_form || false }
11
+ { nativeApp: native_app?, nativeForm: @native_form || false }
12
12
  end
13
13
  end
14
14
  end
@@ -1,3 +1,3 @@
1
1
  module RubyNative
2
- VERSION = "0.3.2"
2
+ VERSION = "0.4.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_native
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Masilotti
@@ -75,6 +75,7 @@ files:
75
75
  - lib/ruby_native.rb
76
76
  - lib/ruby_native/cli.rb
77
77
  - lib/ruby_native/cli/credentials.rb
78
+ - lib/ruby_native/cli/deploy.rb
78
79
  - lib/ruby_native/cli/login.rb
79
80
  - lib/ruby_native/cli/preview.rb
80
81
  - lib/ruby_native/cli/screenshots.rb