dev_shopify_theme 0.0.21
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 +7 -0
- data/.gitignore +5 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +22 -0
- data/CONTRIBUTING +6 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +140 -0
- data/Rakefile +14 -0
- data/bin/dev-theme +24 -0
- data/doc/how_to_find_theme_id.png +0 -0
- data/lib/shopify_theme.rb +155 -0
- data/lib/shopify_theme/cli.rb +304 -0
- data/lib/shopify_theme/version.rb +3 -0
- data/shipit.rubygems.yml +1 -0
- data/shopify_theme.gemspec +32 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/unit/cli_spec.rb +85 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6b4bc2a2f214aebb3d716fb73dcbc16c75cece67
|
4
|
+
data.tar.gz: 45aa9e57e59faf45ed4264db7b6fba01ff61a32b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3d2abfe0baa1921a294cbf470b5e11f0a4f15f70d3b72be3a9c43a18154e40c6380eb4e12b8b851941ad65b32ddc81cd3cc643482fd732efa0c15a48f5adaa28
|
7
|
+
data.tar.gz: a29ee36ab3d79f27eb4e135bcd7d5897b63044931733ff82c4c9297b672e13f13ead52e88580edfc47eb334455f5fd60dc5992b6abf3b6d354739ad88f5b6eb8
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# v0.0.21
|
2
|
+
|
3
|
+
* Adds Locale support
|
4
|
+
* Running `theme replace` no longer deletes unwatched files if they are found on Shopify
|
5
|
+
|
6
|
+
# v0.0.20
|
7
|
+
|
8
|
+
* Fixes issue with unknown types raising exceptions -- csaunders
|
9
|
+
|
10
|
+
# v0.0.19
|
11
|
+
|
12
|
+
* Fixes issue with incorrectly detecting binary assets -- csaunders
|
13
|
+
* Properly handle unknown event types --
|
14
|
+
|
15
|
+
# v0.0.12
|
16
|
+
|
17
|
+
* Fixes issue where app tried uploading directories
|
18
|
+
|
19
|
+
# v0.0.11
|
20
|
+
|
21
|
+
* Locks JSON api version which fixed an issue with downloading large binary files -- Tyler Ball
|
22
|
+
* Respect API limits when downloading theme assets from Shopify API -- Chris Saunders
|
data/CONTRIBUTING
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
- If the issue doesn't exist, open one to start a discussion
|
2
|
+
- If the issue is fine (it probably is) start working on your patch
|
3
|
+
- Create a Pull Request
|
4
|
+
- Be sure to update CHANGELOG.md to include the details of your fix
|
5
|
+
- Go through code review
|
6
|
+
- Get it merged
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Shopify
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
# Edit your Shopify theme locally
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/shopify_theme)
|
4
|
+
|
5
|
+
The Shopify theme gem is a command line tool that lets you make live changes to themes on your Shopify store. If the command line is scary, check out the [Desktop Theme Editor app](http://apps.shopify.com/desktop-theme-editor).
|
6
|
+
|
7
|
+
It will watch your local folders for any changes in your theme (including adding and removing files) and will update your .myshopify.com store to the latest changes.
|
8
|
+
|
9
|
+

|
10
|
+
|
11
|
+
You do not need to make changes to your default theme. You can leverage the theme preview feature of Shopify
|
12
|
+
that allows you to view what an unpublished theme looks like on your Shopify store. This means you don't need to
|
13
|
+
sign up for extra accounts and keep your shop up to date. You will however have a blue bar that shows up that you can
|
14
|
+
remove via the web inspector in Chrome or Safari.
|
15
|
+
|
16
|
+
# Requirements
|
17
|
+
|
18
|
+
This gem works with OS X or Windows with Ruby 1.9.
|
19
|
+
|
20
|
+
First time installing Ruby on windows? Try [Rubyinstaller](http://http://rubyinstaller.org/).
|
21
|
+
|
22
|
+
# Installation
|
23
|
+
|
24
|
+
To install the shopify_theme gem use 'gem install' (you might have use 'sudo gem install')
|
25
|
+
|
26
|
+
```
|
27
|
+
gem install shopify_theme [optional_theme_id]
|
28
|
+
```
|
29
|
+
|
30
|
+
to update to the latest version
|
31
|
+
|
32
|
+
```
|
33
|
+
gem update shopify_theme
|
34
|
+
```
|
35
|
+
|
36
|
+
# Usage
|
37
|
+
|
38
|
+
Generate the config file. Go get a valid api_key and password for your store head to `https://[your store].myshopify.com/admin/apps/private` and generate a private application. By default it adds the main theme, if you want to edit one of your other themes, add the `theme_id`.
|
39
|
+
|
40
|
+
```
|
41
|
+
theme configure api_key password store_url
|
42
|
+
```
|
43
|
+
|
44
|
+
Example of config.yml. Notice store has no http or https declaration. You can
|
45
|
+
use `:whitelist_files:` to specify files for upload. The `assets/`, `config/`,
|
46
|
+
`layout/`, `snippets/`, `templates/` and `locales/`directories are included by
|
47
|
+
default.
|
48
|
+
|
49
|
+
You can also use `:ignore_files:` to exclude files from getting uploaded, for
|
50
|
+
example your `config/settings.html` or other configuration driven items
|
51
|
+
|
52
|
+
```yaml
|
53
|
+
---
|
54
|
+
:api_key: 7a8da86d3dd730b67a357dedabaac5d6
|
55
|
+
:password: 552338ce0d3aba7fc501dcf99bc57a81
|
56
|
+
:store: little-plastics.myshopify.com
|
57
|
+
:theme_id:
|
58
|
+
:whitelist_files:
|
59
|
+
- directoryToUpload/
|
60
|
+
- importantFile.txt
|
61
|
+
:ignore_files:
|
62
|
+
- config/settings.html
|
63
|
+
```
|
64
|
+
|
65
|
+
Download all the theme files
|
66
|
+
|
67
|
+
```
|
68
|
+
theme download
|
69
|
+
```
|
70
|
+
|
71
|
+
Upload a theme file
|
72
|
+
|
73
|
+
```
|
74
|
+
theme upload assets/layout.liquid
|
75
|
+
```
|
76
|
+
|
77
|
+
Remove a theme file
|
78
|
+
|
79
|
+
```
|
80
|
+
theme remove assets/layout.liquid
|
81
|
+
```
|
82
|
+
|
83
|
+
Completely replace shop theme assets with the local assets
|
84
|
+
|
85
|
+
```
|
86
|
+
theme replace
|
87
|
+
```
|
88
|
+
|
89
|
+
Watch the theme directory and upload any files as they change
|
90
|
+
|
91
|
+
```
|
92
|
+
theme watch
|
93
|
+
```
|
94
|
+
|
95
|
+
Open the store in the default browser
|
96
|
+
|
97
|
+
```
|
98
|
+
theme open
|
99
|
+
```
|
100
|
+
|
101
|
+
Bootstrap a new theme with [Timber](http://www.shopify.com/timber)
|
102
|
+
|
103
|
+
```
|
104
|
+
# use latest stable
|
105
|
+
theme bootstrap api_key password shop_name theme_name
|
106
|
+
|
107
|
+
# use latest build
|
108
|
+
theme bootstrap api_key password shop_name theme_name master
|
109
|
+
```
|
110
|
+
|
111
|
+
# Common Problems
|
112
|
+
|
113
|
+
## SSL Certificates won't verify on Windows
|
114
|
+
|
115
|
+
If you are experiencing SSL validation errors, it is most likely because your installation does not have any valid
|
116
|
+
certificates. This can be taken care of by [downloading a certificate file](http://curl.haxx.se/ca/cacert.pem) and
|
117
|
+
[setting a the SSL_CERT_FILE environment variable on your system](http://www.computerhope.com/issues/ch000549.htm).
|
118
|
+
For more details check out the following [gist](https://gist.github.com/fnichol/867550).
|
119
|
+
|
120
|
+
[See the following issue for more details](https://github.com/Shopify/shopify_theme/issues/103)
|
121
|
+
|
122
|
+
## How do I edit a theme that isn't my shops main theme?
|
123
|
+
|
124
|
+
This can be done by setting the `theme_id` field in `config.yml` which was created when you
|
125
|
+
ran `theme configure`. Your file should look like the following:
|
126
|
+
|
127
|
+
```yaml
|
128
|
+
---
|
129
|
+
:api_key: 7a8da86d3dd730b67a357dedabaac5d6
|
130
|
+
:password: 552338ce0d3aba7fc501dcf99bc57a81
|
131
|
+
:store: little-plastics.myshopify.com
|
132
|
+
:theme_id: 0987654321
|
133
|
+
```
|
134
|
+
|
135
|
+
## Where can I find my Theme Id?
|
136
|
+
|
137
|
+
Currently the best way to find the id of the theme you want to edit is to go to the theme in your
|
138
|
+
shops admin and grab it from the url.
|
139
|
+
|
140
|
+

|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
task :default => [:spec]
|
6
|
+
|
7
|
+
Rake::TestTask.new 'spec' do |t|
|
8
|
+
ENV['test'] = 'true'
|
9
|
+
t.libs = ['lib', 'spec']
|
10
|
+
t.ruby_opts << '-rubygems'
|
11
|
+
t.verbose = true
|
12
|
+
t.test_files = FileList['spec/**/*_spec.rb']
|
13
|
+
end
|
14
|
+
|
data/bin/dev-theme
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This allows shopify_theme to run easily from a git checkout without install.
|
4
|
+
# Hat tip to chriseppstein of Compass fame for the code for this
|
5
|
+
def fallback_load_path(path)
|
6
|
+
retried = false
|
7
|
+
begin
|
8
|
+
yield
|
9
|
+
rescue LoadError
|
10
|
+
unless retried
|
11
|
+
$: << path
|
12
|
+
retried = true
|
13
|
+
retry
|
14
|
+
end
|
15
|
+
raise
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
fallback_load_path(File.join(File.dirname(__FILE__), '..', 'lib')) do
|
20
|
+
require 'shopify_theme'
|
21
|
+
require 'shopify_theme/cli'
|
22
|
+
end
|
23
|
+
|
24
|
+
ShopifyTheme::Cli.start(ARGV)
|
Binary file
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
module ShopifyTheme
|
3
|
+
include HTTParty
|
4
|
+
@@current_api_call_count = 0
|
5
|
+
@@total_api_calls = 40
|
6
|
+
|
7
|
+
NOOPParser = Proc.new {|data, format| {} }
|
8
|
+
TIMER_RESET = 10
|
9
|
+
PERMIT_LOWER_LIMIT = 3
|
10
|
+
TIMBER_ZIP = "https://github.com/Shopify/Timber/archive/%s.zip"
|
11
|
+
LAST_KNOWN_STABLE = "v1.1.0"
|
12
|
+
|
13
|
+
def self.test?
|
14
|
+
ENV['test']
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.manage_timer(response)
|
18
|
+
return unless response.headers['x-shopify-shop-api-call-limit']
|
19
|
+
@@current_api_call_count, @@total_api_calls = response.headers['x-shopify-shop-api-call-limit'].split('/')
|
20
|
+
@@current_timer = Time.now if @current_timer.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.critical_permits?
|
24
|
+
@@total_api_calls.to_i - @@current_api_call_count.to_i < PERMIT_LOWER_LIMIT
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.passed_api_refresh?
|
28
|
+
delta_seconds > TIMER_RESET
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.delta_seconds
|
32
|
+
Time.now.to_i - @@current_timer.to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.needs_sleep?
|
36
|
+
critical_permits? && !passed_api_refresh?
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.sleep
|
40
|
+
if needs_sleep?
|
41
|
+
Kernel.sleep(TIMER_RESET - delta_seconds)
|
42
|
+
@current_timer = nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.api_usage
|
47
|
+
"[API Limit: #{@@current_api_call_count || "??"}/#{@@total_api_calls || "??"}]"
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def self.asset_list
|
52
|
+
# HTTParty parser chokes on assest listing, have it noop
|
53
|
+
# and then use a rel JSON parser.
|
54
|
+
response = shopify.get(path, :parser => NOOPParser)
|
55
|
+
manage_timer(response)
|
56
|
+
assets = JSON.parse(response.body)["assets"].collect {|a| a['key'] }
|
57
|
+
# Remove any .css files if a .css.liquid file exists
|
58
|
+
assets.reject{|a| assets.include?("#{a}.liquid") }
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.get_asset(asset)
|
62
|
+
response = shopify.get(path, :query =>{:asset => {:key => asset}}, :parser => NOOPParser)
|
63
|
+
manage_timer(response)
|
64
|
+
|
65
|
+
# HTTParty json parsing is broken?
|
66
|
+
asset = response.code == 200 ? JSON.parse(response.body)["asset"] : {}
|
67
|
+
asset['response'] = response
|
68
|
+
asset
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.send_asset(data)
|
72
|
+
response = shopify.put(path, :body =>{:asset => data})
|
73
|
+
manage_timer(response)
|
74
|
+
response
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.delete_asset(asset)
|
78
|
+
response = shopify.delete(path, :body =>{:asset => {:key => asset}})
|
79
|
+
manage_timer(response)
|
80
|
+
response
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.upload_timber(name, master)
|
84
|
+
source = TIMBER_ZIP % (master ? 'master' : LAST_KNOWN_STABLE)
|
85
|
+
puts master ? "Using latest build from shopify" : "Using last known stable build -- #{LAST_KNOWN_STABLE}"
|
86
|
+
response = shopify.post("/admin/themes.json", :body => {:theme => {:name => name, :src => source, :role => 'unpublished'}})
|
87
|
+
manage_timer(response)
|
88
|
+
body = JSON.parse(response.body)
|
89
|
+
if theme = body['theme']
|
90
|
+
watch_until_processing_complete(theme)
|
91
|
+
else
|
92
|
+
puts "Could not download theme!"
|
93
|
+
puts body
|
94
|
+
exit 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.config
|
99
|
+
@config ||= if File.exist? 'config.yml'
|
100
|
+
config = YAML.load(File.read('config.yml'))
|
101
|
+
config
|
102
|
+
else
|
103
|
+
puts "config.yml does not exist!" unless test?
|
104
|
+
{}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.config=(config)
|
109
|
+
@config = config
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.path
|
113
|
+
@path ||= config[:theme_id] ? "/admin/themes/#{config[:theme_id]}/assets.json" : "/admin/assets.json"
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.ignore_files
|
117
|
+
(config[:ignore_files] || []).compact.map { |r| Regexp.new(r) }
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.whitelist_files
|
121
|
+
(config[:whitelist_files] || []).compact
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.is_binary_data?(string)
|
125
|
+
if string.respond_to?(:encoding)
|
126
|
+
string.encoding == "US-ASCII"
|
127
|
+
else
|
128
|
+
( string.count( "^ -~", "^\r\n" ).fdiv(string.size) > 0.3 || string.index( "\x00" ) ) unless string.empty?
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.check_config
|
133
|
+
shopify.get(path).code == 200
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
def self.shopify
|
138
|
+
basic_auth config[:api_key], config[:password]
|
139
|
+
# TODO: switch back to https
|
140
|
+
# base_uri "https://#{config[:store]}"
|
141
|
+
base_uri "http://#{config[:store]}"
|
142
|
+
ShopifyTheme
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.watch_until_processing_complete(theme)
|
146
|
+
count = 0
|
147
|
+
while true do
|
148
|
+
Kernel.sleep(count)
|
149
|
+
response = shopify.get("/admin/themes/#{theme['id']}.json")
|
150
|
+
theme = JSON.parse(response.body)['theme']
|
151
|
+
return theme if theme['previewable']
|
152
|
+
count += 5
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,304 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'yaml'
|
3
|
+
YAML::ENGINE.yamler = 'syck' if defined? Syck
|
4
|
+
require 'abbrev'
|
5
|
+
require 'base64'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'json'
|
8
|
+
require 'filewatcher'
|
9
|
+
require 'launchy'
|
10
|
+
require 'mimemagic'
|
11
|
+
|
12
|
+
module ShopifyTheme
|
13
|
+
EXTENSIONS = [
|
14
|
+
{mimetype: 'application/x-liquid', extensions: %w(liquid), parents: 'text/plain'},
|
15
|
+
{mimetype: 'application/json', extensions: %w(json), parents: 'text/plain'},
|
16
|
+
{mimetype: 'application/js', extensions: %w(map), parents: 'text/plain'},
|
17
|
+
{mimetype: 'application/vnd.ms-fontobject', extensions: %w(eot)},
|
18
|
+
{mimetype: 'image/svg+xml', extensions: %w(svg svgz)}
|
19
|
+
]
|
20
|
+
|
21
|
+
def self.configureMimeMagic
|
22
|
+
ShopifyTheme::EXTENSIONS.each do |extension|
|
23
|
+
MimeMagic.add(extension.delete(:mimetype), extension)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Cli < Thor
|
28
|
+
include Thor::Actions
|
29
|
+
|
30
|
+
IGNORE = %w(config.yml)
|
31
|
+
DEFAULT_WHITELIST = %w(layout/ assets/ config/ snippets/ templates/ locales/)
|
32
|
+
TIMEFORMAT = "%H:%M:%S"
|
33
|
+
|
34
|
+
tasks.keys.abbrev.each do |shortcut, command|
|
35
|
+
map shortcut => command.to_sym
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "check", "check configuration"
|
39
|
+
def check
|
40
|
+
if ShopifyTheme.check_config
|
41
|
+
say("Configuration [OK]", :green)
|
42
|
+
else
|
43
|
+
say("Configuration [FAIL]", :red)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "configure API_KEY PASSWORD STORE THEME_ID", "generate a config file for the store to connect to"
|
48
|
+
def configure(api_key=nil, password=nil, store=nil, theme_id=nil)
|
49
|
+
config = {:api_key => api_key, :password => password, :store => store, :theme_id => theme_id}
|
50
|
+
create_file('config.yml', config.to_yaml)
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "bootstrap API_KEY PASSWORD STORE THEME_NAME", "bootstrap with Timber to shop and configure local directory. Include master if you'd like to use the latest build for the theme"
|
54
|
+
method_option :master, :type => :boolean, :default => false
|
55
|
+
def bootstrap(api_key=nil, password=nil, store=nil, theme_name=nil, master=nil)
|
56
|
+
ShopifyTheme.config = {:api_key => api_key, :password => password, :store => store}
|
57
|
+
|
58
|
+
theme_name ||= 'Timber'
|
59
|
+
say("Registering #{theme_name} theme on #{store}", :green)
|
60
|
+
theme = ShopifyTheme.upload_timber(theme_name, master || false)
|
61
|
+
|
62
|
+
say("Creating directory named #{theme_name}", :green)
|
63
|
+
empty_directory(theme_name)
|
64
|
+
|
65
|
+
say("Saving configuration to #{theme_name}", :green)
|
66
|
+
ShopifyTheme.config.merge!(theme_id: theme['id'])
|
67
|
+
create_file("#{theme_name}/config.yml", ShopifyTheme.config.to_yaml)
|
68
|
+
|
69
|
+
say("Downloading #{theme_name} assets from Shopify")
|
70
|
+
Dir.chdir(theme_name)
|
71
|
+
download()
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "download FILE", "download the shops current theme assets"
|
75
|
+
method_option :quiet, :type => :boolean, :default => false
|
76
|
+
method_option :exclude
|
77
|
+
def download(*keys)
|
78
|
+
assets = keys.empty? ? ShopifyTheme.asset_list : keys
|
79
|
+
|
80
|
+
if options['exclude']
|
81
|
+
assets = assets.delete_if { |asset| asset =~ Regexp.new(options['exclude']) }
|
82
|
+
end
|
83
|
+
|
84
|
+
assets.each do |asset|
|
85
|
+
download_asset(asset)
|
86
|
+
say("#{ShopifyTheme.api_usage} Downloaded: #{asset}", :green) unless options['quiet']
|
87
|
+
end
|
88
|
+
say("Done.", :green) unless options['quiet']
|
89
|
+
end
|
90
|
+
|
91
|
+
desc "open", "open the store in your browser"
|
92
|
+
def open(*keys)
|
93
|
+
if Launchy.open shop_theme_url
|
94
|
+
say("Done.", :green)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
desc "upload FILE", "upload all theme assets to shop"
|
99
|
+
method_option :quiet, :type => :boolean, :default => false
|
100
|
+
def upload(*keys)
|
101
|
+
assets = keys.empty? ? local_assets_list : keys
|
102
|
+
assets.each do |asset|
|
103
|
+
send_asset(asset, options['quiet'])
|
104
|
+
end
|
105
|
+
say("Done.", :green) unless options['quiet']
|
106
|
+
end
|
107
|
+
|
108
|
+
desc "replace FILE", "completely replace shop theme assets with local theme assets"
|
109
|
+
method_option :quiet, :type => :boolean, :default => false
|
110
|
+
def replace(*keys)
|
111
|
+
say("Are you sure you want to completely replace your shop theme assets? This is not undoable.", :yellow)
|
112
|
+
if ask("Continue? (Y/N): ") == "Y"
|
113
|
+
# only delete files on remote that are not present locally
|
114
|
+
# files present on remote and present locally get overridden anyway
|
115
|
+
remote_assets = keys.empty? ? (ShopifyTheme.asset_list - local_assets_list) : keys
|
116
|
+
remote_assets.each do |asset|
|
117
|
+
delete_asset(asset, options['quiet']) unless ShopifyTheme.ignore_files.any? { |regex| regex =~ asset }
|
118
|
+
end
|
119
|
+
local_assets = keys.empty? ? local_assets_list : keys
|
120
|
+
local_assets.each do |asset|
|
121
|
+
send_asset(asset, options['quiet'])
|
122
|
+
end
|
123
|
+
say("Done.", :green) unless options['quiet']
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
desc "remove FILE", "remove theme asset"
|
128
|
+
method_option :quiet, :type => :boolean, :default => false
|
129
|
+
def remove(*keys)
|
130
|
+
keys.each do |key|
|
131
|
+
delete_asset(key, options['quiet'])
|
132
|
+
end
|
133
|
+
say("Done.", :green) unless options['quiet']
|
134
|
+
end
|
135
|
+
|
136
|
+
desc "watch", "upload and delete individual theme assets as they change, use the --keep_files flag to disable remote file deletion"
|
137
|
+
method_option :quiet, :type => :boolean, :default => false
|
138
|
+
method_option :keep_files, :type => :boolean, :default => false
|
139
|
+
def watch
|
140
|
+
puts "Watching current folder: #{Dir.pwd}"
|
141
|
+
watcher do |filename, event|
|
142
|
+
filename = filename.gsub("#{Dir.pwd}/", '')
|
143
|
+
|
144
|
+
next unless local_assets_list.include?(filename)
|
145
|
+
action = if [:changed, :new].include?(event)
|
146
|
+
:send_asset
|
147
|
+
elsif event == :delete
|
148
|
+
:delete_asset
|
149
|
+
else
|
150
|
+
raise NotImplementedError, "Unknown event -- #{event} -- #{filename}"
|
151
|
+
end
|
152
|
+
|
153
|
+
send(action, filename, options['quiet'])
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
desc "systeminfo", "print out system information and actively loaded libraries for aiding in submitting bug reports"
|
158
|
+
def systeminfo
|
159
|
+
ruby_version = "#{RUBY_VERSION}"
|
160
|
+
ruby_version += "-p#{RUBY_PATCHLEVEL}" if RUBY_PATCHLEVEL
|
161
|
+
puts "Ruby: v#{ruby_version}"
|
162
|
+
puts "Operating System: #{RUBY_PLATFORM}"
|
163
|
+
%w(Thor Listen HTTParty Launchy).each do |lib|
|
164
|
+
require "#{lib.downcase}/version"
|
165
|
+
puts "#{lib}: v" + Kernel.const_get("#{lib}::VERSION")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
protected
|
170
|
+
|
171
|
+
def config
|
172
|
+
@config ||= YAML.load_file 'config.yml'
|
173
|
+
end
|
174
|
+
|
175
|
+
def shop_theme_url
|
176
|
+
url = config[:store]
|
177
|
+
url += "?preview_theme_id=#{config[:theme_id]}" if config[:theme_id] && config[:theme_id].to_i > 0
|
178
|
+
url
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def watcher
|
184
|
+
FileWatcher.new(Dir.pwd).watch() do |filename, event|
|
185
|
+
yield(filename, event)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def local_assets_list
|
190
|
+
local_files.reject do |p|
|
191
|
+
@permitted_files ||= (DEFAULT_WHITELIST | ShopifyTheme.whitelist_files).map{|pattern| Regexp.new(pattern)}
|
192
|
+
@permitted_files.none? { |regex| regex =~ p } || ShopifyTheme.ignore_files.any? { |regex| regex =~ p }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def local_files
|
197
|
+
Dir.glob(File.join('**', '*')).reject do |f|
|
198
|
+
File.directory?(f)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def download_asset(key)
|
203
|
+
return unless valid?(key)
|
204
|
+
notify_and_sleep("Approaching limit of API permits. Naptime until more permits become available!") if ShopifyTheme.needs_sleep?
|
205
|
+
asset = ShopifyTheme.get_asset(key)
|
206
|
+
if asset['value']
|
207
|
+
# For CRLF line endings
|
208
|
+
content = asset['value'].gsub("\r", "")
|
209
|
+
format = "w"
|
210
|
+
elsif asset['attachment']
|
211
|
+
content = Base64.decode64(asset['attachment'])
|
212
|
+
format = "w+b"
|
213
|
+
end
|
214
|
+
|
215
|
+
FileUtils.mkdir_p(File.dirname(key))
|
216
|
+
File.open(key, format) {|f| f.write content} if content
|
217
|
+
end
|
218
|
+
|
219
|
+
def send_asset(asset, quiet=false)
|
220
|
+
return unless valid?(asset)
|
221
|
+
data = {:key => asset}
|
222
|
+
content = File.read(asset)
|
223
|
+
if binary_file?(asset) || ShopifyTheme.is_binary_data?(content)
|
224
|
+
content = File.open(asset, "rb") { |io| io.read }
|
225
|
+
data.merge!(:attachment => Base64.encode64(content))
|
226
|
+
else
|
227
|
+
data.merge!(:value => content)
|
228
|
+
end
|
229
|
+
|
230
|
+
response = show_during("[#{timestamp}] Uploading: #{asset}", quiet) do
|
231
|
+
ShopifyTheme.send_asset(data)
|
232
|
+
end
|
233
|
+
if response.success?
|
234
|
+
say("[#{timestamp}] Uploaded: #{asset}", :green) unless quiet
|
235
|
+
else
|
236
|
+
report_error(Time.now, "Could not upload #{asset}", response)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def delete_asset(key, quiet=false)
|
241
|
+
return unless valid?(key)
|
242
|
+
response = show_during("[#{timestamp}] Removing: #{key}", quiet) do
|
243
|
+
ShopifyTheme.delete_asset(key)
|
244
|
+
end
|
245
|
+
if response.success?
|
246
|
+
say("[#{timestamp}] Removed: #{key}", :green) unless quiet
|
247
|
+
else
|
248
|
+
report_error(Time.now, "Could not remove #{key}", response)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def notify_and_sleep(message)
|
253
|
+
say(message, :red)
|
254
|
+
ShopifyTheme.sleep
|
255
|
+
end
|
256
|
+
|
257
|
+
def valid?(key)
|
258
|
+
return true if DEFAULT_WHITELIST.include?(key.split('/').first + "/")
|
259
|
+
say("'#{key}' is not in a valid file for theme uploads", :yellow)
|
260
|
+
say("Files need to be in one of the following subdirectories: #{DEFAULT_WHITELIST.join(' ')}", :yellow)
|
261
|
+
false
|
262
|
+
end
|
263
|
+
|
264
|
+
def binary_file?(path)
|
265
|
+
mime = MimeMagic.by_path(path)
|
266
|
+
say("'#{path}' is an unknown file-type, uploading asset as binary", :yellow) if mime.nil? && ENV['TEST'] != 'true'
|
267
|
+
mime.nil? || !mime.text?
|
268
|
+
end
|
269
|
+
|
270
|
+
def report_error(time, message, response)
|
271
|
+
say("[#{timestamp(time)}] Error: #{message}", :red)
|
272
|
+
say("Error Details: #{errors_from_response(response)}", :yellow)
|
273
|
+
end
|
274
|
+
|
275
|
+
def errors_from_response(response)
|
276
|
+
object = {status: response.headers['status'], request_id: response.headers['x-request-id']}
|
277
|
+
|
278
|
+
errors = response.parsed_response ? response.parsed_response["errors"] : response.body
|
279
|
+
|
280
|
+
object[:errors] = case errors
|
281
|
+
when NilClass
|
282
|
+
''
|
283
|
+
when String
|
284
|
+
errors.strip
|
285
|
+
else
|
286
|
+
errors.values.join(", ")
|
287
|
+
end
|
288
|
+
object.delete(:errors) if object[:errors].length <= 0
|
289
|
+
object
|
290
|
+
end
|
291
|
+
|
292
|
+
def show_during(message = '', quiet = false, &block)
|
293
|
+
print(message) unless quiet
|
294
|
+
result = yield
|
295
|
+
print("\r#{' ' * message.length}\r") unless quiet
|
296
|
+
result
|
297
|
+
end
|
298
|
+
|
299
|
+
def timestamp(time = Time.now)
|
300
|
+
time.strftime(TIMEFORMAT)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
ShopifyTheme.configureMimeMagic
|
data/shipit.rubygems.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# using the default shipit config
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "shopify_theme/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "dev_shopify_theme"
|
7
|
+
s.version = ShopifyTheme::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["John Duff", "Lawson Kurtz"]
|
10
|
+
s.email = ["john.duff@shopify.com", "lawson.kurtz@viget.com"]
|
11
|
+
s.homepage = "https://github.com/ltk/shopify_theme"
|
12
|
+
s.summary = %q{*Insecure* command line tool for developing themes (Dev env only!)}
|
13
|
+
s.description = %q{*Insecure* command line tool to help with developing Shopify themes. Provides simple commands to download, upload and delete files from a theme. Also includes the watch command to watch a directory and upload files as they change. (Dev env only!)}
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.add_dependency('thor', '>= 0.14.4')
|
17
|
+
s.add_dependency('httparty', '~> 0.13.0')
|
18
|
+
s.add_dependency('json', '~> 1.8.0')
|
19
|
+
s.add_dependency('mimemagic')
|
20
|
+
s.add_dependency('filewatcher')
|
21
|
+
s.add_dependency('launchy')
|
22
|
+
|
23
|
+
s.add_development_dependency 'rake'
|
24
|
+
s.add_development_dependency 'minitest', '>= 5.0.0'
|
25
|
+
s.add_development_dependency 'pry'
|
26
|
+
s.add_development_dependency 'pry-debugger'
|
27
|
+
|
28
|
+
s.files = `git ls-files`.split("\n")
|
29
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
30
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
31
|
+
s.require_paths = ['lib']
|
32
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'shopify_theme'
|
3
|
+
require 'shopify_theme/cli'
|
4
|
+
|
5
|
+
module ShopifyTheme
|
6
|
+
describe "Cli" do
|
7
|
+
|
8
|
+
class CliDouble < Cli
|
9
|
+
attr_writer :local_files, :mock_config
|
10
|
+
|
11
|
+
desc "",""
|
12
|
+
def config
|
13
|
+
@mock_config || super
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "",""
|
17
|
+
def shop_theme_url
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "",""
|
22
|
+
def binary_file?(file)
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "", ""
|
27
|
+
def local_files
|
28
|
+
@local_files
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
before do
|
33
|
+
@cli = CliDouble.new
|
34
|
+
ShopifyTheme.config = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should remove assets that are not a part of the white list" do
|
38
|
+
@cli.local_files = ['assets/image.png', 'config.yml', 'layout/theme.liquid', 'locales/en.default.json']
|
39
|
+
local_assets_list = @cli.send(:local_assets_list)
|
40
|
+
assert_equal 3, local_assets_list.length
|
41
|
+
assert_equal false, local_assets_list.include?('config.yml')
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should remove assets that are part of the ignore list" do
|
45
|
+
ShopifyTheme.config = {ignore_files: ['config/settings.html']}
|
46
|
+
@cli.local_files = ['assets/image.png', 'layout/theme.liquid', 'config/settings.html']
|
47
|
+
local_assets_list = @cli.send(:local_assets_list)
|
48
|
+
assert_equal 2, local_assets_list.length
|
49
|
+
assert_equal false, local_assets_list.include?('config/settings.html')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should generate the shop path URL to the query parameter preview_theme_id if the id is present" do
|
53
|
+
@cli.mock_config = {store: 'somethingfancy.myshopify.com', theme_id: 12345}
|
54
|
+
assert_equal "somethingfancy.myshopify.com?preview_theme_id=12345", @cli.shop_theme_url
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should generate the shop path URL withouth the preview_theme_id if the id is not present" do
|
58
|
+
@cli.mock_config = {store: 'somethingfancy.myshopify.com'}
|
59
|
+
assert_equal "somethingfancy.myshopify.com", @cli.shop_theme_url
|
60
|
+
|
61
|
+
@cli.mock_config = {store: 'somethingfancy.myshopify.com', theme_id: ''}
|
62
|
+
assert_equal "somethingfancy.myshopify.com", @cli.shop_theme_url
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should report binary files as such" do
|
66
|
+
extensions = %w(png gif jpg jpeg eot svg ttf woff otf swf ico pdf)
|
67
|
+
extensions.each do |ext|
|
68
|
+
assert @cli.binary_file?("hello.#{ext}"), "#{ext.upcase}s are binary files"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should report unknown files as binary files" do
|
73
|
+
assert @cli.binary_file?('omg.wut'), "Unknown filetypes are assumed to be binary"
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not report text based files as binary" do
|
77
|
+
refute @cli.binary_file?('theme.liquid'), "liquid files are not binary"
|
78
|
+
refute @cli.binary_file?('style.sass.liquid'), "sass.liquid files are not binary"
|
79
|
+
refute @cli.binary_file?('style.css'), 'CSS files are not binary'
|
80
|
+
refute @cli.binary_file?('application.js'), 'Javascript files are not binary'
|
81
|
+
refute @cli.binary_file?('settings_data.json'), 'JSON files are not binary'
|
82
|
+
refute @cli.binary_file?('applicaton.js.map'), 'Javascript Map files are not binary'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
metadata
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dev_shopify_theme
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.21
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Duff
|
8
|
+
- Lawson Kurtz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-01-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.14.4
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - '>='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.14.4
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: httparty
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.13.0
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 0.13.0
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: json
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ~>
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 1.8.0
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 1.8.0
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: mimemagic
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: filewatcher
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: launchy
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: rake
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: minitest
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 5.0.0
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 5.0.0
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: pry
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - '>='
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
- !ruby/object:Gem::Dependency
|
141
|
+
name: pry-debugger
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - '>='
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
description: '*Insecure* command line tool to help with developing Shopify themes.
|
155
|
+
Provides simple commands to download, upload and delete files from a theme. Also
|
156
|
+
includes the watch command to watch a directory and upload files as they change.
|
157
|
+
(Dev env only!)'
|
158
|
+
email:
|
159
|
+
- john.duff@shopify.com
|
160
|
+
- lawson.kurtz@viget.com
|
161
|
+
executables:
|
162
|
+
- dev-theme
|
163
|
+
extensions: []
|
164
|
+
extra_rdoc_files: []
|
165
|
+
files:
|
166
|
+
- .gitignore
|
167
|
+
- .travis.yml
|
168
|
+
- CHANGELOG.md
|
169
|
+
- CONTRIBUTING
|
170
|
+
- Gemfile
|
171
|
+
- MIT-LICENSE
|
172
|
+
- README.md
|
173
|
+
- Rakefile
|
174
|
+
- bin/dev-theme
|
175
|
+
- doc/how_to_find_theme_id.png
|
176
|
+
- lib/shopify_theme.rb
|
177
|
+
- lib/shopify_theme/cli.rb
|
178
|
+
- lib/shopify_theme/version.rb
|
179
|
+
- shipit.rubygems.yml
|
180
|
+
- shopify_theme.gemspec
|
181
|
+
- spec/spec_helper.rb
|
182
|
+
- spec/unit/cli_spec.rb
|
183
|
+
homepage: https://github.com/ltk/shopify_theme
|
184
|
+
licenses:
|
185
|
+
- MIT
|
186
|
+
metadata: {}
|
187
|
+
post_install_message:
|
188
|
+
rdoc_options: []
|
189
|
+
require_paths:
|
190
|
+
- lib
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
193
|
+
- - '>='
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
196
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
197
|
+
requirements:
|
198
|
+
- - '>='
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '0'
|
201
|
+
requirements: []
|
202
|
+
rubyforge_project:
|
203
|
+
rubygems_version: 2.2.1
|
204
|
+
signing_key:
|
205
|
+
specification_version: 4
|
206
|
+
summary: '*Insecure* command line tool for developing themes (Dev env only!)'
|
207
|
+
test_files:
|
208
|
+
- spec/spec_helper.rb
|
209
|
+
- spec/unit/cli_spec.rb
|
210
|
+
has_rdoc:
|