packs-specification 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 71b2b5bb84c327f15708d53b5a3da1739fb14e298efa58801409f79d5b17459d
4
+ data.tar.gz: 124bdf2eaa370451d0ab1d7fc86a1217f6b9bef4d7eedc2eb63f23a67d45f06f
5
+ SHA512:
6
+ metadata.gz: ae8c0af2ba916b8615cfc8c843c2894dac61969f65f97d5683e66a5db0df65e3c80b7731cd8388dc05fcd49ecef7e51577dba26f67e049803f8794d7cf13503c
7
+ data.tar.gz: f251bacc060817d2247523a1e373545aac4ced7917a34b8da5ea0ce97be6b62af1615a04002ff35a136507f807f7c9312c6d1ab7a304136bcb5b5b02f7f0bb86
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # packs-specification
2
+
3
+ Welcome to `packs-specification`! `packs` are a simple ruby specification for an extensible packaging system to help modularize Ruby applications.
4
+
5
+ A `pack` (short for `package`) is a folder of Ruby code with a `package.yml` at the root that is intended to represent a well-modularized domain, and the rest of the [rubyatscale](https://github.com/rubyatscale) ecosystem is intended to help make the boundaries between a pack and any other more clear.
6
+
7
+ # Configuration
8
+ By default, this library will look for `packs` in the folder `packs/*/package.yml` (as well as nested packs at `packs/*/*/package.yml`). To change where `packs` are located, create a `packs.yml` file:
9
+ ```
10
+ pack_paths:
11
+ - "{packs,utilities,deprecated}/*" # packs with multiple roots!
12
+ - "{packs,utilities,deprecated}/*/*" # nested packs!
13
+ - gems/* # gems can be packs too!
14
+ ```
15
+
16
+ Here are some example integrations with `packs`:
17
+ - [`packs-rails`](https://github.com/rubyatscale/packs-rails) can be used to integrate `packs` into your `rails` application
18
+ - [`rubocop-packs`](https://github.com/rubyatscale/rubocop-packs) contains cops to improve boundaries around `packs`
19
+ - [`packwerk`](https://github.com/Shopify/packwerk) and [`packwerk-extensions`](https://github.com/rubyatscale/packwerk-extensions) help you describe and constrain your package graph in terms of dependencies between packs and pack public API
20
+ - [`code_ownership`](https://github.com/rubyatscale/code_ownership) gives your application the capability to determine the owner of a pack
21
+ - [`use_packs`](https://github.com/rubyatscale/use_packs) gives a CLI, `bin/packs`, that makes it easy to create new packs, move files between packs, and more.
22
+ - [`pack_stats`](https://github.com/rubyatscale/pack_stats) makes it easy to send metrics about pack adoption and modularization to your favorite metrics provider, such as DataDog (which has built-in support).
23
+
24
+ # How is a pack different from a gem?
25
+ A ruby [`gem`](https://guides.rubygems.org/what-is-a-gem/) is the Ruby community solution for packaging and distributing Ruby code. A gem is a great place to start new projects, and a great end state for code that's been extracted from an existing codebase. `packs` are intended to help gradually modularize an application that has some conceptual boundaries, but is not yet ready to be factored into gems.
data/lib/packs/pack.rb ADDED
@@ -0,0 +1,48 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ class Pack < T::Struct
5
+ extend T::Sig
6
+
7
+ const :name, String
8
+ const :path, Pathname
9
+ const :relative_path, Pathname
10
+ const :raw_hash, T::Hash[T.untyped, T.untyped]
11
+
12
+ sig { params(package_yml_absolute_path: Pathname).returns(Pack) }
13
+ def self.from(package_yml_absolute_path)
14
+ package_loaded_yml = YAML.load_file(package_yml_absolute_path)
15
+ path = package_yml_absolute_path.dirname
16
+ relative_path = path.relative_path_from(Private.root)
17
+ package_name = relative_path.cleanpath.to_s
18
+
19
+ Pack.new(
20
+ name: package_name,
21
+ path: path,
22
+ relative_path: relative_path,
23
+ raw_hash: package_loaded_yml || {}
24
+ )
25
+ end
26
+
27
+ sig { params(relative: T::Boolean).returns(Pathname) }
28
+ def yml(relative: true)
29
+ path_to_use = relative ? relative_path : path
30
+ path_to_use.join(PACKAGE_FILE).cleanpath
31
+ end
32
+
33
+ sig { returns(String) }
34
+ def last_name
35
+ relative_path.basename.to_s
36
+ end
37
+
38
+ sig { returns(T::Boolean) }
39
+ def is_gem?
40
+ @is_gem ||= T.let(relative_path.glob('*.gemspec').any?, T.nilable(T::Boolean))
41
+ end
42
+
43
+ sig { returns(T::Hash[T.untyped, T.untyped]) }
44
+ def metadata
45
+ raw_hash['metadata'] || {}
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ class Configuration < T::Struct
6
+ extend T::Sig
7
+ CONFIGURATION_PATHNAME = T.let(Pathname.new('packs.yml'), Pathname)
8
+
9
+ DEFAULT_PACK_PATHS = T.let([
10
+ 'packs/*',
11
+ 'packs/*/*'
12
+ ], T::Array[String])
13
+
14
+ prop :pack_paths, T::Array[String]
15
+
16
+ sig { returns(Configuration) }
17
+ def self.fetch
18
+ config_hash = CONFIGURATION_PATHNAME.exist? ? YAML.load_file(CONFIGURATION_PATHNAME) : {}
19
+
20
+ new(
21
+ pack_paths: pack_paths(config_hash)
22
+ )
23
+ end
24
+
25
+ sig { params(config_hash: T::Hash[T.untyped, T.untyped]).returns(T::Array[String]) }
26
+ def self.pack_paths(config_hash)
27
+ specified_pack_paths = config_hash['pack_paths']
28
+ if specified_pack_paths.nil?
29
+ DEFAULT_PACK_PATHS.dup
30
+ else
31
+ Array(specified_pack_paths)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+
3
+ require 'packs/private/configuration'
4
+
5
+ module Packs
6
+ module Private
7
+ extend T::Sig
8
+
9
+ sig { returns(Pathname) }
10
+ def self.root
11
+ Pathname.pwd
12
+ end
13
+ end
14
+
15
+ private_constant :Private
16
+ end
@@ -0,0 +1,33 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module FixtureHelper
5
+ extend T::Sig
6
+
7
+ sig { params(path: String, content: String).returns(String) }
8
+ def write_file(path, content = '')
9
+ pathname = Pathname.pwd.join(path)
10
+ FileUtils.mkdir_p(pathname.dirname)
11
+ pathname.write(content)
12
+ path
13
+ end
14
+
15
+ sig do
16
+ params(
17
+ pack_name: String,
18
+ config: T::Hash[T.untyped, T.untyped]
19
+ ).void
20
+ end
21
+ def write_pack(
22
+ pack_name,
23
+ config = {}
24
+ )
25
+ path = Pathname.pwd.join(pack_name).join('package.yml')
26
+ write_file(path.to_s, YAML.dump(config))
27
+ end
28
+
29
+ sig { params(path: String).void }
30
+ def delete_app_file(path)
31
+ File.delete(path)
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'fixture_helper'
2
+
3
+ RSpec.configure do |config|
4
+ config.include FixtureHelper
5
+
6
+ config.before do
7
+ # We bust_cache always because each test may write its own packs
8
+ Packs.bust_cache!
9
+ end
10
+
11
+ # Eventually, we could make this opt-in via metadata so someone can use this support without affecting all their tests.
12
+ config.around do |example|
13
+ prefix = [File.basename($0), Process.pid].join('-') # rubocop:disable Style/SpecialGlobalVars
14
+ tmpdir = Dir.mktmpdir(prefix)
15
+ Dir.chdir(tmpdir) do
16
+ example.run
17
+ end
18
+ ensure
19
+ FileUtils.rm_rf(tmpdir)
20
+ end
21
+ end
data/lib/packs.rb ADDED
@@ -0,0 +1,80 @@
1
+ # typed: strict
2
+
3
+ require 'yaml'
4
+ require 'pathname'
5
+ require 'sorbet-runtime'
6
+ require 'packs/pack'
7
+ require 'packs/private'
8
+
9
+ module Packs
10
+ PACKAGE_FILE = T.let('package.yml'.freeze, String)
11
+
12
+ class << self
13
+ extend T::Sig
14
+
15
+ sig { returns(T::Array[Pack]) }
16
+ def all
17
+ packs_by_name.values
18
+ end
19
+
20
+ sig { params(name: String).returns(T.nilable(Pack)) }
21
+ def find(name)
22
+ packs_by_name[name]
23
+ end
24
+
25
+ sig { params(file_path: T.any(Pathname, String)).returns(T.nilable(Pack)) }
26
+ def for_file(file_path)
27
+ path_string = file_path.to_s
28
+ @for_file = T.let(@for_file, T.nilable(T::Hash[String, T.nilable(Pack)]))
29
+ @for_file ||= {}
30
+ @for_file[path_string] ||= all.find { |package| path_string.start_with?("#{package.name}/") || path_string == package.name }
31
+ end
32
+
33
+ sig { void }
34
+ def bust_cache!
35
+ @packs_by_name = nil
36
+ @config = nil
37
+ @for_file = nil
38
+ end
39
+
40
+ sig { returns(Private::Configuration) }
41
+ def config
42
+ @config = T.let(@config, T.nilable(Private::Configuration))
43
+ @config ||= Private::Configuration.fetch
44
+ end
45
+
46
+ sig { params(blk: T.proc.params(arg0: Private::Configuration).void).void }
47
+ def configure(&blk)
48
+ # If packs.yml is being used, then ignore direct configuration.
49
+ # This is only a stop-gap to permit Stimpack users to more easily migrate
50
+ # to packs.yml
51
+ return if Private::Configuration::CONFIGURATION_PATHNAME.exist?
52
+
53
+ yield(config)
54
+ end
55
+
56
+ private
57
+
58
+ sig { returns(T::Hash[String, Pack]) }
59
+ def packs_by_name
60
+ @packs_by_name = T.let(@packs_by_name, T.nilable(T::Hash[String, Pack]))
61
+ @packs_by_name ||= begin
62
+ all_packs = package_glob_patterns.map do |path|
63
+ Pack.from(path)
64
+ end
65
+
66
+ # We want to match more specific paths first so for_file works correctly.
67
+ sorted_packages = all_packs.sort_by { |package| -package.name.length }
68
+ sorted_packages.to_h { |p| [p.name, p] }
69
+ end
70
+ end
71
+
72
+ sig { returns(T::Array[Pathname]) }
73
+ def package_glob_patterns
74
+ absolute_root = Private.root
75
+ config.pack_paths.flat_map do |pack_path|
76
+ Pathname.glob(absolute_root.join(pack_path).join(PACKAGE_FILE))
77
+ end
78
+ end
79
+ end
80
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: packs-specification
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ platform: ruby
6
+ authors:
7
+ - Gusto Engineers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-08-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sorbet-runtime
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.16
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.16
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sorbet
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: tapioca
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: The specification for packs in the `rubyatscale` ecosystem.
112
+ email:
113
+ - dev@gusto.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - README.md
119
+ - lib/packs.rb
120
+ - lib/packs/pack.rb
121
+ - lib/packs/private.rb
122
+ - lib/packs/private/configuration.rb
123
+ - lib/packs/rspec/fixture_helper.rb
124
+ - lib/packs/rspec/support.rb
125
+ homepage: https://github.com/rubyatscale/packs-specification
126
+ licenses:
127
+ - MIT
128
+ metadata:
129
+ homepage_uri: https://github.com/rubyatscale/packs-specification
130
+ source_code_uri: https://github.com/rubyatscale/packs-specification
131
+ changelog_uri: https://github.com/rubyatscale/packs-specification/releases
132
+ allowed_push_host: https://rubygems.org
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '2.6'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubygems_version: 3.1.6
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: The specification for packs in the `rubyatscale` ecosystem.
152
+ test_files: []