packs-specification 0.0.7
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 +7 -0
- data/README.md +25 -0
- data/lib/packs/pack.rb +48 -0
- data/lib/packs/private/configuration.rb +36 -0
- data/lib/packs/private.rb +16 -0
- data/lib/packs/rspec/fixture_helper.rb +33 -0
- data/lib/packs/rspec/support.rb +21 -0
- data/lib/packs.rb +80 -0
- metadata +152 -0
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,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: []
|