# frozen_string_literal: true require "shopify_cli" module ShopifyCLI ## # ShopifyCLI::Project captures the current project that the user is working on. # This class can be used to fetch and save project environment as well as the # project config `.shopify-cli.yml`. # class Project include SmartProperties class << self ## # will get an instance of the project that the user is currently operating # on. This is used for access to project resources. # # #### Parameters # # * `force_reload` - whether to force a reload of the project files # # #### Returns # # * `project` - a Project instance if the user is currently in the project. # # #### Raises # # * `ShopifyCLI::Abort` - If the cli is not currently in a project directory # then this will be raised with a message implying that the user is not in # a project directory. # # #### Example # # project = ShopifyCLI::Project.current # def current(force_reload: false) clear if force_reload at(Dir.pwd) end ## # will return true if the command line is currently within a project # # #### Returns # # * `has_current?` - boolean, true if there is a current project # def has_current? !directory(Dir.pwd).nil? end ## # will fetch the project type of the current project. This is mostly used # for internal project type loading, you should not normally need this. # # #### Returns # # * `type` - a symbol of the name of the project type identifier. i.e. [rails, node] # This will be nil if the user is not in a current project. # # #### Example # # type = ShopifyCLI::Project.current_project_type # def current_project_type return unless has_current? current.config["project_type"].to_sym end ## # writes out the `.shopify-cli.yml` file. You should use this when creating # a project type so that the rest of your project type commands will load # in this project, in the future. # # #### Parameters # # * `ctx` - the current running context of your command # * `project_type` - a string or symbol of your project type name # * `organization_id` - the id of the partner organization that the app is owned by. Used for metrics # * `identifiers` - an optional hash of other app identifiers # # #### Example # # type = ShopifyCLI::Project.current_project_type # def write(ctx, project_type:, organization_id:, **identifiers) require "yaml" # takes 20ms, so deferred as late as possible. content = Hash[{ project_type: project_type, organization_id: organization_id.to_i } .merge(identifiers) .collect { |k, v| [k.to_s, v] }] content["shopify_organization"] = true if Shopifolk.acting_as_shopify_organization? ctx.write(".shopify-cli.yml", YAML.dump(content)) clear end def project_name File.basename(current.directory) end def clear @at = nil @dir = nil end private def directory(dir) @dir ||= Hash.new { |h, k| h[k] = __directory(k) } @dir[dir] end def at(dir) proj_dir = directory(dir) unless proj_dir raise(ShopifyCLI::Abort, Context.message("core.project.error.not_in_project")) end @at ||= Hash.new { |h, k| h[k] = new(directory: k) } @at[proj_dir] end def __directory(curr) loop do return nil if curr == "/" || /^[A-Z]:\/$/.match?(curr) file = File.join(curr, ".shopify-cli.yml") return curr if File.exist?(file) curr = File.dirname(curr) end end end property :directory # :nodoc: ## # will read, parse and return the envfile for the project # # #### Returns # # * `env` - An instance of a ShopifyCLI::Resources::EnvFile # # #### Example # # ShopifyCLI::Project.current.env # def env @env ||= begin Resources::EnvFile.read(directory) rescue Errno::ENOENT nil end end ## # will read, parse and return the .shopify-cli.yml for the project # # #### Returns # # * `config` - A hash of configuration # # #### Raises # # * `ShopifyCLI::Abort` - If the yml is invalid or poorly formatted # * `ShopifyCLI::Abort` - If the yml file does not exist # # #### Example # # ShopifyCLI::Project.current.config # def config @config ||= begin config = load_yaml_file(".shopify-cli.yml") unless config.is_a?(Hash) raise ShopifyCLI::Abort, Context.message("core.yaml.error.not_hash", ".shopify-cli.yml") end # The app_type key was deprecated in favour of project_type, so replace it if config.key?("app_type") config["project_type"] = config["app_type"] config.delete("app_type") end config end end private def load_yaml_file(relative_path) f = File.join(directory, relative_path) require "yaml" # takes 20ms, so deferred as late as possible. begin YAML.load_file(f) rescue Psych::SyntaxError => e raise(ShopifyCLI::Abort, Context.message("core.yaml.error.invalid", relative_path, e.message)) # rescue Errno::EACCES => e # TODO # Dev::Helpers::EaccesHandler.diagnose_and_raise(f, e, mode: :read) rescue Errno::ENOENT raise ShopifyCLI::Abort, Context.message("core.yaml.error.not_found", f) end end end end