shopify-cli 2.7.3 → 2.7.4
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/.gitignore +1 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile.lock +1 -1
- data/dev.yml +2 -2
- data/ext/javy/javy.rb +7 -8
- data/lib/graphql/get_extension_registrations.graphql +27 -0
- data/lib/project_types/extension/cli.rb +27 -2
- data/lib/project_types/extension/commands/build.rb +10 -10
- data/lib/project_types/extension/commands/create.rb +2 -3
- data/lib/project_types/extension/commands/push.rb +36 -8
- data/lib/project_types/extension/extension_project.rb +1 -1
- data/lib/project_types/extension/features/argo_serve.rb +6 -5
- data/lib/project_types/extension/forms/questions/ask_registration.rb +6 -2
- data/lib/project_types/extension/loaders/project.rb +29 -0
- data/lib/project_types/extension/loaders/specification_handler.rb +22 -0
- data/lib/project_types/extension/messages/messages.rb +4 -0
- data/lib/project_types/extension/models/app.rb +1 -1
- data/lib/project_types/extension/models/specification_handlers/default.rb +4 -0
- data/lib/project_types/extension/tasks/convert_server_config.rb +3 -1
- data/lib/project_types/extension/tasks/execute_commands/base.rb +13 -0
- data/lib/project_types/extension/tasks/execute_commands/build.rb +29 -0
- data/lib/project_types/extension/tasks/execute_commands/create.rb +33 -0
- data/lib/project_types/extension/tasks/execute_commands/serve.rb +35 -0
- data/lib/project_types/extension/tasks/merge_server_config.rb +33 -22
- data/lib/project_types/script/cli.rb +2 -0
- data/lib/project_types/script/commands/connect.rb +19 -0
- data/lib/project_types/script/layers/application/connect_app.rb +9 -3
- data/lib/project_types/script/layers/application/create_script.rb +16 -16
- data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +21 -0
- data/lib/project_types/script/messages/messages.rb +12 -1
- data/lib/project_types/theme/commands/push.rb +3 -1
- data/lib/project_types/theme/messages/messages.rb +1 -0
- data/lib/shopify_cli/command.rb +6 -0
- data/lib/shopify_cli/constants.rb +4 -0
- data/lib/shopify_cli/context.rb +1 -1
- data/lib/shopify_cli/form.rb +2 -0
- data/lib/shopify_cli/messages/messages.rb +7 -1
- data/lib/shopify_cli/partners_api/app_extensions/job.rb +36 -0
- data/lib/shopify_cli/partners_api/app_extensions.rb +46 -0
- data/lib/shopify_cli/partners_api/organizations.rb +2 -5
- data/lib/shopify_cli/partners_api.rb +1 -0
- data/lib/shopify_cli/project.rb +8 -7
- data/lib/shopify_cli/resources/env_file.rb +13 -5
- data/lib/shopify_cli/theme/dev_server/hot-reload.js +15 -2
- data/lib/shopify_cli/theme/dev_server/proxy/template_param_builder.rb +84 -0
- data/lib/shopify_cli/theme/dev_server/proxy.rb +9 -15
- data/lib/shopify_cli/thread_pool/job.rb +27 -0
- data/lib/shopify_cli/thread_pool.rb +37 -0
- data/lib/shopify_cli/version.rb +1 -1
- data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +3 -1
- metadata +15 -4
- data/lib/graphql/all_orgs_with_extensions.graphql +0 -37
- data/lib/project_types/extension/tasks/run_extension_command.rb +0 -82
| @@ -13,6 +13,7 @@ module Script | |
| 13 13 | 
             
                hidden_feature(feature_set: :script_project)
         | 
| 14 14 | 
             
                subcommand :Create, "create", Project.project_filepath("commands/create")
         | 
| 15 15 | 
             
                subcommand :Push, "push", Project.project_filepath("commands/push")
         | 
| 16 | 
            +
                subcommand :Connect, "connect", Project.project_filepath("commands/connect")
         | 
| 16 17 | 
             
                subcommand :Javy, "javy", Project.project_filepath("commands/javy")
         | 
| 17 18 | 
             
              end
         | 
| 18 19 | 
             
              ShopifyCLI::Commands.register("Script::Command", "script")
         | 
| @@ -24,6 +25,7 @@ module Script | |
| 24 25 | 
             
                autoload :AskScriptUuid, Project.project_filepath("forms/ask_script_uuid")
         | 
| 25 26 | 
             
                autoload :RunAgainstShopifyOrg, Project.project_filepath("forms/run_against_shopify_org")
         | 
| 26 27 | 
             
                autoload :Create, Project.project_filepath("forms/create")
         | 
| 28 | 
            +
                autoload :Connect, Project.project_filepath("forms/connect")
         | 
| 27 29 | 
             
                autoload :ScriptForm, Project.project_filepath("forms/script_form")
         | 
| 28 30 | 
             
              end
         | 
| 29 31 |  | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            module Script
         | 
| 3 | 
            +
              class Command
         | 
| 4 | 
            +
                class Connect < ShopifyCLI::Command::SubCommand
         | 
| 5 | 
            +
                  prerequisite_task :ensure_authenticated
         | 
| 6 | 
            +
                  prerequisite_task ensure_project_type: :script
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def call(_args, _)
         | 
| 9 | 
            +
                    Layers::Application::ConnectApp.call(ctx: @ctx, force: true, strict: true)
         | 
| 10 | 
            +
                  rescue StandardError => e
         | 
| 11 | 
            +
                    UI::ErrorHandler.pretty_print_and_raise(e, failed_op: @ctx.message("script.connect.error.operation_failed"))
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def self.help
         | 
| 15 | 
            +
                    ShopifyCLI::Context.new.message("connect.help", ShopifyCLI::TOOL_NAME, ShopifyCLI::TOOL_NAME)
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -7,10 +7,11 @@ module Script | |
| 7 7 | 
             
                module Application
         | 
| 8 8 | 
             
                  class ConnectApp
         | 
| 9 9 | 
             
                    class << self
         | 
| 10 | 
            -
                      def call(ctx:)
         | 
| 10 | 
            +
                      def call(ctx:, force: false, strict: false)
         | 
| 11 11 | 
             
                        script_project_repo = Layers::Infrastructure::ScriptProjectRepository.new(ctx: ctx)
         | 
| 12 12 | 
             
                        script_project = script_project_repo.get
         | 
| 13 | 
            -
             | 
| 13 | 
            +
             | 
| 14 | 
            +
                        return false if script_project.env_valid? && !force
         | 
| 14 15 |  | 
| 15 16 | 
             
                        if ShopifyCLI::Shopifolk.check && Forms::RunAgainstShopifyOrg.ask(ctx, nil, nil).response
         | 
| 16 17 | 
             
                          ShopifyCLI::Shopifolk.act_as_shopify_organization
         | 
| @@ -37,13 +38,18 @@ module Script | |
| 37 38 | 
             
                        extension_point_type = script_project.extension_point_type
         | 
| 38 39 | 
             
                        scripts = script_service.get_app_scripts(extension_point_type: extension_point_type)
         | 
| 39 40 |  | 
| 40 | 
            -
                        uuid = Forms::AskScriptUuid.ask(ctx, scripts, nil) | 
| 41 | 
            +
                        uuid = Forms::AskScriptUuid.ask(ctx, scripts, nil)&.uuid
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                        if strict && uuid.nil?
         | 
| 44 | 
            +
                          ctx.abort(ctx.message("script.connect.missing_script"))
         | 
| 45 | 
            +
                        end
         | 
| 41 46 |  | 
| 42 47 | 
             
                        script_project_repo.create_env(
         | 
| 43 48 | 
             
                          api_key: app["apiKey"],
         | 
| 44 49 | 
             
                          secret: app["apiSecretKeys"].first["secret"],
         | 
| 45 50 | 
             
                          uuid: uuid
         | 
| 46 51 | 
             
                        )
         | 
| 52 | 
            +
                        ctx.done(ctx.message("script.connect.connected", app["title"]))
         | 
| 47 53 |  | 
| 48 54 | 
             
                        true
         | 
| 49 55 | 
             
                      end
         | 
| @@ -8,11 +8,14 @@ module Script | |
| 8 8 | 
             
                  class CreateScript
         | 
| 9 9 | 
             
                    class << self
         | 
| 10 10 | 
             
                      def call(ctx:, language:, sparse_checkout_branch:, script_name:, extension_point_type:)
         | 
| 11 | 
            -
                         | 
| 11 | 
            +
                        script_project_repo = Infrastructure::ScriptProjectRepository.new(
         | 
| 12 | 
            +
                          ctx: ctx,
         | 
| 13 | 
            +
                          directory: script_name,
         | 
| 14 | 
            +
                          initial_directory: ctx.root
         | 
| 15 | 
            +
                        )
         | 
| 12 16 |  | 
| 13 | 
            -
                        in_new_directory_context( | 
| 17 | 
            +
                        in_new_directory_context(script_project_repo) do
         | 
| 14 18 | 
             
                          extension_point = ExtensionPoints.get(type: extension_point_type)
         | 
| 15 | 
            -
                          script_project_repo = Infrastructure::ScriptProjectRepository.new(ctx: ctx)
         | 
| 16 19 | 
             
                          project = script_project_repo.create(
         | 
| 17 20 | 
             
                            script_name: script_name,
         | 
| 18 21 | 
             
                            extension_point_type: extension_point_type,
         | 
| @@ -62,19 +65,16 @@ module Script | |
| 62 65 | 
             
                        ProjectDependencies.install(ctx: ctx, task_runner: task_runner)
         | 
| 63 66 | 
             
                      end
         | 
| 64 67 |  | 
| 65 | 
            -
                      def in_new_directory_context( | 
| 66 | 
            -
                         | 
| 67 | 
            -
                         | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
                         | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
                        ensure
         | 
| 76 | 
            -
                          ctx.chdir(initial_directory)
         | 
| 77 | 
            -
                        end
         | 
| 68 | 
            +
                      def in_new_directory_context(script_project_repo)
         | 
| 69 | 
            +
                        script_project_repo.create_project_directory
         | 
| 70 | 
            +
                        yield
         | 
| 71 | 
            +
                      rescue Infrastructure::Errors::ScriptProjectAlreadyExistsError
         | 
| 72 | 
            +
                        raise
         | 
| 73 | 
            +
                      rescue
         | 
| 74 | 
            +
                        script_project_repo.delete_project_directory
         | 
| 75 | 
            +
                        raise
         | 
| 76 | 
            +
                      ensure
         | 
| 77 | 
            +
                        script_project_repo.change_to_initial_directory
         | 
| 78 78 | 
             
                      end
         | 
| 79 79 | 
             
                    end
         | 
| 80 80 | 
             
                  end
         | 
| @@ -6,9 +6,26 @@ module Script | |
| 6 6 | 
             
                  class ScriptProjectRepository
         | 
| 7 7 | 
             
                    include SmartProperties
         | 
| 8 8 | 
             
                    property! :ctx, accepts: ShopifyCLI::Context
         | 
| 9 | 
            +
                    property :directory, accepts: String
         | 
| 10 | 
            +
                    property :initial_directory, accepts: String
         | 
| 9 11 |  | 
| 10 12 | 
             
                    MUTABLE_ENV_VALUES = %i(uuid)
         | 
| 11 13 |  | 
| 14 | 
            +
                    def create_project_directory
         | 
| 15 | 
            +
                      raise Infrastructure::Errors::ScriptProjectAlreadyExistsError, directory if ctx.dir_exist?(directory)
         | 
| 16 | 
            +
                      ctx.mkdir_p(directory)
         | 
| 17 | 
            +
                      change_directory(directory: directory)
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    def delete_project_directory
         | 
| 21 | 
            +
                      change_to_initial_directory
         | 
| 22 | 
            +
                      ctx.rm_r(directory)
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    def change_to_initial_directory
         | 
| 26 | 
            +
                      change_directory(directory: initial_directory)
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 12 29 | 
             
                    def create(script_name:, extension_point_type:, language:)
         | 
| 13 30 | 
             
                      validate_metadata!(extension_point_type, language)
         | 
| 14 31 |  | 
| @@ -95,6 +112,10 @@ module Script | |
| 95 112 |  | 
| 96 113 | 
             
                    private
         | 
| 97 114 |  | 
| 115 | 
            +
                    def change_directory(directory:)
         | 
| 116 | 
            +
                      ctx.chdir(directory)
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
             | 
| 98 119 | 
             
                    def capture_io(&block)
         | 
| 99 120 | 
             
                      CLI::UI::StdoutRouter::Capture.new(&block).run
         | 
| 100 121 | 
             
                    end
         | 
| @@ -144,6 +144,7 @@ module Script | |
| 144 144 |  | 
| 145 145 | 
             
                      language_library_for_api_not_found_cause: "Script can’t be pushed because the %{language} library for API %{api} is missing.",
         | 
| 146 146 | 
             
                      language_library_for_api_not_found_help: "Make sure extension_point.yml contains the correct API library.",
         | 
| 147 | 
            +
                      no_scripts_found_in_app: "The selected apps have no scripts. Please, create them first on the partners' dashboard.",
         | 
| 147 148 | 
             
                    },
         | 
| 148 149 |  | 
| 149 150 | 
             
                    create: {
         | 
| @@ -179,7 +180,17 @@ module Script | |
| 179 180 |  | 
| 180 181 | 
             
                      script_pushed: "{{v}} Script pushed to app (API key: %{api_key}).",
         | 
| 181 182 | 
             
                    },
         | 
| 182 | 
            -
             | 
| 183 | 
            +
                    connect: {
         | 
| 184 | 
            +
                      connected: "Connected! Your project is now connected to {{green:%s}}",
         | 
| 185 | 
            +
                      missing_script: "No script has been selected.",
         | 
| 186 | 
            +
                      help: <<~HELP,
         | 
| 187 | 
            +
                        {{command:%s script connect}}: Connects an existing script to an app.
         | 
| 188 | 
            +
                          Usage: {{command:%s script connect}}
         | 
| 189 | 
            +
                      HELP
         | 
| 190 | 
            +
                      error: {
         | 
| 191 | 
            +
                        operation_failed: "Couldn't connect script to app.",
         | 
| 192 | 
            +
                      },
         | 
| 193 | 
            +
                    },
         | 
| 183 194 | 
             
                    javy: {
         | 
| 184 195 | 
             
                      help: <<~HELP,
         | 
| 185 196 | 
             
                        Compile the JavaScript code into WebAssembly.
         | 
| @@ -51,7 +51,9 @@ module Theme | |
| 51 51 | 
             
                    end
         | 
| 52 52 |  | 
| 53 53 | 
             
                    if theme.live? && !options.flags[:allow_live]
         | 
| 54 | 
            -
                       | 
| 54 | 
            +
                      question = @ctx.message("theme.push.live")
         | 
| 55 | 
            +
                      question += @ctx.message("theme.push.theme", theme.name, theme.id) if options.flags[:live]
         | 
| 56 | 
            +
                      return unless CLI::UI::Prompt.confirm(question)
         | 
| 55 57 | 
             
                    end
         | 
| 56 58 |  | 
| 57 59 | 
             
                    ignore_filter = ShopifyCLI::Theme::IgnoreFilter.from_path(root)
         | 
| @@ -74,6 +74,7 @@ module Theme | |
| 74 74 | 
             
                      push: "Pushing theme files to Shopify",
         | 
| 75 75 | 
             
                      select: "Select theme to push to",
         | 
| 76 76 | 
             
                      live: "Are you sure you want to push to your live theme?",
         | 
| 77 | 
            +
                      theme: "\n  Theme: {{blue:%s #%s}} {{green:[live]}}",
         | 
| 77 78 | 
             
                      theme_not_found: "Theme #%s doesn't exist",
         | 
| 78 79 | 
             
                      done: <<~DONE,
         | 
| 79 80 | 
             
                        {{green:Your theme was pushed successfully}}
         | 
    
        data/lib/shopify_cli/command.rb
    CHANGED
    
    | @@ -29,6 +29,12 @@ module ShopifyCLI | |
| 29 29 | 
             
                      run_prerequisites
         | 
| 30 30 | 
             
                      cmd.call(args, command_name)
         | 
| 31 31 | 
             
                    end
         | 
| 32 | 
            +
                  rescue OptionParser::InvalidOption => error
         | 
| 33 | 
            +
                    arg = error.args.first
         | 
| 34 | 
            +
                    raise ShopifyCLI::Abort, @ctx.message("core.errors.option_parser.invalid_option", arg)
         | 
| 35 | 
            +
                  rescue OptionParser::MissingArgument => error
         | 
| 36 | 
            +
                    arg = error.args.first
         | 
| 37 | 
            +
                    raise ShopifyCLI::Abort, @ctx.message("core.errors.option_parser.missing_argument", arg)
         | 
| 32 38 | 
             
                  end
         | 
| 33 39 |  | 
| 34 40 | 
             
                  def options(&block)
         | 
    
        data/lib/shopify_cli/context.rb
    CHANGED
    
    
    
        data/lib/shopify_cli/form.rb
    CHANGED
    
    
| @@ -14,6 +14,12 @@ module ShopifyCLI | |
| 14 14 | 
             
                    },
         | 
| 15 15 | 
             
                  },
         | 
| 16 16 | 
             
                  core: {
         | 
| 17 | 
            +
                    errors: {
         | 
| 18 | 
            +
                      option_parser: {
         | 
| 19 | 
            +
                        invalid_option: "The option {{command:%s}} is not supported.",
         | 
| 20 | 
            +
                        missing_argument: "The required argument {{command:%s}} is missing.",
         | 
| 21 | 
            +
                      },
         | 
| 22 | 
            +
                    },
         | 
| 17 23 | 
             
                    app: {
         | 
| 18 24 | 
             
                      help: <<~HELP,
         | 
| 19 25 | 
             
                      Suite of commands for developing apps. See {{command:%1$s app <command> --help}} for usage of each command.
         | 
| @@ -719,7 +725,7 @@ module ShopifyCLI | |
| 719 725 | 
             
                      signup_suggestion: <<~MESSAGE,
         | 
| 720 726 | 
             
                        {{*}} To avoid tunnels that timeout, it is recommended to signup for a free ngrok
         | 
| 721 727 | 
             
                        account at {{underline:https://ngrok.com/signup}}. After you signup, install your
         | 
| 722 | 
            -
                        personalized authorization token using {{command:%s  | 
| 728 | 
            +
                        personalized authorization token using {{command:%s app tunnel auth <token>}}.
         | 
| 723 729 | 
             
                      MESSAGE
         | 
| 724 730 | 
             
                      start: "{{v}} ngrok tunnel running at {{underline:%s}}",
         | 
| 725 731 | 
             
                      start_with_account: "{{v}} ngrok tunnel running at {{underline:%s}}, with account %s",
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "shopify_cli/thread_pool/job"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module ShopifyCLI
         | 
| 6 | 
            +
              class PartnersAPI
         | 
| 7 | 
            +
                class AppExtensions
         | 
| 8 | 
            +
                  class Job < ShopifyCLI::ThreadPool::Job
         | 
| 9 | 
            +
                    attr_reader :result
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    def initialize(ctx, app, type)
         | 
| 12 | 
            +
                      super()
         | 
| 13 | 
            +
                      @ctx = ctx
         | 
| 14 | 
            +
                      @app = app
         | 
| 15 | 
            +
                      @api_key = @app["apiKey"]
         | 
| 16 | 
            +
                      @type = type
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    def perform!
         | 
| 20 | 
            +
                      resp = PartnersAPI.query(@ctx, "get_extension_registrations", **params)
         | 
| 21 | 
            +
                      @result = resp&.dig("data", "app") || {}
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def patch_app_with_extensions!
         | 
| 25 | 
            +
                      @app.merge!(result)
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    private
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    def params
         | 
| 31 | 
            +
                      { api_key: @api_key, type: @type }
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "shopify_cli/thread_pool"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require_relative "app_extensions/job"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module ShopifyCLI
         | 
| 8 | 
            +
              class PartnersAPI
         | 
| 9 | 
            +
                class AppExtensions
         | 
| 10 | 
            +
                  class << self
         | 
| 11 | 
            +
                    def fetch_apps_extensions(ctx, orgs, type)
         | 
| 12 | 
            +
                      jobs = apps(orgs).map { |app| AppExtensions::Job.new(ctx, app, type) }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                      consume_jobs!(jobs)
         | 
| 15 | 
            +
                      patch_apps_with_extensions!(jobs)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                      orgs
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    private
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    def apps(orgs)
         | 
| 23 | 
            +
                      orgs.flat_map { |org| org["apps"] }
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    def consume_jobs!(jobs)
         | 
| 27 | 
            +
                      thread_pool = ShopifyCLI::ThreadPool.new
         | 
| 28 | 
            +
                      jobs.each do |job|
         | 
| 29 | 
            +
                        thread_pool.schedule(job)
         | 
| 30 | 
            +
                      end
         | 
| 31 | 
            +
                      thread_pool.shutdown
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                      raise_if_any_error(jobs)
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    def patch_apps_with_extensions!(jobs)
         | 
| 37 | 
            +
                      jobs.each(&:patch_app_with_extensions!)
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    def raise_if_any_error(jobs)
         | 
| 41 | 
            +
                      jobs.find(&:error?).tap { |job| raise job.error if job }
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
| @@ -28,11 +28,8 @@ module ShopifyCLI | |
| 28 28 | 
             
                    end
         | 
| 29 29 |  | 
| 30 30 | 
             
                    def fetch_with_extensions(ctx, type)
         | 
| 31 | 
            -
                       | 
| 32 | 
            -
                      ( | 
| 33 | 
            -
                        org["apps"] = (org.dig("apps", "nodes") || [])
         | 
| 34 | 
            -
                        org
         | 
| 35 | 
            -
                      end
         | 
| 31 | 
            +
                      orgs = fetch_with_app(ctx)
         | 
| 32 | 
            +
                      AppExtensions.fetch_apps_extensions(ctx, orgs, type)
         | 
| 36 33 | 
             
                    end
         | 
| 37 34 | 
             
                  end
         | 
| 38 35 | 
             
                end
         | 
    
        data/lib/shopify_cli/project.rb
    CHANGED
    
    | @@ -107,13 +107,6 @@ module ShopifyCLI | |
| 107 107 | 
             
                    @dir = nil
         | 
| 108 108 | 
             
                  end
         | 
| 109 109 |  | 
| 110 | 
            -
                  private
         | 
| 111 | 
            -
             | 
| 112 | 
            -
                  def directory(dir)
         | 
| 113 | 
            -
                    @dir ||= Hash.new { |h, k| h[k] = __directory(k) }
         | 
| 114 | 
            -
                    @dir[dir]
         | 
| 115 | 
            -
                  end
         | 
| 116 | 
            -
             | 
| 117 110 | 
             
                  def at(dir)
         | 
| 118 111 | 
             
                    proj_dir = directory(dir)
         | 
| 119 112 | 
             
                    unless proj_dir
         | 
| @@ -123,6 +116,13 @@ module ShopifyCLI | |
| 123 116 | 
             
                    @at[proj_dir]
         | 
| 124 117 | 
             
                  end
         | 
| 125 118 |  | 
| 119 | 
            +
                  private
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  def directory(dir)
         | 
| 122 | 
            +
                    @dir ||= Hash.new { |h, k| h[k] = __directory(k) }
         | 
| 123 | 
            +
                    @dir[dir]
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
             | 
| 126 126 | 
             
                  def __directory(curr)
         | 
| 127 127 | 
             
                    loop do
         | 
| 128 128 | 
             
                      return nil if curr == "/" || /^[A-Z]:\/$/.match?(curr)
         | 
| @@ -134,6 +134,7 @@ module ShopifyCLI | |
| 134 134 | 
             
                end
         | 
| 135 135 |  | 
| 136 136 | 
             
                property :directory # :nodoc:
         | 
| 137 | 
            +
                property :env # :nodoc:
         | 
| 137 138 |  | 
| 138 139 | 
             
                ##
         | 
| 139 140 | 
             
                # will read, parse and return the envfile for the project
         | 
| @@ -14,11 +14,15 @@ module ShopifyCLI | |
| 14 14 | 
             
                  }
         | 
| 15 15 |  | 
| 16 16 | 
             
                  class << self
         | 
| 17 | 
            -
                    def read(_directory = Dir.pwd)
         | 
| 18 | 
            -
                      input = parse_external_env
         | 
| 17 | 
            +
                    def read(_directory = Dir.pwd, overrides: {})
         | 
| 18 | 
            +
                      input = parse_external_env(overrides: overrides)
         | 
| 19 19 | 
             
                      new(input)
         | 
| 20 20 | 
             
                    end
         | 
| 21 21 |  | 
| 22 | 
            +
                    def from_hash(hash)
         | 
| 23 | 
            +
                      new(env_input(hash))
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
             | 
| 22 26 | 
             
                    def parse(directory)
         | 
| 23 27 | 
             
                      File.read(File.join(directory, FILENAME))
         | 
| 24 28 | 
             
                        .gsub("\r\n", "\n").split("\n").each_with_object({}) do |line, output|
         | 
| @@ -37,10 +41,14 @@ module ShopifyCLI | |
| 37 41 | 
             
                      end
         | 
| 38 42 | 
             
                    end
         | 
| 39 43 |  | 
| 40 | 
            -
                    def parse_external_env(directory = Dir.pwd)
         | 
| 44 | 
            +
                    def parse_external_env(directory = Dir.pwd, overrides: {})
         | 
| 45 | 
            +
                      env_input(parse(directory), overrides: overrides)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def env_input(parsed_source, overrides: {})
         | 
| 41 49 | 
             
                      env_details = {}
         | 
| 42 50 | 
             
                      extra = {}
         | 
| 43 | 
            -
                       | 
| 51 | 
            +
                      parsed_source.merge(overrides).each do |key, value|
         | 
| 44 52 | 
             
                        if KEY_MAP[key]
         | 
| 45 53 | 
             
                          env_details[KEY_MAP[key]] = value
         | 
| 46 54 | 
             
                        else
         | 
| @@ -53,7 +61,7 @@ module ShopifyCLI | |
| 53 61 | 
             
                  end
         | 
| 54 62 |  | 
| 55 63 | 
             
                  property :api_key, required: true
         | 
| 56 | 
            -
                  property :secret | 
| 64 | 
            +
                  property :secret
         | 
| 57 65 | 
             
                  property :shop
         | 
| 58 66 | 
             
                  property :scopes
         | 
| 59 67 | 
             
                  property :host
         | 
| @@ -33,7 +33,20 @@ | |
| 33 33 | 
             
                }
         | 
| 34 34 | 
             
              }
         | 
| 35 35 |  | 
| 36 | 
            -
              function  | 
| 36 | 
            +
              function setHotReloadCookie(files) {
         | 
| 37 | 
            +
                var date = new Date();
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                // Hot reload cookie expires in 3 seconds
         | 
| 40 | 
            +
                date.setSeconds(date.getSeconds() + 3);
         | 
| 41 | 
            +
                
         | 
| 42 | 
            +
                var sections = files.join(',');
         | 
| 43 | 
            +
                var expires = date.toUTCString();
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                document.cookie = `hot_reload_sections=${sections}; expires=${expires}; path=/`;
         | 
| 46 | 
            +
              }
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              function refreshPage(files) {
         | 
| 49 | 
            +
                setHotReloadCookie(files);
         | 
| 37 50 | 
             
                console.log('[HotReload] Refreshing entire page');
         | 
| 38 51 | 
             
                window.location.reload();
         | 
| 39 52 | 
             
              }
         | 
| @@ -43,7 +56,7 @@ | |
| 43 56 | 
             
                var modifiedFiles = data.modified;
         | 
| 44 57 |  | 
| 45 58 | 
             
                if (isRefreshRequired(modifiedFiles)) {
         | 
| 46 | 
            -
                  refreshPage();
         | 
| 59 | 
            +
                  refreshPage(modifiedFiles);
         | 
| 47 60 | 
             
                } else {
         | 
| 48 61 | 
             
                  modifiedFiles.forEach(refreshFile);
         | 
| 49 62 | 
             
                }
         | 
| @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "cgi"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module ShopifyCLI
         | 
| 6 | 
            +
              module Theme
         | 
| 7 | 
            +
                module DevServer
         | 
| 8 | 
            +
                  class Proxy
         | 
| 9 | 
            +
                    class TemplateParamBuilder
         | 
| 10 | 
            +
                      def build
         | 
| 11 | 
            +
                        # Core doesn't support replace_templates
         | 
| 12 | 
            +
                        return {} if core?(current_path)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                        (syncer_templates + request_templates)
         | 
| 15 | 
            +
                          .select { |file| file.liquid? || file.json? }
         | 
| 16 | 
            +
                          .uniq(&:relative_path)
         | 
| 17 | 
            +
                          .map { |file| as_param(file) }
         | 
| 18 | 
            +
                          .to_h
         | 
| 19 | 
            +
                      end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                      def with_core_endpoints(core_endpoints)
         | 
| 22 | 
            +
                        @core_endpoints = core_endpoints
         | 
| 23 | 
            +
                        self
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      def with_syncer(syncer)
         | 
| 27 | 
            +
                        @syncer = syncer
         | 
| 28 | 
            +
                        self
         | 
| 29 | 
            +
                      end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      def with_rack_env(rack_env)
         | 
| 32 | 
            +
                        @rack_env = rack_env
         | 
| 33 | 
            +
                        self
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      def with_theme(theme)
         | 
| 37 | 
            +
                        @theme = theme
         | 
| 38 | 
            +
                        self
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                      private
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                      def as_param(file)
         | 
| 44 | 
            +
                        ["replace_templates[#{file.relative_path}]", file.read]
         | 
| 45 | 
            +
                      end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                      def syncer_templates
         | 
| 48 | 
            +
                        @syncer&.pending_updates || []
         | 
| 49 | 
            +
                      end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                      def request_templates
         | 
| 52 | 
            +
                        cookie_sections
         | 
| 53 | 
            +
                          .map { |section| @theme[section] unless @theme.nil? }
         | 
| 54 | 
            +
                          .compact
         | 
| 55 | 
            +
                      end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      def cookie_sections
         | 
| 58 | 
            +
                        CGI::Cookie.parse(cookie)["hot_reload_sections"].join.split(",") || []
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                      def core?(path)
         | 
| 62 | 
            +
                        core_endpoints.include?(path)
         | 
| 63 | 
            +
                      end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                      def current_path
         | 
| 66 | 
            +
                        rack_env["PATH_INFO"]
         | 
| 67 | 
            +
                      end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                      def cookie
         | 
| 70 | 
            +
                        rack_env["HTTP_COOKIE"]
         | 
| 71 | 
            +
                      end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                      def core_endpoints
         | 
| 74 | 
            +
                        @core_endpoints || []
         | 
| 75 | 
            +
                      end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                      def rack_env
         | 
| 78 | 
            +
                        @rack_env || {}
         | 
| 79 | 
            +
                      end
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
            end
         | 
| @@ -2,6 +2,9 @@ | |
| 2 2 | 
             
            require "net/http"
         | 
| 3 3 | 
             
            require "stringio"
         | 
| 4 4 | 
             
            require "time"
         | 
| 5 | 
            +
            require "cgi"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            require_relative "proxy/template_param_builder"
         | 
| 5 8 |  | 
| 6 9 | 
             
            module ShopifyCLI
         | 
| 7 10 | 
             
              module Theme
         | 
| @@ -112,21 +115,12 @@ module ShopifyCLI | |
| 112 115 | 
             
                    end
         | 
| 113 116 |  | 
| 114 117 | 
             
                    def build_replace_templates_param(env)
         | 
| 115 | 
            -
                       | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
                        # Only replace Liquid or JSON files
         | 
| 122 | 
            -
                        file.liquid? || file.json?
         | 
| 123 | 
            -
                      end
         | 
| 124 | 
            -
             | 
| 125 | 
            -
                      pending_templates.each do |path|
         | 
| 126 | 
            -
                        params["replace_templates[#{path.relative_path}]"] = path.read
         | 
| 127 | 
            -
                      end
         | 
| 128 | 
            -
             | 
| 129 | 
            -
                      params
         | 
| 118 | 
            +
                      TemplateParamBuilder.new
         | 
| 119 | 
            +
                        .with_core_endpoints(@core_endpoints)
         | 
| 120 | 
            +
                        .with_syncer(@syncer)
         | 
| 121 | 
            +
                        .with_theme(@theme)
         | 
| 122 | 
            +
                        .with_rack_env(env)
         | 
| 123 | 
            +
                        .build
         | 
| 130 124 | 
             
                    end
         | 
| 131 125 |  | 
| 132 126 | 
             
                    def add_session_cookie(cookie_header)
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ShopifyCLI
         | 
| 4 | 
            +
              class ThreadPool
         | 
| 5 | 
            +
                class Job
         | 
| 6 | 
            +
                  attr_reader :error
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def perform!
         | 
| 9 | 
            +
                    raise "`#{self.class.name}#perform!` must be defined"
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def call
         | 
| 13 | 
            +
                    perform!
         | 
| 14 | 
            +
                  rescue StandardError => error
         | 
| 15 | 
            +
                    @error = error
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def success?
         | 
| 19 | 
            +
                    !@error
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def error?
         | 
| 23 | 
            +
                    !!@error
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ShopifyCLI
         | 
| 4 | 
            +
              class ThreadPool
         | 
| 5 | 
            +
                attr_reader :errors
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(pool_size: 10)
         | 
| 8 | 
            +
                  @jobs = Queue.new
         | 
| 9 | 
            +
                  @pool = Array.new(pool_size) { spawn_thread }
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def schedule(job)
         | 
| 13 | 
            +
                  @jobs << job
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def shutdown
         | 
| 17 | 
            +
                  @pool.size.times do
         | 
| 18 | 
            +
                    schedule(-> { throw(:stop_thread) })
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                  @pool.map(&:join)
         | 
| 21 | 
            +
                ensure
         | 
| 22 | 
            +
                  @jobs.close
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                private
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def spawn_thread
         | 
| 28 | 
            +
                  Thread.new do
         | 
| 29 | 
            +
                    catch(:stop_thread) do
         | 
| 30 | 
            +
                      loop do
         | 
| 31 | 
            +
                        @jobs.pop.call
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         |