shopify-cli 1.9.0 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.github/PULL_REQUEST_TEMPLATE.md +1 -0
  3. data/.github/workflows/build.yml +28 -0
  4. data/.github/workflows/release.yml +2 -4
  5. data/CHANGELOG.md +27 -0
  6. data/Gemfile.lock +1 -1
  7. data/README.md +2 -1
  8. data/lib/project_types/extension/cli.rb +7 -1
  9. data/lib/project_types/extension/commands/serve.rb +69 -1
  10. data/lib/project_types/extension/commands/tunnel.rb +3 -1
  11. data/lib/project_types/extension/extension_project.rb +1 -0
  12. data/lib/project_types/extension/features/argo.rb +18 -49
  13. data/lib/project_types/extension/features/argo_runtime.rb +81 -0
  14. data/lib/project_types/extension/features/argo_serve.rb +35 -27
  15. data/lib/project_types/extension/features/argo_serve_options.rb +41 -0
  16. data/lib/project_types/extension/features/argo_setup.rb +1 -1
  17. data/lib/project_types/extension/messages/messages.rb +5 -4
  18. data/lib/project_types/extension/models/npm_package.rb +14 -0
  19. data/lib/project_types/extension/models/specification.rb +3 -2
  20. data/lib/project_types/extension/models/specification_handlers/checkout_argo_extension.rb +18 -0
  21. data/lib/project_types/extension/models/specification_handlers/default.rb +33 -3
  22. data/lib/project_types/extension/models/version.rb +1 -1
  23. data/lib/project_types/extension/tasks/choose_next_available_port.rb +36 -0
  24. data/lib/project_types/extension/tasks/configure_features.rb +2 -0
  25. data/lib/project_types/extension/tasks/find_npm_packages.rb +106 -0
  26. data/lib/project_types/node/messages/messages.rb +4 -4
  27. data/lib/project_types/rails/messages/messages.rb +4 -4
  28. data/lib/project_types/script/cli.rb +17 -12
  29. data/lib/project_types/script/commands/push.rb +1 -1
  30. data/lib/project_types/script/config/extension_points.yml +2 -3
  31. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +5 -2
  32. data/lib/project_types/script/graphql/get_app_scripts.graphql +6 -0
  33. data/lib/project_types/script/layers/application/create_script.rb +2 -2
  34. data/lib/project_types/script/layers/application/push_script.rb +6 -3
  35. data/lib/project_types/script/layers/domain/errors.rb +0 -2
  36. data/lib/project_types/script/layers/domain/push_package.rb +4 -0
  37. data/lib/project_types/script/layers/domain/script_project.rb +21 -1
  38. data/lib/project_types/script/layers/infrastructure/command_runner.rb +19 -0
  39. data/lib/project_types/script/layers/infrastructure/errors.rb +30 -3
  40. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +97 -0
  41. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +103 -0
  42. data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +26 -0
  43. data/lib/project_types/script/layers/infrastructure/languages/rust_project_creator.rb +73 -0
  44. data/lib/project_types/script/layers/infrastructure/languages/rust_task_runner.rb +60 -0
  45. data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +21 -0
  46. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +6 -5
  47. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +61 -34
  48. data/lib/project_types/script/layers/infrastructure/script_service.rb +14 -2
  49. data/lib/project_types/script/messages/messages.rb +20 -3
  50. data/lib/project_types/script/tasks/ensure_env.rb +85 -0
  51. data/lib/project_types/script/ui/error_handler.rb +25 -6
  52. data/lib/shopify-cli/admin_api.rb +7 -4
  53. data/lib/shopify-cli/js_system.rb +2 -2
  54. data/lib/shopify-cli/messages/messages.rb +51 -45
  55. data/lib/shopify-cli/method_object.rb +4 -4
  56. data/lib/shopify-cli/oauth.rb +9 -3
  57. data/lib/shopify-cli/packager.rb +1 -1
  58. data/lib/shopify-cli/partners_api.rb +7 -4
  59. data/lib/shopify-cli/partners_api/organizations.rb +3 -3
  60. data/lib/shopify-cli/resolve_constant.rb +1 -1
  61. data/lib/shopify-cli/resources/env_file.rb +2 -2
  62. data/lib/shopify-cli/shopifolk.rb +1 -1
  63. data/lib/shopify-cli/tasks/select_org_and_shop.rb +6 -4
  64. data/lib/shopify-cli/transform_data_structure.rb +1 -1
  65. data/lib/shopify-cli/tunnel.rb +22 -1
  66. data/lib/shopify-cli/version.rb +1 -1
  67. data/lib/shopify_cli.rb +0 -1
  68. data/vendor/deps/smart_properties/REVISION +1 -1
  69. data/vendor/deps/smart_properties/lib/smart_properties/property.rb +7 -1
  70. data/vendor/deps/smart_properties/lib/smart_properties/version.rb +1 -1
  71. metadata +19 -11
  72. data/.travis.yml +0 -14
  73. data/lib/project_types/extension/features/argo_renderer_package.rb +0 -32
  74. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +0 -100
  75. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +0 -95
  76. data/lib/project_types/script/layers/infrastructure/project_creator.rb +0 -24
  77. data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +0 -72
  78. data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +0 -59
  79. data/lib/project_types/script/layers/infrastructure/task_runner.rb +0 -19
@@ -0,0 +1,41 @@
1
+ module Extension
2
+ module Features
3
+ class ArgoServeOptions
4
+ include SmartProperties
5
+
6
+ property! :argo_runtime, accepts: Features::ArgoRuntime
7
+ property! :context, accepts: ShopifyCli::Context
8
+ property :port, accepts: Integer, default: 39351
9
+ property :public_url, accepts: String, default: ""
10
+ property! :required_fields, accepts: Array, default: -> { [] }
11
+ property! :renderer_package, accepts: Models::NpmPackage
12
+
13
+ YARN_SERVE_COMMAND = %w(server)
14
+ NPM_SERVE_COMMAND = %w(run-script server)
15
+
16
+ def yarn_serve_command
17
+ YARN_SERVE_COMMAND + options
18
+ end
19
+
20
+ def npm_serve_command
21
+ NPM_SERVE_COMMAND + ["--"] + options
22
+ end
23
+
24
+ private
25
+
26
+ def options
27
+ project = ExtensionProject.current
28
+ api_key = project.env.api_key
29
+
30
+ @serve_options ||= [].tap do |options|
31
+ options << "--port=#{port}" if argo_runtime.accepts_port?
32
+ options << "--shop=#{project.env.shop}" if required_fields.include?(:shop) && argo_runtime.accepts_shop?
33
+ options << "--apiKey=#{api_key}" if required_fields.include?(:api_key) && argo_runtime.accepts_api_key?
34
+ options << "--argoVersion=#{renderer_package.version}" if argo_runtime.accepts_argo_version?
35
+ options << "--uuid=#{project.registration_uuid}" if argo_runtime.accepts_uuid?
36
+ options << "--publicUrl=#{public_url}" if argo_runtime.accepts_tunnel_url?
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -9,7 +9,7 @@ module Extension
9
9
  SCRIPTS_DIRECTORY = "scripts"
10
10
 
11
11
  property! :git_template, accepts: String
12
- property! :dependency_checks, default: []
12
+ property! :dependency_checks, default: -> { [] }
13
13
 
14
14
  def call(directory_name, identifier, context)
15
15
  steps = [
@@ -56,8 +56,10 @@ module Extension
56
56
  },
57
57
  serve: {
58
58
  frame_title: "Serving extension...",
59
+ no_available_ports_found: "No available ports found to run extension.",
59
60
  serve_failure_message: "Failed to run extension code.",
60
61
  serve_missing_information: "Missing shop or api_key.",
62
+ tunnel_already_running: "A tunnel running on another port has been detected. Close the tunnel and try again.",
61
63
  },
62
64
  tunnel: {
63
65
  missing_token: "{{x}} {{red:auth requires a token argument}}. "\
@@ -98,10 +100,8 @@ module Extension
98
100
  node_not_installed: "Node must be installed to create this extension.",
99
101
  version_too_low: "Your node version %s does not meet the minimum required version %s",
100
102
  },
101
- argo_missing_renderer_package_error: "%s Install the missing package and try again.",
102
- argo_renderer_package_invalid_version_error: <<~MESSAGE,
103
- The renderer package version is not a valid SemVer Version (http://semver.org)
104
- MESSAGE
103
+ argo_missing_renderer_package_error: "Extension template references invalid renderer package "\
104
+ "please contact Shopify for help.",
105
105
  yarn_install_error: "Something went wrong while running 'yarn install'. %s.",
106
106
  yarn_run_script_error: "Something went wrong while running script. %s.",
107
107
  },
@@ -117,6 +117,7 @@ module Extension
117
117
  },
118
118
  errors: {
119
119
  unknown_type: "Unknown extension type %s",
120
+ package_not_found: "`%s` package not found.",
120
121
  },
121
122
  }
122
123
 
@@ -0,0 +1,14 @@
1
+ require "semantic/semantic"
2
+
3
+ module Extension
4
+ module Models
5
+ NpmPackage = Struct.new(:name, :version, keyword_init: true) do
6
+ include Comparable
7
+
8
+ def <=>(other)
9
+ return nil unless name == other.name
10
+ Semantic::Version.new(version) <=> Semantic::Version.new(other.version)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -10,8 +10,9 @@ module Extension
10
10
  property! :surface, converts: :to_str
11
11
  property! :renderer_package_name, converts: :to_str
12
12
  property! :git_template, converts: :to_str
13
- property! :required_fields, accepts: Array, default: []
14
- property! :required_shop_beta_flags, accepts: Array, default: []
13
+ property! :required_fields, accepts: Array, default: -> { [] }
14
+ property! :required_shop_beta_flags, accepts: Array, default: -> { [] }
15
+ property! :cli_package_name, accepts: String, converts: :to_str, default: ""
15
16
  end
16
17
 
17
18
  def self.build(feature_set_attributes)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Extension
4
+ module Models
5
+ module SpecificationHandlers
6
+ class CheckoutArgoExtension < Default
7
+ PERMITTED_CONFIG_KEYS = [:metafields, :extension_points]
8
+
9
+ def config(context)
10
+ {
11
+ **Features::ArgoConfig.parse_yaml(context, PERMITTED_CONFIG_KEYS),
12
+ **argo.config(context),
13
+ }
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -42,20 +42,50 @@ module Extension
42
42
  []
43
43
  end
44
44
 
45
- def serve(context)
46
- Features::ArgoServe.new(specification_handler: self, context: context).call
45
+ def choose_port?(context)
46
+ argo_runtime(context).accepts_port?
47
+ end
48
+
49
+ def establish_tunnel?(context)
50
+ argo_runtime(context).accepts_tunnel_url?
51
+ end
52
+
53
+ def serve(context:, port:, tunnel_url:)
54
+ Features::ArgoServe.new(
55
+ specification_handler: self,
56
+ argo_runtime: argo_runtime(context),
57
+ context: context,
58
+ port: port,
59
+ tunnel_url: tunnel_url,
60
+ ).call
47
61
  end
48
62
 
49
63
  def renderer_package(context)
50
64
  argo.renderer_package(context)
51
65
  end
52
66
 
67
+ def argo_runtime(context)
68
+ @argo_runtime ||= Features::ArgoRuntime.new(
69
+ renderer: renderer_package(context),
70
+ cli: cli_package(context),
71
+ )
72
+ end
73
+
74
+ def cli_package(context)
75
+ cli_package_name = specification.features.argo&.cli_package_name
76
+ return unless cli_package_name
77
+
78
+ js_system = ShopifyCli::JsSystem.new(ctx: context)
79
+ Tasks::FindNpmPackages.exactly_one_of(cli_package_name, js_system: js_system)
80
+ .unwrap { |_e| context.abort(context.message("errors.package_not_found", cli_package_name)) }
81
+ end
82
+
53
83
  protected
54
84
 
55
85
  def argo
56
86
  Features::Argo.new(
57
87
  git_template: specification.features.argo.git_template,
58
- renderer_package_name: specification.features.argo.renderer_package_name,
88
+ renderer_package_name: specification.features.argo.renderer_package_name
59
89
  )
60
90
  end
61
91
 
@@ -9,7 +9,7 @@ module Extension
9
9
  property! :last_user_interaction_at, accepts: Time
10
10
  property :context, accepts: String
11
11
  property :location, accepts: String
12
- property :validation_errors, accepts: Models::ValidationError::IS_VALIDATION_ERROR_LIST, default: []
12
+ property :validation_errors, accepts: Models::ValidationError::IS_VALIDATION_ERROR_LIST, default: -> { [] }
13
13
  end
14
14
  end
15
15
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ require "shopify_cli"
3
+ require "socket"
4
+
5
+ module Extension
6
+ module Tasks
7
+ class ChooseNextAvailablePort
8
+ include ShopifyCli::MethodObject
9
+
10
+ property! :from
11
+ property! :to, default: -> { from + 10 }
12
+ property! :host, default: "localhost"
13
+
14
+ def call
15
+ available_port = port_range(from: from, to: to).find { |p| available?(host, p) }
16
+ raise ArgumentError, "Ports between #{from} and #{to} are unavailable" if available_port.nil?
17
+ available_port
18
+ end
19
+
20
+ private
21
+
22
+ def port_range(from:, to:)
23
+ (from..to)
24
+ end
25
+
26
+ def available?(host, port)
27
+ Socket.tcp(host, port, connect_timeout: 1) do |socket|
28
+ socket.close
29
+ false
30
+ end
31
+ rescue Errno::ECONNREFUSED
32
+ true
33
+ end
34
+ end
35
+ end
36
+ end
@@ -42,10 +42,12 @@ module Extension
42
42
  renderer_package_name: "@shopify/argo-admin",
43
43
  required_fields: [:shop, :api_key],
44
44
  required_shop_beta_flags: [:argo_admin_beta],
45
+ cli_package_name: "@shopify/argo-admin-cli",
45
46
  },
46
47
  checkout: {
47
48
  git_template: "https://github.com/Shopify/argo-checkout-template.git",
48
49
  renderer_package_name: "@shopify/argo-checkout",
50
+ cli_package_name: "@shopify/argo-run",
49
51
  },
50
52
  }
51
53
  end
@@ -0,0 +1,106 @@
1
+ module Extension
2
+ module Tasks
3
+ class FindNpmPackages
4
+ include ShopifyCli::MethodObject
5
+
6
+ property! :js_system, accepts: ShopifyCli::JsSystem
7
+ property! :production_only, accepts: [true, false], default: false, reader: :production_only?
8
+
9
+ def self.at_least_one_of(*package_names, **config)
10
+ new(**config).at_least_one_of(*package_names)
11
+ end
12
+
13
+ def self.all(*package_names, **config)
14
+ new(**config).all(*package_names)
15
+ end
16
+
17
+ def self.exactly_one_of(*package_names, **config)
18
+ new(**config).exactly_one_of(*package_names)
19
+ end
20
+
21
+ def all(*package_names)
22
+ call(*package_names) do |found_packages|
23
+ found_package_names = found_packages.map(&:name)
24
+ next found_packages if Set.new(found_package_names) == Set.new(package_names)
25
+ raise PackageResolutionFailed, format(
26
+ "Missing packages: %s",
27
+ (package_names - found_package_names).join(", ")
28
+ )
29
+ end
30
+ end
31
+
32
+ def at_least_one_of(*package_names)
33
+ call(*package_names) do |found_packages|
34
+ found_package_names = found_packages.map(&:name)
35
+ next found_packages unless (found_package_names & package_names).empty?
36
+ raise PackageResolutionFailed, format(
37
+ "Expected at least one of the following packages: %s",
38
+ package_names.join(", ")
39
+ )
40
+ end
41
+ end
42
+
43
+ def exactly_one_of(*package_names)
44
+ call(*package_names) do |found_packages|
45
+ case found_packages.count
46
+ when 0
47
+ raise PackageResolutionFailed, format(
48
+ "Expected one of the following packages: %s",
49
+ package_names.join(", ")
50
+ )
51
+ when 1
52
+ found_packages.first.tap do |found_package|
53
+ next found_package if package_names.include?(found_package.name)
54
+ raise PackageResolutionFailed, format(
55
+ "Expected the following package: %s",
56
+ found_package.name
57
+ )
58
+ end
59
+ else
60
+ raise PackageResolutionFailed, "Found more than one package"
61
+ end
62
+ end
63
+ end
64
+
65
+ def call(*package_names, &validate)
66
+ validate ||= ->(found_packages) { found_packages }
67
+
68
+ unless package_names.all? { |name| name.is_a?(String) }
69
+ raise ArgumentError, "Expected a list of package names"
70
+ end
71
+
72
+ ShopifyCli::Result
73
+ .call(&method(:list_packages))
74
+ .then(&method(:search_packages).curry[package_names])
75
+ .then(&method(:filter_duplicates))
76
+ .then(&validate)
77
+ end
78
+
79
+ def list_packages
80
+ result, error, status =
81
+ js_system.call(yarn: yarn_list, npm: npm_list, capture_response: true)
82
+ raise error unless status.success?
83
+ result
84
+ end
85
+
86
+ def yarn_list
87
+ production_only? ? %w[list --production] : %w[list]
88
+ end
89
+
90
+ def npm_list
91
+ production_only? ? %w[list --prod --depth=1] : %w[list --depth=1]
92
+ end
93
+
94
+ def search_packages(packages, package_list)
95
+ pattern = /(#{packages.join("|")})@(\d.*)$/
96
+ package_list.scan(pattern).map do |(name, version)|
97
+ Models::NpmPackage.new(name: name, version: version.strip)
98
+ end
99
+ end
100
+
101
+ def filter_duplicates(packages)
102
+ packages.reject { |p| p.version.match(/deduped/) }.uniq
103
+ end
104
+ end
105
+ end
106
+ end
@@ -59,21 +59,21 @@ module Node
59
59
  installed: "Installed Heroku CLI",
60
60
  authenticating: "Authenticating with Heroku…",
61
61
  authenticated: "{{v}} Authenticated with Heroku",
62
- authenticated_with_account: "{{v}} Authenticated with Heroku as `%s`",
62
+ authenticated_with_account: "{{v}} Authenticated with Heroku as {{green:%s}}",
63
63
  deploying: "Deploying to Heroku…",
64
64
  deployed: "{{v}} Deployed to Heroku",
65
65
  git: {
66
66
  checking: "Checking git repo…",
67
67
  initialized: "Git repo initialized",
68
68
  what_branch: "What branch would you like to deploy?",
69
- branch_selected: "{{v}} Git branch `%s` selected for deploy",
69
+ branch_selected: "{{v}} Git branch {{green:%s}} selected for deploy",
70
70
  },
71
71
  app: {
72
72
  no_apps_found: "No existing Heroku app found. What would you like to do?",
73
73
  name: "What is your Heroku app’s name?",
74
74
  select: "Specify an existing Heroku app",
75
- selecting: "Selecting Heroku app `%s`…",
76
- selected: "{{v}} Heroku app `%s` selected",
75
+ selecting: "Selecting Heroku app %s",
76
+ selected: "{{v}} Heroku app {{green:%s}} selected",
77
77
  create: "Create a new Heroku app",
78
78
  creating: "Creating new Heroku app…",
79
79
  created: "{{v}} New Heroku app created",
@@ -89,7 +89,7 @@ module Rails
89
89
  downloaded: "Downloaded Heroku CLI",
90
90
  installing: "Installing Heroku CLI...",
91
91
  installed: "Installed Heroku CLI",
92
- authenticated_with_account: "{{v}} Authenticated with Heroku as `%s`",
92
+ authenticated_with_account: "{{v}} Authenticated with Heroku as {{green:%s}}",
93
93
  authenticating: "Authenticating with Heroku...",
94
94
  authenticated: "{{v}} Authenticated with Heroku",
95
95
  deploying: "Deploying to Heroku...",
@@ -109,14 +109,14 @@ module Rails
109
109
  checking: "Checking git repo...",
110
110
  initialized: "Git repo initialized",
111
111
  what_branch: "What branch would you like to deploy?",
112
- branch_selected: "{{v}} Git branch `%s` selected for deploy",
112
+ branch_selected: "{{v}} Git branch {{green:%s}} selected for deploy",
113
113
  },
114
114
  app: {
115
115
  no_apps_found: "No existing Heroku app found. What would you like to do?",
116
116
  name: "What is your Heroku app’s name?",
117
117
  select: "Specify an existing Heroku app",
118
- selecting: "Selecting Heroku app `%s`...",
119
- selected: "{{v}} Heroku app `%s` selected",
118
+ selecting: "Selecting Heroku app %s...",
119
+ selected: "{{v}} Heroku app {{green:%s}} selected",
120
120
  create: "Create a new Heroku app",
121
121
  creating: "Creating new Heroku app...",
122
122
  created: "{{v}} New Heroku app created",
@@ -24,6 +24,10 @@ module Script
24
24
  autoload :ScriptForm, Project.project_filepath("forms/script_form")
25
25
  end
26
26
 
27
+ module Tasks
28
+ autoload :EnsureEnv, Project.project_filepath("tasks/ensure_env")
29
+ end
30
+
27
31
  module Layers
28
32
  module Application
29
33
  autoload :BuildScript, Project.project_filepath("layers/application/build_script")
@@ -44,22 +48,23 @@ module Script
44
48
 
45
49
  module Infrastructure
46
50
  autoload :Errors, Project.project_filepath("layers/infrastructure/errors")
47
- autoload :AssemblyScriptDependencyManager,
48
- Project.project_filepath("layers/infrastructure/assemblyscript_dependency_manager")
49
- autoload :AssemblyScriptProjectCreator,
50
- Project.project_filepath("layers/infrastructure/assemblyscript_project_creator")
51
- autoload :AssemblyScriptTaskRunner, Project.project_filepath("layers/infrastructure/assemblyscript_task_runner")
52
- autoload :AssemblyScriptTsConfig, Project.project_filepath("layers/infrastructure/assemblyscript_tsconfig")
53
- autoload :RustProjectCreator,
54
- Project.project_filepath("layers/infrastructure/rust_project_creator.rb")
55
- autoload :RustTaskRunner, Project.project_filepath("layers/infrastructure/rust_task_runner")
56
-
51
+ autoload :CommandRunner, Project.project_filepath("layers/infrastructure/command_runner")
57
52
  autoload :PushPackageRepository, Project.project_filepath("layers/infrastructure/push_package_repository")
58
53
  autoload :ExtensionPointRepository, Project.project_filepath("layers/infrastructure/extension_point_repository")
59
- autoload :ProjectCreator, Project.project_filepath("layers/infrastructure/project_creator")
60
54
  autoload :ScriptProjectRepository, Project.project_filepath("layers/infrastructure/script_project_repository")
61
55
  autoload :ScriptService, Project.project_filepath("layers/infrastructure/script_service")
62
- autoload :TaskRunner, Project.project_filepath("layers/infrastructure/task_runner")
56
+
57
+ module Languages
58
+ autoload :AssemblyScriptProjectCreator,
59
+ Project.project_filepath("layers/infrastructure/languages/assemblyscript_project_creator")
60
+ autoload :AssemblyScriptTaskRunner,
61
+ Project.project_filepath("layers/infrastructure/languages/assemblyscript_task_runner")
62
+ autoload :ProjectCreator, Project.project_filepath("layers/infrastructure/languages/project_creator")
63
+ autoload :RustProjectCreator,
64
+ Project.project_filepath("layers/infrastructure/languages/rust_project_creator.rb")
65
+ autoload :RustTaskRunner, Project.project_filepath("layers/infrastructure/languages/rust_task_runner")
66
+ autoload :TaskRunner, Project.project_filepath("layers/infrastructure/languages/task_runner")
67
+ end
63
68
  end
64
69
  end
65
70