greeter-core 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 10cbecb382247a62f7cec751c68d49bc5e9941da3b6cf58c4d448482aadd87c7
4
+ data.tar.gz: fdac9d0b4b33236ea3889f4cc224302df79a6438b6b0457619178b42f19b3239
5
+ SHA512:
6
+ metadata.gz: 0b77ea8cef0e5a30ca2d7dcc696ee15e45f29ff23f51255f2dd66fd7b8bb794752bfba1d1eef0ce2fd004959c3cac2988975aa6726c0ae0fd70e3074754be10f
7
+ data.tar.gz: aea155eab6deb79fc3ab453e5d8f667415c042072ec726a7850417ee746682e94bd10aa71ad3e987ed5b6a810ccfbb6d9aec4c280d96476aefeff6fc5f39c36a
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adam Bonsu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # greeter-core
2
+
3
+ Pure domain core and ports for the Greeter application.
4
+
5
+ ## Usage
6
+ ```ruby
7
+ require 'greeter/core'
8
+
9
+ clock = my_clock # any object responding to #now
10
+ service = Greeter::Core::Domain::GreetingService.new(clock: clock)
11
+ greeting = service.greet('alice')
12
+ greeting.guest_name.display # => "Alice"
13
+ ```
14
+
15
+ ## Testing helpers
16
+ ```ruby
17
+ require 'greeter/core/testing'
18
+ clock = Greeter::Core::Testing::FixedClock.new
19
+ ```
20
+
21
+ ## Install, verify, commit
22
+ ### from greeter-core root
23
+ `bundle install`
24
+
25
+ ### domain + ports + testing specs
26
+ `bundle exec rspec`
27
+
28
+ ### lint
29
+ `bundle exec rubocop`
30
+
31
+ ### smoke-test the packaged load path resolves and behaves
32
+ `bundle exec ruby -Ilib -e "require 'greeter/core'; puts Greeter::Core::Domain::GuestName.new('alice bonsu').display" # => Alice Bonsu
33
+
34
+ ## confirm the gem builds cleanly
35
+ gem build greeter-core.gemspec
36
+
37
+
38
+ ## Publishing to RubyGems.org
39
+
40
+ The `publish` job in `.github/workflows/ci.yml` publishes the gem to
41
+ [rubygems.org](https://rubygems.org). It runs **only when a version tag is
42
+ pushed** (a tag matching `v*`, e.g. `v0.1.0`) and **only after** the `spec`,
43
+ `lint`, and `perf` jobs pass.
44
+
45
+ Publishing uses RubyGems **trusted publishing** (OIDC): GitHub Actions proves its
46
+ identity to RubyGems and receives a short-lived, single-use API key at run time.
47
+ No long-lived RubyGems API key is stored in GitHub.
48
+
49
+ ### 1. Create the RubyGems account (one time)
50
+
51
+ - Sign up / sign in at <https://rubygems.org>.
52
+ - Enable MFA on the account (RubyGems requires it for publishing).
53
+
54
+ ### 2. Reserve the gem name (first release only)
55
+
56
+ Trusted publishing can only be configured on a gem that already exists on
57
+ rubygems.org. For the very first `0.1.0` release, do a one-time manual push from
58
+ your machine so the name is registered:
59
+
60
+ ```bash
61
+ gem build greeter-core.gemspec
62
+ gem push greeter-core-0.1.0.gem # prompts for RubyGems credentials + MFA
63
+ ```
64
+
65
+ After that, all future releases go through the automated workflow below.
66
+
67
+ ### 3. Configure trusted publishing on RubyGems (one time)
68
+
69
+ On rubygems.org, open the gem page → **Trusted publishers** → **Create**, and add
70
+ a GitHub Actions publisher matching this repo and workflow:
71
+
72
+ | Field | Value |
73
+ | --- | --- |
74
+ | Repository owner | your GitHub username/org |
75
+ | Repository name | `greeter-core` |
76
+ | Workflow filename | `ci.yml` |
77
+ | Environment | `rubygems` |
78
+
79
+ The **Environment** must match the `environment: rubygems` set on the `publish`
80
+ job in the workflow.
81
+
82
+ ### 4. Create the matching GitHub environment (one time)
83
+
84
+ In the GitHub repo: **Settings → Environments → New environment**, name it
85
+ `rubygems`. No secrets are needed — trusted publishing supplies the credential.
86
+ Optionally add a **deployment protection rule** (e.g. required reviewer, or
87
+ restrict to tags matching `v*`) so releases require approval.
88
+
89
+ There are **no repository secrets to add** for publishing.
90
+
91
+ ### 5. Release a new version
92
+
93
+ 1. Bump `VERSION` in `lib/greeter/core/version.rb` (the workflow fails if the tag
94
+ and gem version disagree).
95
+ 2. Commit and push to your default branch.
96
+ 3. Tag and push the tag:
97
+
98
+ ```bash
99
+ git tag v0.1.0
100
+ git push origin v0.1.0
101
+ ```
102
+
103
+ The `publish` job runs after the test/lint/perf gate and publishes
104
+ `greeter-core-<version>` to rubygems.org via trusted publishing.
105
+
106
+ ### Consuming the published gem
107
+
108
+ Because it's public on rubygems.org, no special configuration is needed:
109
+
110
+ ```ruby
111
+ # Gemfile
112
+ gem "greeter-core"
113
+ ```
114
+
115
+ ```bash
116
+ gem install greeter-core
117
+ ```
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Greeter
4
+ module Core
5
+ module Domain
6
+ class Greeting
7
+ attr_reader :guest_name, :greeted_at
8
+
9
+ def initialize(guest_name:, greeted_at:)
10
+ @guest_name = guest_name
11
+ @greeted_at = greeted_at
12
+ freeze
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'greeter/core/domain/guest_name'
4
+ require 'greeter/core/domain/greeting'
5
+
6
+ module Greeter
7
+ module Core
8
+ module Domain
9
+ class GreetingService
10
+ def initialize(clock:)
11
+ @clock = clock
12
+ end
13
+
14
+ def greet(raw_name)
15
+ Greeting.new(
16
+ guest_name: GuestName.new(raw_name),
17
+ greeted_at: @clock.now
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Greeter
4
+ module Core
5
+ module Domain
6
+ class InvalidGuestName < ArgumentError; end
7
+
8
+ class GuestName
9
+ MAX_LENGTH = 64
10
+
11
+ attr_reader :display
12
+
13
+ def initialize(raw)
14
+ validate_characters!(raw.to_s)
15
+ stripped = raw.to_s.strip
16
+ validate_content!(stripped)
17
+ @display = titlecase(stripped)
18
+ freeze
19
+ end
20
+
21
+ def ==(other)
22
+ other.is_a?(GuestName) && display == other.display
23
+ end
24
+
25
+ private
26
+
27
+ def validate_characters!(value)
28
+ raise InvalidGuestName, 'Name contains invalid characters' if value.match?(/[\x00-\x1F\x7F]/)
29
+ end
30
+
31
+ def validate_content!(value)
32
+ raise InvalidGuestName, 'Name must not be empty' if value.empty?
33
+ raise InvalidGuestName, "Name must not exceed #{MAX_LENGTH} characters" if value.length > MAX_LENGTH
34
+ end
35
+
36
+ def titlecase(value)
37
+ value.split.map(&:capitalize).join(' ')
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Greeter
4
+ module Core
5
+ module Ports
6
+ class Clock
7
+ def now
8
+ raise NotImplementedError, "#{self.class}#now is not implemented"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Greeter
4
+ module Core
5
+ module Ports
6
+ class GreetingPresenter
7
+ def present(_greeting)
8
+ raise NotImplementedError, "#{self.class}#present is not implemented"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Greeter
4
+ module Core
5
+ module Testing
6
+ class FakeClock
7
+ def initialize(time)
8
+ @time = time
9
+ end
10
+
11
+ def now
12
+ @time
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Greeter
4
+ module Core
5
+ module Testing
6
+ class FixedClock
7
+ def initialize(time = Time.utc(2024, 6, 1, 9, 0, 0))
8
+ @time = time
9
+ end
10
+
11
+ def now
12
+ @time
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Opt-in test doubles for consumers of greeter-core.
4
+ # Not loaded by `require 'greeter/core'` so production stays I/O-free.
5
+ require 'greeter/core/testing/fake_clock'
6
+ require 'greeter/core/testing/fixed_clock'
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Greeter
4
+ module Core
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'greeter/core/version'
4
+
5
+ # Ports (interfaces)
6
+ require 'greeter/core/ports/clock'
7
+ require 'greeter/core/ports/greeting_presenter'
8
+
9
+ # Domain (value objects + use case)
10
+ require 'greeter/core/domain/guest_name'
11
+ require 'greeter/core/domain/greeting'
12
+ require 'greeter/core/domain/greeting_service'
13
+
14
+ module Greeter
15
+ module Core
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: greeter-core
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Bonsu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-09-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Framework-agnostic domain logic (GuestName, Greeting, GreetingService)
14
+ and driving/driven ports for Greeter, with in-memory test doubles under Greeter::Core::Testing.
15
+ email:
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - README.md
22
+ - lib/greeter/core.rb
23
+ - lib/greeter/core/domain/greeting.rb
24
+ - lib/greeter/core/domain/greeting_service.rb
25
+ - lib/greeter/core/domain/guest_name.rb
26
+ - lib/greeter/core/ports/clock.rb
27
+ - lib/greeter/core/ports/greeting_presenter.rb
28
+ - lib/greeter/core/testing.rb
29
+ - lib/greeter/core/testing/fake_clock.rb
30
+ - lib/greeter/core/testing/fixed_clock.rb
31
+ - lib/greeter/core/version.rb
32
+ homepage:
33
+ licenses:
34
+ - MIT
35
+ metadata:
36
+ rubygems_mfa_required: 'true'
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 3.3.0
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.5.23
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Pure domain core and ports for the Greeter application.
56
+ test_files: []