shopify-cli 1.11.0 → 2.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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -1
- data/.github/CONTRIBUTING.md +7 -7
- data/.github/DESIGN.md +3 -3
- data/.github/PULL_REQUEST_TEMPLATE.md +1 -1
- data/.github/workflows/build.yml +1 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +3 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +48 -20
- data/Gemfile +4 -0
- data/Gemfile.lock +32 -0
- data/LICENSE +4 -1
- data/README.md +92 -26
- data/RELEASING.md +29 -7
- data/Rakefile +2 -2
- data/SECURITY.md +1 -1
- data/bin/load_shopify.rb +1 -1
- data/bin/shopify +3 -3
- data/dev.yml +1 -1
- data/docs/app/node/index.md +1 -1
- data/docs/app/rails/index.md +1 -1
- data/docs/core/index.md +1 -1
- data/docs/getting-started/index.md +1 -1
- data/docs/getting-started/install/index.md +1 -1
- data/docs/getting-started/migrate/index.md +1 -1
- data/docs/getting-started/uninstall/index.md +1 -1
- data/docs/getting-started/upgrade/index.md +1 -1
- data/docs/help/start-app/index.md +1 -1
- data/docs/index.md +1 -1
- data/ext/shopify-cli/extconf.rb +17 -5
- data/install.sh +1 -1
- data/lib/docgen/index_template.md.erb +2 -2
- data/lib/graphql/all_orgs_with_extensions.graphql +37 -0
- data/lib/graphql/find_organization.graphql +2 -1
- data/lib/project_types/extension/cli.rb +18 -15
- data/lib/project_types/extension/commands/build.rb +4 -5
- data/lib/project_types/extension/commands/connect.rb +35 -0
- data/lib/project_types/extension/commands/create.rb +12 -16
- data/lib/project_types/extension/commands/extension_command.rb +2 -2
- data/lib/project_types/extension/commands/info.rb +86 -0
- data/lib/project_types/extension/commands/push.rb +8 -7
- data/lib/project_types/extension/commands/register.rb +4 -5
- data/lib/project_types/extension/commands/serve.rb +5 -8
- data/lib/project_types/extension/commands/tunnel.rb +3 -1
- data/lib/project_types/extension/errors.rb +9 -0
- data/lib/project_types/extension/extension_project.rb +5 -0
- data/lib/project_types/extension/features/argo.rb +6 -6
- data/lib/project_types/extension/features/argo_runtime.rb +22 -38
- data/lib/project_types/extension/features/argo_serve.rb +25 -20
- data/lib/project_types/extension/forms/connect.rb +42 -0
- data/lib/project_types/extension/forms/questions/ask_name.rb +14 -6
- data/lib/project_types/extension/forms/questions/ask_registration.rb +51 -0
- data/lib/project_types/extension/messages/messages.rb +75 -11
- data/lib/project_types/extension/models/specification.rb +1 -0
- data/lib/project_types/extension/models/specification_handlers/{checkout_argo_extension.rb → checkout_ui_extension.rb} +3 -1
- data/lib/project_types/extension/models/specification_handlers/default.rb +21 -6
- data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +86 -0
- data/lib/project_types/extension/models/specifications.rb +1 -0
- data/lib/project_types/extension/tasks/configure_features.rb +6 -7
- data/lib/project_types/extension/tasks/configure_options.rb +20 -0
- data/lib/project_types/extension/tasks/get_extensions.rb +32 -0
- data/lib/project_types/node/cli.rb +9 -21
- data/lib/project_types/node/commands/connect.rb +8 -2
- data/lib/project_types/node/commands/create.rb +9 -5
- data/lib/project_types/node/commands/deploy.rb +15 -5
- data/lib/project_types/node/commands/deploy/heroku.rb +29 -29
- data/lib/project_types/node/commands/generate.rb +4 -2
- data/lib/project_types/node/commands/open.rb +4 -2
- data/lib/project_types/node/commands/serve.rb +3 -2
- data/lib/project_types/node/commands/tunnel.rb +4 -2
- data/lib/project_types/node/messages/messages.rb +46 -89
- data/lib/project_types/rails/cli.rb +9 -21
- data/lib/project_types/rails/commands/connect.rb +8 -2
- data/lib/project_types/rails/commands/create.rb +10 -6
- data/lib/project_types/rails/commands/deploy.rb +15 -5
- data/lib/project_types/rails/commands/deploy/heroku.rb +84 -82
- data/lib/project_types/rails/commands/generate.rb +15 -5
- data/lib/project_types/rails/commands/generate/webhook.rb +28 -26
- data/lib/project_types/rails/commands/open.rb +4 -2
- data/lib/project_types/rails/commands/serve.rb +3 -2
- data/lib/project_types/rails/commands/tunnel.rb +4 -2
- data/lib/project_types/rails/messages/messages.rb +54 -101
- data/lib/project_types/script/cli.rb +18 -20
- data/lib/project_types/script/commands/create.rb +3 -1
- data/lib/project_types/script/commands/push.rb +12 -5
- data/lib/project_types/script/config/extension_points.yml +0 -3
- data/lib/project_types/script/graphql/app_script_update_or_create.graphql +9 -3
- data/lib/project_types/script/layers/application/create_script.rb +6 -5
- data/lib/project_types/script/layers/application/push_script.rb +2 -1
- data/lib/project_types/script/layers/domain/errors.rb +6 -11
- data/lib/project_types/script/layers/domain/push_package.rb +4 -8
- data/lib/project_types/script/layers/domain/script_json.rb +32 -0
- data/lib/project_types/script/layers/domain/script_project.rb +1 -1
- data/lib/project_types/script/layers/infrastructure/errors.rb +14 -18
- data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +105 -0
- data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +103 -0
- data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +26 -0
- data/lib/project_types/script/layers/infrastructure/languages/rust_project_creator.rb +73 -0
- data/lib/project_types/script/layers/infrastructure/languages/rust_task_runner.rb +60 -0
- data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +21 -0
- data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +2 -4
- data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +45 -34
- data/lib/project_types/script/layers/infrastructure/script_service.rb +20 -14
- data/lib/project_types/script/messages/messages.rb +66 -55
- data/lib/project_types/script/tasks/ensure_env.rb +22 -1
- data/lib/project_types/script/ui/error_handler.rb +32 -32
- data/lib/project_types/theme/cli.rb +15 -27
- data/lib/project_types/theme/commands/check.rb +33 -0
- data/lib/project_types/theme/commands/delete.rb +64 -0
- data/lib/project_types/theme/commands/language_server.rb +16 -0
- data/lib/project_types/theme/commands/package.rb +55 -0
- data/lib/project_types/theme/commands/publish.rb +43 -0
- data/lib/project_types/theme/commands/pull.rb +51 -0
- data/lib/project_types/theme/commands/push.rb +58 -32
- data/lib/project_types/theme/commands/serve.rb +7 -17
- data/lib/project_types/theme/forms/confirm_store.rb +15 -0
- data/lib/project_types/theme/forms/select.rb +59 -0
- data/lib/project_types/theme/messages/messages.rb +110 -106
- data/lib/project_types/theme/ui/sync_progress_bar.rb +20 -0
- data/lib/shopify-cli/admin_api.rb +53 -35
- data/lib/shopify-cli/admin_api/populate_resource_command.rb +6 -14
- data/lib/shopify-cli/admin_api/schema.rb +1 -10
- data/lib/shopify-cli/api.rb +29 -14
- data/lib/shopify-cli/command.rb +15 -3
- data/lib/shopify-cli/commands.rb +7 -2
- data/lib/shopify-cli/commands/help.rb +2 -29
- data/lib/shopify-cli/commands/login.rb +95 -0
- data/lib/shopify-cli/commands/logout.rb +24 -8
- data/lib/shopify-cli/commands/populate.rb +23 -0
- data/lib/{project_types/node → shopify-cli}/commands/populate/customer.rb +2 -8
- data/lib/{project_types/node → shopify-cli}/commands/populate/draft_order.rb +2 -2
- data/lib/{project_types/node → shopify-cli}/commands/populate/product.rb +2 -8
- data/lib/shopify-cli/commands/store.rb +15 -0
- data/lib/shopify-cli/commands/switch.rb +39 -0
- data/lib/shopify-cli/commands/system.rb +12 -0
- data/lib/shopify-cli/commands/whoami.rb +28 -0
- data/lib/shopify-cli/connect.rb +32 -0
- data/lib/shopify-cli/context.rb +65 -4
- data/lib/shopify-cli/core/entry_point.rb +3 -22
- data/lib/shopify-cli/db.rb +4 -4
- data/lib/shopify-cli/http_request.rb +10 -0
- data/lib/shopify-cli/identity_auth.rb +282 -0
- data/lib/shopify-cli/{oauth → identity_auth}/servlet.rb +11 -12
- data/lib/shopify-cli/messages/messages.rb +133 -39
- data/lib/shopify-cli/partners_api.rb +21 -41
- data/lib/shopify-cli/partners_api/organizations.rb +8 -0
- data/lib/shopify-cli/project_commands.rb +16 -0
- data/lib/shopify-cli/project_type.rb +0 -31
- data/lib/shopify-cli/resources/env_file.rb +1 -1
- data/lib/shopify-cli/shopifolk.rb +8 -11
- data/lib/shopify-cli/sub_command.rb +1 -0
- data/lib/shopify-cli/tasks.rb +3 -0
- data/lib/shopify-cli/tasks/confirm_store.rb +18 -0
- data/lib/shopify-cli/tasks/create_api_client.rb +2 -2
- data/lib/shopify-cli/tasks/ensure_authenticated.rb +13 -0
- data/lib/shopify-cli/tasks/ensure_loopback_url.rb +1 -1
- data/lib/shopify-cli/tasks/ensure_project_type.rb +12 -0
- data/lib/shopify-cli/tasks/select_org_and_shop.rb +0 -3
- data/lib/shopify-cli/theme/dev_server.rb +98 -0
- data/lib/shopify-cli/theme/dev_server/certificate_manager.rb +79 -0
- data/lib/shopify-cli/theme/dev_server/header_hash.rb +94 -0
- data/lib/shopify-cli/theme/dev_server/hot-reload.js +93 -0
- data/lib/shopify-cli/theme/dev_server/hot_reload.rb +76 -0
- data/lib/shopify-cli/theme/dev_server/local_assets.rb +87 -0
- data/lib/shopify-cli/theme/dev_server/proxy.rb +205 -0
- data/lib/shopify-cli/theme/dev_server/sse.rb +75 -0
- data/lib/shopify-cli/theme/dev_server/watcher.rb +59 -0
- data/lib/shopify-cli/theme/dev_server/web_server.rb +140 -0
- data/lib/shopify-cli/theme/development_theme.rb +69 -0
- data/lib/shopify-cli/theme/file.rb +112 -0
- data/lib/shopify-cli/theme/ignore_filter.rb +109 -0
- data/lib/shopify-cli/theme/mime_type.rb +34 -0
- data/lib/shopify-cli/theme/syncer.rb +328 -0
- data/lib/shopify-cli/theme/theme.rb +204 -0
- data/lib/shopify-cli/version.rb +1 -1
- data/lib/shopify_cli.rb +18 -11
- data/shopify-cli.gemspec +12 -5
- data/shopify.fish +1 -1
- data/shopify.sh +1 -1
- metadata +95 -41
- data/.github/workflows/release.yml +0 -61
- data/lib/project_types/extension/features/argo_serve_options.rb +0 -40
- data/lib/project_types/node/commands/populate.rb +0 -23
- data/lib/project_types/rails/commands/populate.rb +0 -23
- data/lib/project_types/rails/commands/populate/customer.rb +0 -31
- data/lib/project_types/rails/commands/populate/draft_order.rb +0 -28
- data/lib/project_types/rails/commands/populate/product.rb +0 -30
- data/lib/project_types/script/layers/domain/config_ui.rb +0 -16
- data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +0 -95
- data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +0 -101
- data/lib/project_types/script/layers/infrastructure/project_creator.rb +0 -24
- data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +0 -71
- data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +0 -58
- data/lib/project_types/script/layers/infrastructure/task_runner.rb +0 -19
- data/lib/project_types/theme/commands/connect.rb +0 -54
- data/lib/project_types/theme/commands/create.rb +0 -48
- data/lib/project_types/theme/commands/deploy.rb +0 -38
- data/lib/project_types/theme/commands/generate.rb +0 -20
- data/lib/project_types/theme/commands/generate/env.rb +0 -79
- data/lib/project_types/theme/forms/connect.rb +0 -34
- data/lib/project_types/theme/forms/create.rb +0 -22
- data/lib/project_types/theme/tasks/ensure_themekit_installed.rb +0 -78
- data/lib/project_types/theme/themekit.rb +0 -113
- data/lib/shopify-cli/commands/connect.rb +0 -64
- data/lib/shopify-cli/commands/create.rb +0 -50
- data/lib/shopify-cli/oauth.rb +0 -198
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "thread"
|
|
3
|
+
|
|
4
|
+
module ShopifyCli
|
|
5
|
+
module Theme
|
|
6
|
+
module DevServer
|
|
7
|
+
# Server-Sent events implementation for Rack.
|
|
8
|
+
# Based on https://gist.github.com/raggi/ff7971991297e5c8a1ce
|
|
9
|
+
class SSE
|
|
10
|
+
class Event < Struct.new(:data)
|
|
11
|
+
def to_s
|
|
12
|
+
"data: #{data}\n" \
|
|
13
|
+
"\n\n"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Stream
|
|
18
|
+
def initialize(streams)
|
|
19
|
+
@streams = streams
|
|
20
|
+
@queue = Queue.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def <<(event)
|
|
24
|
+
raise TypeError, "expected SSE::Event" unless event.is_a?(Event)
|
|
25
|
+
@queue << event
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def each
|
|
29
|
+
while (event = @queue.pop)
|
|
30
|
+
yield event.to_s
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def close
|
|
35
|
+
@streams.remove(self)
|
|
36
|
+
@queue << nil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Streams
|
|
41
|
+
def initialize
|
|
42
|
+
@list = []
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def size
|
|
46
|
+
@list.size
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def new
|
|
50
|
+
stream = Stream.new(self)
|
|
51
|
+
@list << stream
|
|
52
|
+
stream
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def remove(stream)
|
|
56
|
+
raise TypeError, "expected SSE::Stream" unless stream.is_a?(Stream)
|
|
57
|
+
@list.delete(stream)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def broadcast(data)
|
|
61
|
+
raise TypeError, "expected String" unless data.is_a?(String)
|
|
62
|
+
@list.each do |stream|
|
|
63
|
+
stream << Event.new(data)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def close
|
|
68
|
+
@list.each(&:close)
|
|
69
|
+
@list.clear
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "listen"
|
|
3
|
+
require "observer"
|
|
4
|
+
|
|
5
|
+
module ShopifyCli
|
|
6
|
+
module Theme
|
|
7
|
+
module DevServer
|
|
8
|
+
# Watches for file changes and publish events to the theme
|
|
9
|
+
class Watcher
|
|
10
|
+
include Observable
|
|
11
|
+
|
|
12
|
+
def initialize(ctx, theme:, syncer:, ignore_filter: nil)
|
|
13
|
+
@ctx = ctx
|
|
14
|
+
@theme = theme
|
|
15
|
+
@syncer = syncer
|
|
16
|
+
@ignore_filter = ignore_filter
|
|
17
|
+
@listener = Listen.to(@theme.root) do |modified, added, removed|
|
|
18
|
+
changed
|
|
19
|
+
notify_observers(modified, added, removed)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
add_observer(self, :upload_files_when_changed)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def start
|
|
26
|
+
@listener.start
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def stop
|
|
30
|
+
@listener.stop
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def upload_files_when_changed(modified, added, removed)
|
|
34
|
+
modified_theme_files = filter_theme_files(modified + added)
|
|
35
|
+
if modified_theme_files.any?
|
|
36
|
+
@syncer.enqueue_updates(modified_theme_files)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
removed_theme_files = filter_remote_files(removed)
|
|
40
|
+
if removed_theme_files.any?
|
|
41
|
+
@syncer.enqueue_deletes(removed_theme_files)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def filter_theme_files(files)
|
|
46
|
+
files
|
|
47
|
+
.select { |file| @theme.theme_file?(file) }
|
|
48
|
+
.reject { |file| @ignore_filter&.ignore?(file) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def filter_remote_files(files)
|
|
52
|
+
files
|
|
53
|
+
.select { |file| @syncer.remote_file?(file) }
|
|
54
|
+
.reject { |file| @ignore_filter&.ignore?(file) }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webrick"
|
|
4
|
+
require "stringio"
|
|
5
|
+
|
|
6
|
+
module ShopifyCli
|
|
7
|
+
module Theme
|
|
8
|
+
module DevServer
|
|
9
|
+
# WEBrick will sometimes cause a fatal deadlock error on shutdown.
|
|
10
|
+
# The error happens because `Thread#join` is called without a timeout argument.
|
|
11
|
+
# We monkey-patch WEBrick to call `Thread#join(timeout)` before the existing
|
|
12
|
+
# `Thread#join`.
|
|
13
|
+
module WEBrickGenericServerThreadJoinWithTimeout
|
|
14
|
+
# Hook into a method called right before the threads are shutdown.
|
|
15
|
+
def cleanup_listener
|
|
16
|
+
# Force a Thread#join with a timeout to prevent any deadlock error on stop
|
|
17
|
+
Thread.list.each do |thread|
|
|
18
|
+
next unless thread[:WEBrickThread]
|
|
19
|
+
thread.join(2)
|
|
20
|
+
# Prevent the `join` call without a timeout inside WEBrick.
|
|
21
|
+
thread[:WEBrickThread] = false
|
|
22
|
+
end
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Base on Rack::Handler::WEBrick
|
|
28
|
+
class WebServer < ::WEBrick::HTTPServlet::AbstractServlet
|
|
29
|
+
def self.run(app, **options)
|
|
30
|
+
environment = ENV["RACK_ENV"] || "development"
|
|
31
|
+
default_host = environment == "development" ? "localhost" : nil
|
|
32
|
+
|
|
33
|
+
if !options[:BindAddress] || options[:Host]
|
|
34
|
+
options[:BindAddress] = options.delete(:Host) || default_host
|
|
35
|
+
end
|
|
36
|
+
options[:Port] ||= 8080
|
|
37
|
+
if options[:SSLEnable]
|
|
38
|
+
require "webrick/https"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@server = ::WEBrick::HTTPServer.new(options)
|
|
42
|
+
@server.extend(WEBrickGenericServerThreadJoinWithTimeout)
|
|
43
|
+
@server.mount("/", WebServer, app)
|
|
44
|
+
yield @server if block_given?
|
|
45
|
+
@server.start
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.valid_options
|
|
49
|
+
environment = ENV["RACK_ENV"] || "development"
|
|
50
|
+
default_host = environment == "development" ? "localhost" : "0.0.0.0"
|
|
51
|
+
|
|
52
|
+
{
|
|
53
|
+
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
|
54
|
+
"Port=PORT" => "Port to listen on (default: 8080)",
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.shutdown
|
|
59
|
+
if @server
|
|
60
|
+
@server.shutdown
|
|
61
|
+
@server = nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def initialize(server, app)
|
|
66
|
+
super(server)
|
|
67
|
+
@app = app
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def service(req, res)
|
|
71
|
+
# res.rack = true
|
|
72
|
+
env = req.meta_vars
|
|
73
|
+
env.delete_if { |_k, v| v.nil? }
|
|
74
|
+
|
|
75
|
+
rack_input = StringIO.new(req.body.to_s)
|
|
76
|
+
rack_input.set_encoding(Encoding::BINARY)
|
|
77
|
+
|
|
78
|
+
env.update(
|
|
79
|
+
"rack.version" => [1, 3],
|
|
80
|
+
"rack.input" => rack_input,
|
|
81
|
+
"rack.errors" => $stderr,
|
|
82
|
+
"rack.multithread" => true,
|
|
83
|
+
"rack.multiprocess" => false,
|
|
84
|
+
"rack.run_once" => false,
|
|
85
|
+
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http",
|
|
86
|
+
"rack.hijack?" => true,
|
|
87
|
+
"rack.hijack" => lambda { raise NotImplementedError, "only partial hijack is supported." },
|
|
88
|
+
"rack.hijack_io" => nil
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
|
92
|
+
env["QUERY_STRING"] ||= ""
|
|
93
|
+
unless env["PATH_INFO"] == ""
|
|
94
|
+
path = req.request_uri.path
|
|
95
|
+
n = env["SCRIPT_NAME"].length
|
|
96
|
+
env["PATH_INFO"] = path[n, path.length - n]
|
|
97
|
+
end
|
|
98
|
+
env["REQUEST_PATH"] ||= [env["SCRIPT_NAME"], env["PATH_INFO"]].join
|
|
99
|
+
|
|
100
|
+
status, headers, body = @app.call(env)
|
|
101
|
+
|
|
102
|
+
res.status = status.to_i
|
|
103
|
+
io_lambda = nil
|
|
104
|
+
headers.each do |k, vs|
|
|
105
|
+
if k == "rack.hijack"
|
|
106
|
+
io_lambda = vs
|
|
107
|
+
elsif k == "webrick.chunked"
|
|
108
|
+
res.chunked = true
|
|
109
|
+
elsif k.downcase == "set-cookie"
|
|
110
|
+
res.cookies.concat(vs.split("\n"))
|
|
111
|
+
else
|
|
112
|
+
# Since WEBrick won't accept repeated headers,
|
|
113
|
+
# merge the values per RFC 1945 section 4.2.
|
|
114
|
+
res[k] = vs.split("\n").join(", ")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if io_lambda
|
|
119
|
+
rd, wr = IO.pipe
|
|
120
|
+
res.body = rd
|
|
121
|
+
res.chunked = true
|
|
122
|
+
io_lambda.call(wr)
|
|
123
|
+
body.close if body.respond_to?(:close)
|
|
124
|
+
elsif body.respond_to?(:to_path)
|
|
125
|
+
res.body = ::File.open(body.to_path, "rb")
|
|
126
|
+
body.close if body.respond_to?(:close)
|
|
127
|
+
else
|
|
128
|
+
res.body = lambda do |out|
|
|
129
|
+
out.set_encoding(Encoding::BINARY) if out.respond_to?(:set_encoding)
|
|
130
|
+
body.each do |part|
|
|
131
|
+
out.write(part)
|
|
132
|
+
end
|
|
133
|
+
body.close if body.respond_to?(:close)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "theme"
|
|
3
|
+
|
|
4
|
+
require "socket"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
|
|
7
|
+
module ShopifyCli
|
|
8
|
+
module Theme
|
|
9
|
+
class DevelopmentTheme < Theme
|
|
10
|
+
def id
|
|
11
|
+
ShopifyCli::DB.get(:development_theme_id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def name
|
|
15
|
+
ShopifyCli::DB.get(:development_theme_name) || generate_theme_name
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def role
|
|
19
|
+
"development"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ensure_exists!
|
|
23
|
+
if exists?
|
|
24
|
+
@ctx.debug("Using temporary development theme: ##{id} #{name}")
|
|
25
|
+
else
|
|
26
|
+
create
|
|
27
|
+
@ctx.debug("Created temporary development theme: #{@id}")
|
|
28
|
+
ShopifyCli::DB.set(development_theme_id: @id)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def exists?
|
|
33
|
+
return false unless id
|
|
34
|
+
|
|
35
|
+
ShopifyCli::AdminAPI.rest_request(
|
|
36
|
+
@ctx,
|
|
37
|
+
shop: shop,
|
|
38
|
+
path: "themes/#{id}.json",
|
|
39
|
+
api_version: "unstable",
|
|
40
|
+
)
|
|
41
|
+
rescue ShopifyCli::API::APIRequestNotFoundError
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def delete
|
|
46
|
+
super if exists?
|
|
47
|
+
ShopifyCli::DB.del(:development_theme_id) if ShopifyCli::DB.exists?(:development_theme_id)
|
|
48
|
+
ShopifyCli::DB.del(:development_theme_name) if ShopifyCli::DB.exists?(:development_theme_name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.delete(ctx)
|
|
52
|
+
new(ctx).delete
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def generate_theme_name
|
|
58
|
+
hostname = Socket.gethostname.split(".").shift
|
|
59
|
+
hash = SecureRandom.hex(3)
|
|
60
|
+
|
|
61
|
+
theme_name = "Development (#{hash}-#{hostname})"
|
|
62
|
+
|
|
63
|
+
ShopifyCli::DB.set(development_theme_name: theme_name)
|
|
64
|
+
|
|
65
|
+
theme_name
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "mime_type"
|
|
3
|
+
|
|
4
|
+
module ShopifyCli
|
|
5
|
+
module Theme
|
|
6
|
+
class File < Struct.new(:path)
|
|
7
|
+
attr_reader :relative_path
|
|
8
|
+
attr_accessor :remote_checksum
|
|
9
|
+
|
|
10
|
+
def initialize(path, root)
|
|
11
|
+
super(Pathname.new(path))
|
|
12
|
+
|
|
13
|
+
# Path may be relative or absolute depending on the source.
|
|
14
|
+
# By converting both the path and the root to absolute paths, we
|
|
15
|
+
# can safely fetch a relative path.
|
|
16
|
+
@relative_path = self.path.expand_path.relative_path_from(root.expand_path)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def read
|
|
20
|
+
path.read
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def write(content)
|
|
24
|
+
path.parent.mkpath unless path.parent.directory?
|
|
25
|
+
path.write(content)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def delete
|
|
29
|
+
path.delete
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def exist?
|
|
33
|
+
path.exist?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def mime_type
|
|
37
|
+
@mime_type ||= MimeType.by_filename(relative_path)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def text?
|
|
41
|
+
mime_type.text?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def liquid?
|
|
45
|
+
path.extname == ".liquid"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def json?
|
|
49
|
+
path.extname == ".json"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def template?
|
|
53
|
+
relative_path.to_s.start_with?("templates/")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def checksum
|
|
57
|
+
content = read
|
|
58
|
+
if mime_type.json?
|
|
59
|
+
# Normalize JSON to match backend
|
|
60
|
+
begin
|
|
61
|
+
content = normalize_json(content)
|
|
62
|
+
rescue JSON::JSONError
|
|
63
|
+
# Fallback to using the raw content
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
Digest::MD5.hexdigest(content)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Make it possible to check whether a given File is within a list of Files with `include?`,
|
|
70
|
+
# some of which may be relative paths while others are absolute paths.
|
|
71
|
+
def ==(other)
|
|
72
|
+
relative_path == other.relative_path
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def normalize_json(content)
|
|
78
|
+
parsed = JSON.parse(content)
|
|
79
|
+
|
|
80
|
+
if template?
|
|
81
|
+
JsonTemplateNormalizer.new.visit_document(parsed)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
normalized = JSON.generate(parsed)
|
|
85
|
+
# Backend escapes forward slashes
|
|
86
|
+
normalized.gsub!(/\//, "\\/")
|
|
87
|
+
normalized
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
class JsonTemplateNormalizer
|
|
91
|
+
def visit_document(value)
|
|
92
|
+
visit_hash(value["sections"])
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def visit_hash(hash)
|
|
96
|
+
return unless hash.is_a?(Hash)
|
|
97
|
+
hash.each do |_, value|
|
|
98
|
+
visit_value(value)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def visit_value(value)
|
|
103
|
+
# Reinsert settings to force the same ordering as in the backend
|
|
104
|
+
settings = value.delete("settings") || {}
|
|
105
|
+
value["settings"] = settings
|
|
106
|
+
|
|
107
|
+
visit_hash(value["blocks"])
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|