use_packs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +71 -0
  3. data/bin/packs +10 -0
  4. data/bin/rubocop +29 -0
  5. data/bin/tapioca +29 -0
  6. data/lib/use_packs/cli.rb +127 -0
  7. data/lib/use_packs/code_ownership_post_processor.rb +58 -0
  8. data/lib/use_packs/configuration.rb +61 -0
  9. data/lib/use_packs/default_user_event_logger.rb +7 -0
  10. data/lib/use_packs/logging.rb +37 -0
  11. data/lib/use_packs/per_file_processor_interface.rb +18 -0
  12. data/lib/use_packs/private/file_move_operation.rb +80 -0
  13. data/lib/use_packs/private/interactive_cli/pack_selector.rb +34 -0
  14. data/lib/use_packs/private/interactive_cli/team_selector.rb +35 -0
  15. data/lib/use_packs/private/interactive_cli/use_cases/add_dependency.rb +30 -0
  16. data/lib/use_packs/private/interactive_cli/use_cases/check.rb +25 -0
  17. data/lib/use_packs/private/interactive_cli/use_cases/create.rb +27 -0
  18. data/lib/use_packs/private/interactive_cli/use_cases/get_info.rb +74 -0
  19. data/lib/use_packs/private/interactive_cli/use_cases/interface.rb +34 -0
  20. data/lib/use_packs/private/interactive_cli/use_cases/lint_package_yml.rb +26 -0
  21. data/lib/use_packs/private/interactive_cli/use_cases/make_public.rb +34 -0
  22. data/lib/use_packs/private/interactive_cli/use_cases/move.rb +36 -0
  23. data/lib/use_packs/private/interactive_cli/use_cases/nest.rb +31 -0
  24. data/lib/use_packs/private/interactive_cli/use_cases/query.rb +51 -0
  25. data/lib/use_packs/private/interactive_cli/use_cases/regenerate_rubocop_todo.rb +26 -0
  26. data/lib/use_packs/private/interactive_cli/use_cases/rename.rb +34 -0
  27. data/lib/use_packs/private/interactive_cli/use_cases/update_deprecations.rb +25 -0
  28. data/lib/use_packs/private/interactive_cli/use_cases/validate.rb +25 -0
  29. data/lib/use_packs/private/interactive_cli/use_cases/visualize.rb +44 -0
  30. data/lib/use_packs/private/interactive_cli.rb +52 -0
  31. data/lib/use_packs/private/pack_relationship_analyzer.rb +135 -0
  32. data/lib/use_packs/private/packwerk_wrapper/offenses_aggregator_formatter.rb +34 -0
  33. data/lib/use_packs/private/packwerk_wrapper.rb +71 -0
  34. data/lib/use_packs/private.rb +453 -0
  35. data/lib/use_packs/rubocop_post_processor.rb +67 -0
  36. data/lib/use_packs/spring_command.rb +28 -0
  37. data/lib/use_packs/user_event_logger.rb +259 -0
  38. data/lib/use_packs.rb +298 -0
  39. metadata +351 -0
@@ -0,0 +1,259 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module UserEventLogger
5
+ extend T::Sig
6
+ extend T::Helpers
7
+
8
+ abstract!
9
+
10
+ sig { params(pack_name: String).returns(String) }
11
+ def before_create_pack(pack_name)
12
+ <<~MSG
13
+ You are creating a pack, which is great. Check out #{documentation_link} for more info!
14
+ MSG
15
+ end
16
+
17
+ sig { params(pack_name: String).returns(String) }
18
+ def after_create_pack(pack_name)
19
+ <<~MSG
20
+ Your next steps might be:
21
+
22
+ 1) Move files into your pack with `bin/packs move #{pack_name} path/to/file.rb`
23
+
24
+ 2) Run `bin/packwerk update-deprecations` to update the violations. Make sure to run `spring stop` if you've added new load paths (new top-level directories) in your pack.
25
+
26
+ 3) Expose public API in #{pack_name}/app/public. Try `bin/packs make_public #{pack_name}/path/to/file.rb`
27
+
28
+ 4) Update your readme at #{pack_name}/README.md
29
+ MSG
30
+ end
31
+
32
+ sig { params(pack_name: String).returns(String) }
33
+ def before_move_to_pack(pack_name)
34
+ <<~MSG
35
+ You are moving a file to a pack, which is great. Check out #{documentation_link} for more info!
36
+ MSG
37
+ end
38
+
39
+ sig { params(pack_name: String).returns(String) }
40
+ def after_move_to_pack(pack_name)
41
+ <<~MSG
42
+ Your next steps might be:
43
+
44
+ 1) Run `bin/packwerk update-deprecations` to update the violations. Make sure to run `spring stop` if you've added new load paths (new top-level directories) in your pack.
45
+
46
+ 2) Touch base with each team who owns files involved in this move
47
+
48
+ 3) Expose public API in #{pack_name}/app/public. Try `bin/packs make_public #{pack_name}/path/to/file.rb`
49
+
50
+ 4) Update your readme at #{pack_name}/README.md
51
+ MSG
52
+ end
53
+
54
+ sig { returns(String) }
55
+ def before_make_public
56
+ <<~MSG
57
+ You are moving some files into public API. See #{documentation_link} for other utilities!
58
+ MSG
59
+ end
60
+
61
+ sig { returns(String) }
62
+ def after_make_public
63
+ <<~MSG
64
+ Your next steps might be:
65
+
66
+ 1) Run `bin/packwerk update-deprecations` to update the violations. Make sure to run `spring stop` if you've added new load paths (new top-level directories) in your pack.
67
+
68
+ 2) Work to migrate clients of private API to your new public API
69
+
70
+ 3) Update your README at packs/your_package_name/README.md
71
+ MSG
72
+ end
73
+
74
+ sig { params(pack_name: String).returns(String) }
75
+ def before_add_dependency(pack_name)
76
+ <<~MSG
77
+ You are adding a dependency. See #{documentation_link} for other utilities!
78
+ MSG
79
+ end
80
+
81
+ sig { params(pack_name: String).returns(String) }
82
+ def after_add_dependency(pack_name)
83
+ <<~MSG
84
+ Your next steps might be:
85
+
86
+ 1) Run `bin/packwerk validate` to ensure you haven't introduced a cyclic dependency
87
+
88
+ 2) Run `bin/packwerk update-deprecations` to update the violations.
89
+ MSG
90
+ end
91
+
92
+ sig { params(pack_name: String).returns(String) }
93
+ def before_move_to_parent(pack_name)
94
+ <<~MSG
95
+ You are moving one pack to be a child of a different pack. Check out #{documentation_link} for more info!
96
+ MSG
97
+ end
98
+
99
+ sig { params(pack_name: String).returns(String) }
100
+ def after_move_to_parent(pack_name)
101
+ <<~MSG
102
+ Your next steps might be:
103
+
104
+ 1) Delete the old pack when things look good: `rm -rf #{pack_name}`
105
+
106
+ 2) Run `bin/packwerk update-deprecations` to update the violations. Make sure to run `spring stop` first.
107
+ MSG
108
+ end
109
+
110
+ sig { params(pack_name: String).returns(String) }
111
+ def on_create_public_directory_todo(pack_name)
112
+ <<~MSG
113
+ This directory holds your public API!
114
+
115
+ Any classes, constants, or modules that you want other packs to use and you intend to support should go in here.
116
+ Anything that is considered private should go in other folders.
117
+
118
+ If another pack uses classes, constants, or modules that are not in your public folder, it will be considered a "privacy violation" by packwerk.
119
+ You can prevent other packs from using private API by using packwerk.
120
+
121
+ Want to find how your private API is being used today?
122
+ Try running: `bin/packs list_top_privacy_violations #{pack_name}`
123
+
124
+ Want to move something into this folder?
125
+ Try running: `bin/packs make_public #{pack_name}/path/to/file.rb`
126
+
127
+ One more thing -- feel free to delete this file and replace it with a README.md describing your package in the main package directory.
128
+
129
+ See #{documentation_link} for more info!
130
+ MSG
131
+ end
132
+
133
+ sig { params(pack_name: String).returns(String) }
134
+ def on_create_readme_todo(pack_name)
135
+ <<~MSG
136
+ Welcome to `#{pack_name}`!
137
+
138
+ If you're the author, please consider replacing this file with a README.md, which may contain:
139
+ - What your pack is and does
140
+ - How you expect people to use your pack
141
+ - Example usage of your pack's public API (which lives in `#{pack_name}/app/public`)
142
+ - Limitations, risks, and important considerations of usage
143
+ - How to get in touch with eng and other stakeholders for questions or issues pertaining to this pack (note: it is recommended to add ownership in `#{pack_name}/package.yml` under the `owner` metadata key)
144
+ - What SLAs/SLOs (service level agreements/objectives), if any, your package provides
145
+ - When in doubt, keep it simple
146
+ - Anything else you may want to include!
147
+
148
+ README.md files are under version control and should change as your public API changes.#{' '}
149
+
150
+ See #{documentation_link} for more info!
151
+ MSG
152
+ end
153
+
154
+ sig { params(pack_name: T.nilable(String), limit: Integer).returns(String) }
155
+ def before_list_top_dependency_violations(pack_name, limit)
156
+ if pack_name.nil?
157
+ pack_specific_content = <<~PACK_CONTENT
158
+ You are listing top #{limit} dependency violations for all packs. See #{documentation_link} for other utilities!
159
+ Pass in a limit to display more or less, e.g. `use_packs list_top_dependency_violations #{pack_name} -l 1000`
160
+
161
+ This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
162
+ Anything not in pack_name/app/public is considered private API.
163
+ PACK_CONTENT
164
+ else
165
+ pack_specific_content = <<~PACK_CONTENT
166
+ You are listing top #{limit} dependency violations for #{pack_name}. See #{documentation_link} for other utilities!
167
+ Pass in a limit to display more or less, e.g. `bin/packs list_top_dependency_violations #{pack_name} -l 1000`
168
+
169
+ This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
170
+ Anything not in #{pack_name}/app/public is considered private API.
171
+ PACK_CONTENT
172
+ end
173
+
174
+ <<~MSG
175
+ #{pack_specific_content}
176
+
177
+ When using this script, ask yourself some questions like:
178
+ - What do I want to support?
179
+ - What do I *not* want to support?
180
+ - Which direction should a dependency go?
181
+ - What packs should depend on you, and what packs should not depend on you?
182
+ - Would it be simpler if other packs only depended on interfaces to your pack rather than implementation?
183
+
184
+ Looking at dependency violations can help guide the development of your public API, but it is just the beginning!
185
+
186
+ The script will output in the following format:
187
+
188
+ SomeConstant # This is the name of a class, constant, or module defined in your pack, outside of app/public
189
+ - Total Count: 5 # This is the total number of unstated uses of this outside your pack
190
+ - By package: # This is a breakdown of the use of this constant by other packages
191
+ # This is the number of files in this pack that this constant is used.
192
+ # Check `packs/other_pack_a/deprecated_references.yml` under the '#{pack_name}'.'SomeConstant' key to see where this constant is used
193
+ - packs/other_pack_a: 3
194
+ - packs/other_pack_b: 2
195
+ SomeClass # This is the second most violated class, constant, or module defined in your pack
196
+ - Total Count: 2
197
+ - By package:
198
+ - packs/other_pack_a: 1
199
+ - packs/other_pack_b: 1
200
+ MSG
201
+ end
202
+
203
+ sig { params(pack_name: T.nilable(String), limit: Integer).returns(String) }
204
+ def before_list_top_privacy_violations(pack_name, limit)
205
+ if pack_name.nil?
206
+ pack_specific_content = <<~PACK_CONTENT
207
+ You are listing top #{limit} privacy violations for all packs. See #{documentation_link} for other utilities!
208
+ Pass in a limit to display more or less, e.g. `bin/packs list_top_privacy_violations #{pack_name} -l 1000`
209
+
210
+ This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
211
+ Anything not in pack_name/app/public is considered private API.
212
+ PACK_CONTENT
213
+ else
214
+ pack_specific_content = <<~PACK_CONTENT
215
+ You are listing top #{limit} privacy violations for #{pack_name}. See #{documentation_link} for other utilities!
216
+ Pass in a limit to display more or less, e.g. `bin/packs list_top_privacy_violations #{pack_name} -l 1000`
217
+
218
+ This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
219
+ Anything not in #{pack_name}/app/public is considered private API.
220
+ PACK_CONTENT
221
+ end
222
+
223
+ <<~MSG
224
+ #{pack_specific_content}
225
+
226
+ When using this script, ask yourself some questions like:
227
+ - What do I want to support?
228
+ - What do I *not* want to support?
229
+ - What is considered simply an implementation detail, and what is essential to the behavior of my pack?
230
+ - What is a simple, minimialistic API for clients to engage with the behavior of your pack?
231
+ - How do I ensure my public API is not coupled to specific client's use cases?
232
+
233
+ Looking at privacy violations can help guide the development of your public API, but it is just the beginning!
234
+
235
+ The script will output in the following format:
236
+
237
+ SomeConstant # This is the name of a class, constant, or module defined in your pack, outside of app/public
238
+ - Total Count: 5 # This is the total number of uses of this outside your pack
239
+ - By package: # This is a breakdown of the use of this constant by other packages
240
+ # This is the number of files in this pack that this constant is used.
241
+ # Check `packs/other_pack_a/deprecated_references.yml` under the '#{pack_name}'.'SomeConstant' key to see where this constant is used
242
+ - packs/other_pack_a: 3
243
+ - packs/other_pack_b: 2
244
+ SomeClass # This is the second most violated class, constant, or module defined in your pack
245
+ - Total Count: 2
246
+ - By package:
247
+ - packs/other_pack_a: 1
248
+ - packs/other_pack_b: 1
249
+
250
+ Lastly, remember you can use `bin/packs make_public #{pack_name}/path/to/file.rb` to make your class, constant, or module public API.
251
+ MSG
252
+ end
253
+
254
+ sig { returns(String) }
255
+ def documentation_link
256
+ 'https://github.com/rubyatscale/use_packs#readme'
257
+ end
258
+ end
259
+ end
data/lib/use_packs.rb ADDED
@@ -0,0 +1,298 @@
1
+ # typed: strict
2
+
3
+ # Ruby internal requires
4
+ require 'fileutils'
5
+
6
+ # External gem requires
7
+ require 'colorized_string'
8
+
9
+ # Internal gem requires
10
+ require 'parse_packwerk'
11
+ require 'code_teams'
12
+ require 'code_ownership'
13
+ require 'rubocop-packs'
14
+
15
+ # Private implementation requires
16
+ require 'use_packs/private'
17
+ require 'use_packs/per_file_processor_interface'
18
+ require 'use_packs/rubocop_post_processor'
19
+ require 'use_packs/code_ownership_post_processor'
20
+ require 'use_packs/logging'
21
+ require 'use_packs/configuration'
22
+ require 'use_packs/cli'
23
+
24
+ module UsePacks
25
+ extend T::Sig
26
+
27
+ PERMITTED_PACK_LOCATIONS = T.let(%w[
28
+ gems
29
+ components
30
+ packs
31
+ ], T::Array[String])
32
+
33
+ sig { void }
34
+ def self.start_interactive_mode!
35
+ Private::InteractiveCli.start!
36
+ end
37
+
38
+ sig do
39
+ params(
40
+ pack_name: String,
41
+ enforce_privacy: T::Boolean,
42
+ enforce_dependencies: T.nilable(T::Boolean),
43
+ team: T.nilable(CodeTeams::Team)
44
+ ).void
45
+ end
46
+ def self.create_pack!(
47
+ pack_name:,
48
+ enforce_privacy: true,
49
+ enforce_dependencies: nil,
50
+ team: nil
51
+ )
52
+ Private.create_pack!(
53
+ pack_name: pack_name,
54
+ enforce_privacy: enforce_privacy,
55
+ enforce_dependencies: enforce_dependencies,
56
+ team: team
57
+ )
58
+ end
59
+
60
+ sig do
61
+ params(
62
+ pack_name: String,
63
+ paths_relative_to_root: T::Array[String],
64
+ per_file_processors: T::Array[PerFileProcessorInterface]
65
+ ).void
66
+ end
67
+ def self.move_to_pack!(
68
+ pack_name:,
69
+ paths_relative_to_root: [],
70
+ per_file_processors: []
71
+ )
72
+ Logging.section('👋 Hi!') do
73
+ intro = UsePacks.config.user_event_logger.before_move_to_pack(pack_name)
74
+ Logging.print_bold_green(intro)
75
+ end
76
+
77
+ Private.move_to_pack!(
78
+ pack_name: pack_name,
79
+ paths_relative_to_root: paths_relative_to_root,
80
+ per_file_processors: per_file_processors
81
+ )
82
+
83
+ Logging.section('Next steps') do
84
+ next_steps = UsePacks.config.user_event_logger.after_move_to_pack(pack_name)
85
+ Logging.print_bold_green(next_steps)
86
+ end
87
+ end
88
+
89
+ sig do
90
+ params(
91
+ paths_relative_to_root: T::Array[String],
92
+ per_file_processors: T::Array[PerFileProcessorInterface]
93
+ ).void
94
+ end
95
+ def self.make_public!(
96
+ paths_relative_to_root: [],
97
+ per_file_processors: []
98
+ )
99
+ Logging.section('Making files public') do
100
+ intro = UsePacks.config.user_event_logger.before_make_public
101
+ Logging.print_bold_green(intro)
102
+ end
103
+
104
+ Private.make_public!(
105
+ paths_relative_to_root: paths_relative_to_root,
106
+ per_file_processors: per_file_processors
107
+ )
108
+
109
+ Logging.section('Next steps') do
110
+ next_steps = UsePacks.config.user_event_logger.after_make_public
111
+ Logging.print_bold_green(next_steps)
112
+ end
113
+ end
114
+
115
+ sig do
116
+ params(
117
+ pack_name: String,
118
+ dependency_name: String
119
+ ).void
120
+ end
121
+ def self.add_dependency!(
122
+ pack_name:,
123
+ dependency_name:
124
+ )
125
+ Logging.section('Adding a dependency') do
126
+ intro = UsePacks.config.user_event_logger.before_add_dependency(pack_name)
127
+ Logging.print_bold_green(intro)
128
+ end
129
+
130
+ Private.add_dependency!(
131
+ pack_name: pack_name,
132
+ dependency_name: dependency_name
133
+ )
134
+
135
+ Logging.section('Next steps') do
136
+ next_steps = UsePacks.config.user_event_logger.after_add_dependency(pack_name)
137
+ Logging.print_bold_green(next_steps)
138
+ end
139
+ end
140
+
141
+ sig do
142
+ params(
143
+ pack_name: String,
144
+ parent_name: String,
145
+ per_file_processors: T::Array[PerFileProcessorInterface]
146
+ ).void
147
+ end
148
+ def self.move_to_parent!(
149
+ pack_name:,
150
+ parent_name:,
151
+ per_file_processors: []
152
+ )
153
+ Logging.section('👋 Hi!') do
154
+ intro = UsePacks.config.user_event_logger.before_move_to_parent(pack_name)
155
+ Logging.print_bold_green(intro)
156
+ end
157
+
158
+ Private.move_to_parent!(
159
+ pack_name: pack_name,
160
+ parent_name: parent_name,
161
+ per_file_processors: per_file_processors
162
+ )
163
+
164
+ Logging.section('Next steps') do
165
+ next_steps = UsePacks.config.user_event_logger.after_move_to_parent(pack_name)
166
+
167
+ Logging.print_bold_green(next_steps)
168
+ end
169
+ end
170
+
171
+ sig do
172
+ params(
173
+ pack_name: T.nilable(String),
174
+ limit: Integer
175
+ ).void
176
+ end
177
+ def self.list_top_privacy_violations(
178
+ pack_name:,
179
+ limit:
180
+ )
181
+ Private::PackRelationshipAnalyzer.list_top_privacy_violations(
182
+ pack_name,
183
+ limit
184
+ )
185
+ end
186
+
187
+ sig do
188
+ params(
189
+ pack_name: T.nilable(String),
190
+ limit: Integer
191
+ ).void
192
+ end
193
+ def self.list_top_dependency_violations(
194
+ pack_name:,
195
+ limit:
196
+ )
197
+ Private::PackRelationshipAnalyzer.list_top_dependency_violations(
198
+ pack_name,
199
+ limit
200
+ )
201
+ end
202
+
203
+ sig do
204
+ params(
205
+ file: String,
206
+ find: Pathname,
207
+ replace_with: Pathname
208
+ ).void
209
+ end
210
+ def self.replace_in_file(file:, find:, replace_with:)
211
+ Private.replace_in_file(
212
+ file: file,
213
+ find: find,
214
+ replace_with: replace_with
215
+ )
216
+ end
217
+
218
+ sig { void }
219
+ def self.bust_cache!
220
+ Private.bust_cache!
221
+ end
222
+
223
+ #
224
+ # execute_command is like `run` except it does not `exit`
225
+ #
226
+ sig { params(argv: T.untyped, formatter: T.nilable(Packwerk::OffensesFormatter)).void }
227
+ def self.execute(argv, formatter = nil)
228
+ Private::PackwerkWrapper.with_safe_exit_if_no_files_found do
229
+ Private::PackwerkWrapper.packwerk_cli(formatter).execute_command(argv)
230
+ end
231
+ end
232
+
233
+ sig { params(files: T::Array[String]).returns(T::Array[Packwerk::ReferenceOffense]) }
234
+ def self.get_offenses_for_files(files)
235
+ formatter = Private::PackwerkWrapper::OffensesAggregatorFormatter.new
236
+ Private::PackwerkWrapper.packwerk_cli_execute_safely(['check', *files], formatter)
237
+ formatter.aggregated_offenses.compact
238
+ end
239
+
240
+ sig { params(files: T::Array[String]).returns(T::Array[Packwerk::ReferenceOffense]) }
241
+ def self.get_offenses_for_files_by_package(files)
242
+ packages = Private::PackwerkWrapper.package_names_for_files(files)
243
+ argv = ['check', '--packages', packages.join(',')]
244
+ formatter = Private::PackwerkWrapper::OffensesAggregatorFormatter.new
245
+ Private::PackwerkWrapper.packwerk_cli_execute_safely(argv, formatter)
246
+ formatter.aggregated_offenses.compact
247
+ end
248
+
249
+ sig { void }
250
+ def self.lint_deprecated_references_yml_files!
251
+ contents_before = Private.get_deprecated_references_contents
252
+ UsePacks.execute(['update-deprecations'])
253
+ contents_after = Private.get_deprecated_references_contents
254
+ diff = Private.diff_deprecated_references_yml(contents_before, contents_after)
255
+
256
+ if diff == ''
257
+ # No diff generated by `update-deprecations`
258
+ exit 0
259
+ else
260
+ output = <<~OUTPUT
261
+ All `deprecated_references.yml` files must be up-to-date and that no diff is generated when running `bin/packwerk update-deprecations`.
262
+ This helps ensure a high quality signal in other engineers' PRs when inspecting new violations by ensuring there are no unrelated changes.
263
+
264
+ There are three main reasons there may be a diff:
265
+ 1) Most likely, you may have stale violations, meaning there are old violations that no longer apply.
266
+ 2) You may have some sort of auto-formatter set up somewhere (e.g. something that reformats YML files) that is, for example, changing double quotes to single quotes. Ensure this is turned off for these auto-generated files.
267
+ 3) You may have edited these files manually. It's recommended to use the `bin/packwerk update-deprecations` command to make changes to `deprecated_references.yml` files.
268
+
269
+ In all cases, you can run `bin/packwerk update-deprecations` to update these files.
270
+
271
+ Here is the diff generated after running `update-deprecations`:
272
+ ```
273
+ #{diff}
274
+ ```
275
+
276
+ OUTPUT
277
+
278
+ puts output
279
+ UsePacks.config.on_deprecated_references_lint_failure.call(output)
280
+
281
+ exit 1
282
+ end
283
+ end
284
+
285
+ sig { params(packs: T::Array[ParsePackwerk::Package]).void }
286
+ def self.lint_package_yml_files!(packs)
287
+ packs.each do |p|
288
+ new_package = ParsePackwerk::Package.new(
289
+ name: p.name,
290
+ enforce_privacy: p.enforce_privacy,
291
+ enforce_dependencies: p.enforce_dependencies,
292
+ dependencies: p.dependencies.uniq.sort,
293
+ metadata: p.metadata
294
+ )
295
+ ParsePackwerk.write_package_yml!(new_package)
296
+ end
297
+ end
298
+ end