dependabot-devbox 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99efa19eeff967724728c2c6010b2c1f625619a0b7716dbbfc13db644defa666
4
- data.tar.gz: 170cfde66463d21dbad418809c9028456753bdc64a23b7ff43e75817ba3ac7aa
3
+ metadata.gz: 35d218d66727572ab52df9c11c35b6869478d549cd15c5d9042833d0e94a7aad
4
+ data.tar.gz: 262ca126fac5c3083505dc7afd6ef41a285d9b0a2444e635ebbbe8d6acd95d2f
5
5
  SHA512:
6
- metadata.gz: '02429b6a5c826d6130271e1d63120af02d0d49e74082421ed45f62e772fc6dc2fd6fe198d4f84082b55d0b9e0ef29ff6d7f6b62344977403aa52e2589677d6b6'
7
- data.tar.gz: 855892e0fafa13e2b658110baf461931a717326219968d77732b86fe01b143e5ecbbb54ba44607dff99f2c994f3ebedb0670370f73e326159a430355d5010b21
6
+ metadata.gz: 0baa0a5ecfcc39ff64e8e5d546ca570349040f7087db824870f756e50812755d10c8504c35f5666e548c7a0e097b725196d435de65cf10f6f008ed5987d0cd73
7
+ data.tar.gz: 3e056cf0ae29dc2385a63947e2df367901b5aa26f98cb356d4a77310001c00541c79556d9054612cc69929be0cd81b3bf87e0b61eeda9fecf6e2501dbdc19d4e
@@ -7,12 +7,14 @@ require "dependabot/update_checkers"
7
7
  require "dependabot/file_updaters"
8
8
  require "dependabot/pull_request_creator"
9
9
  require "dependabot/devbox"
10
+ require "dependabot/devbox/update_runner"
10
11
 
11
12
  repo = ENV.fetch("GITHUB_REPOSITORY")
12
13
  token = ENV["GITHUB_ACCESS_TOKEN"] || ENV.fetch("GITHUB_TOKEN")
13
14
  directory = ENV.fetch("DIRECTORY_PATH", "/")
14
- branch = ENV["BASE_BRANCH"].then { |b| b&.empty? ? nil : b }
15
+ branch = ENV["BASE_BRANCH"].then { |b| b.nil? || b.empty? ? nil : b }
15
16
  group = ENV.fetch("GROUP_UPDATES", "false") == "true"
17
+ cooldown = Dependabot::Devbox::UpdateRunner.build_cooldown(ENV.fetch("COOLDOWN_DAYS", "0").to_i)
16
18
 
17
19
  credentials = [
18
20
  {
@@ -50,93 +52,29 @@ parser = Dependabot::FileParsers.for_package_manager("devbox").new(
50
52
  dependencies = parser.parse
51
53
  puts "Found #{dependencies.length} devbox package(s)"
52
54
 
53
- def check_dep(dep, files, credentials)
54
- puts "Checking #{dep.name} (#{dep.version})..."
55
-
56
- checker = Dependabot::UpdateCheckers.for_package_manager("devbox").new(
57
- dependency: dep,
58
- dependency_files: files,
59
- credentials: credentials
60
- )
61
-
62
- return nil if checker.up_to_date? && puts(" up to date")
63
-
64
- requirements_to_unlock = checker.requirements_unlocked_or_can_be? ? :own : :none
65
-
66
- begin
67
- checker.updated_dependencies(requirements_to_unlock: requirements_to_unlock)
68
- rescue Dependabot::AllVersionsIgnored
69
- puts " all versions ignored"
70
- nil
71
- end
72
- end
73
-
74
55
  if group
75
- all_updated_deps = dependencies.filter_map { |dep| check_dep(dep, files, credentials) }.flatten
56
+ all_updated_deps = dependencies.filter_map do |dep|
57
+ Dependabot::Devbox::UpdateRunner.check_dependency(dep, files, credentials, cooldown)
58
+ end.flatten
76
59
 
77
60
  if all_updated_deps.empty?
78
61
  puts "All packages up to date."
79
62
  else
80
- updater = Dependabot::FileUpdaters.for_package_manager("devbox").new(
81
- dependencies: all_updated_deps,
82
- dependency_files: files,
83
- credentials: credentials
84
- )
85
-
86
- updated_files = updater.updated_dependency_files
63
+ majors, rest = Dependabot::Devbox::UpdateRunner.partition_majors(all_updated_deps)
87
64
 
88
- pr_creator = Dependabot::PullRequestCreator.new(
89
- source: source,
90
- base_commit: commit,
91
- dependencies: all_updated_deps,
92
- files: updated_files,
93
- credentials: credentials,
94
- label_language: true
95
- )
65
+ Dependabot::Devbox::UpdateRunner.open_pr(source, commit, rest, files, credentials) unless rest.empty?
96
66
 
97
- begin
98
- pr = pr_creator.create
99
- if pr
100
- puts "PR created: #{pr.html_url}"
101
- else
102
- puts "PR already exists or no changes needed"
103
- end
104
- rescue Dependabot::PullRequestCreator::UnmergedPRExists => e
105
- puts "Skipping: #{e.message} (closed but not merged — delete the branch to recreate)"
67
+ majors.each do |dep|
68
+ puts "Opening individual PR for major bump: #{dep.name}"
69
+ Dependabot::Devbox::UpdateRunner.open_pr(source, commit, [dep], files, credentials, indent: " ")
106
70
  end
107
71
  end
108
72
  else
109
- dependencies.each do |dep| # rubocop:disable Metrics/BlockLength
110
- updated_deps = check_dep(dep, files, credentials)
73
+ dependencies.each do |dep|
74
+ updated_deps = Dependabot::Devbox::UpdateRunner.check_dependency(dep, files, credentials, cooldown)
111
75
  next unless updated_deps
112
76
 
113
- updater = Dependabot::FileUpdaters.for_package_manager("devbox").new(
114
- dependencies: updated_deps,
115
- dependency_files: files,
116
- credentials: credentials
117
- )
118
-
119
- updated_files = updater.updated_dependency_files
120
-
121
- pr_creator = Dependabot::PullRequestCreator.new(
122
- source: source,
123
- base_commit: commit,
124
- dependencies: updated_deps,
125
- files: updated_files,
126
- credentials: credentials,
127
- label_language: true
128
- )
129
-
130
- begin
131
- pr = pr_creator.create
132
- if pr
133
- puts " PR created: #{pr.html_url}"
134
- else
135
- puts " PR already exists or no changes needed"
136
- end
137
- rescue Dependabot::PullRequestCreator::UnmergedPRExists => e
138
- puts " Skipping: #{e.message} (closed but not merged — delete the branch to recreate)"
139
- end
77
+ Dependabot::Devbox::UpdateRunner.open_pr(source, commit, updated_deps, files, credentials, indent: " ")
140
78
  end
141
79
  end
142
80
 
@@ -9,8 +9,8 @@ module Dependabot
9
9
  class FileFetcher < Dependabot::FileFetchers::Base
10
10
  extend T::Sig
11
11
 
12
- MANIFEST_FILENAME = T.let("devbox.json", String)
13
- LOCKFILE_FILENAME = T.let("devbox.lock", String)
12
+ MANIFEST_FILENAME = "devbox.json"
13
+ LOCKFILE_FILENAME = "devbox.lock"
14
14
 
15
15
  sig { override.returns(String) }
16
16
  def self.required_files_message
@@ -13,12 +13,12 @@ module Dependabot
13
13
  class FileParser < Dependabot::FileParsers::Base
14
14
  extend T::Sig
15
15
 
16
- ECOSYSTEM = T.let("devbox", String)
17
- MANIFEST_FILENAME = T.let("devbox.json", String)
18
- LOCKFILE_FILENAME = T.let("devbox.lock", String)
16
+ ECOSYSTEM = "devbox"
17
+ MANIFEST_FILENAME = "devbox.json"
18
+ LOCKFILE_FILENAME = "devbox.lock"
19
19
  # An entry without an "@constraint" suffix tracks the newest release.
20
- DEFAULT_CONSTRAINT = T.let("latest", String)
21
- SOURCE_TYPE = T.let("nixhub", String)
20
+ DEFAULT_CONSTRAINT = "latest"
21
+ SOURCE_TYPE = "nixhub"
22
22
 
23
23
  sig { override.returns(T::Array[Dependabot::Dependency]) }
24
24
  def parse
@@ -14,9 +14,9 @@ module Dependabot
14
14
  class FileUpdater < Dependabot::FileUpdaters::Base
15
15
  extend T::Sig
16
16
 
17
- MANIFEST_FILENAME = T.let("devbox.json", String)
18
- LOCKFILE_FILENAME = T.let("devbox.lock", String)
19
- LATEST = T.let("latest", String)
17
+ MANIFEST_FILENAME = "devbox.json"
18
+ LOCKFILE_FILENAME = "devbox.lock"
19
+ LATEST = "latest"
20
20
 
21
21
  sig { override.returns(T::Array[Dependabot::DependencyFile]) }
22
22
  def updated_dependency_files
@@ -15,15 +15,12 @@ module Dependabot
15
15
  # block comment, or a trailing comma. The alternation lets gsub preserve
16
16
  # strings while stripping the JSONC-only constructs, so e.g. "//" inside a
17
17
  # URL value is not mistaken for the start of a comment.
18
- JSONC_TOKEN = T.let(
19
- %r{
18
+ JSONC_TOKEN = %r{
20
19
  ("(?:\\.|[^"\\])*") # JSON string literal
21
20
  | //[^\n]* # line comment
22
21
  | /\*.*?\*/ # block comment
23
22
  | ,(?=\s*[\}\]]) # trailing comma
24
- }mx,
25
- Regexp
26
- )
23
+ }mx
27
24
 
28
25
  sig { params(content: T.nilable(String)).returns(T::Hash[String, Object]) }
29
26
  def self.parse_json_or_jsonc(content)
@@ -12,7 +12,7 @@ module Dependabot
12
12
  class MetadataFinder < Dependabot::MetadataFinders::Base
13
13
  extend T::Sig
14
14
 
15
- SEARCH_URL = T.let("https://search.devbox.sh/v1/search", String)
15
+ SEARCH_URL = "https://search.devbox.sh/v1/search"
16
16
 
17
17
  private
18
18
 
@@ -15,7 +15,7 @@ module Dependabot
15
15
  class PackageDetailsFetcher
16
16
  extend T::Sig
17
17
 
18
- SEARCH_URL = T.let("https://search.devbox.sh/v1/search", String)
18
+ SEARCH_URL = "https://search.devbox.sh/v1/search"
19
19
 
20
20
  sig { params(dependency: Dependabot::Dependency).void }
21
21
  def initialize(dependency:)
@@ -13,7 +13,7 @@ module Dependabot
13
13
 
14
14
  require_relative "update_checker/latest_version_finder"
15
15
 
16
- LATEST = T.let("latest", String)
16
+ LATEST = "latest"
17
17
 
18
18
  sig { override.returns(T.nilable(T.any(String, Gem::Version))) }
19
19
  def latest_version
@@ -0,0 +1,121 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/dependency"
6
+ require "dependabot/update_checkers"
7
+ require "dependabot/file_updaters"
8
+ require "dependabot/pull_request_creator"
9
+ require "dependabot/package/release_cooldown_options"
10
+
11
+ module Dependabot
12
+ module Devbox
13
+ # Extracted from exe/dependabot-devbox-update so its per-dependency and
14
+ # per-PR logic can be unit tested without loading the executable itself
15
+ # (which RubyGems' bin stub runs via Kernel#load, under which the
16
+ # conventional `$PROGRAM_NAME == __FILE__` require-guard never matches).
17
+ module UpdateRunner
18
+ extend T::Sig
19
+
20
+ sig { params(days: Integer).returns(T.nilable(Dependabot::Package::ReleaseCooldownOptions)) }
21
+ def self.build_cooldown(days)
22
+ return nil unless days.positive?
23
+
24
+ Dependabot::Package::ReleaseCooldownOptions.new(default_days: days)
25
+ end
26
+
27
+ # Classifies by comparing the major segment of the previous and new
28
+ # version strings, so it works for both semver ("24.11.1" -> "26.4.0")
29
+ # and nixpkgs' shorter version strings ("0.99.4" -> "1.0.4").
30
+ sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
31
+ def self.major_bump?(dependency)
32
+ previous = dependency.previous_version
33
+ current = dependency.version
34
+ return false unless previous && current
35
+
36
+ previous.to_s.split(".").first != current.to_s.split(".").first
37
+ end
38
+
39
+ sig do
40
+ params(dependencies: T::Array[Dependabot::Dependency])
41
+ .returns([T::Array[Dependabot::Dependency], T::Array[Dependabot::Dependency]])
42
+ end
43
+ def self.partition_majors(dependencies)
44
+ dependencies.partition { |dep| major_bump?(dep) }
45
+ end
46
+
47
+ sig do
48
+ params(
49
+ dependency: Dependabot::Dependency,
50
+ files: T::Array[Dependabot::DependencyFile],
51
+ credentials: T::Array[T.untyped],
52
+ cooldown: T.nilable(Dependabot::Package::ReleaseCooldownOptions)
53
+ ).returns(T.nilable(T::Array[Dependabot::Dependency]))
54
+ end
55
+ def self.check_dependency(dependency, files, credentials, cooldown)
56
+ puts "Checking #{dependency.name} (#{dependency.version})..."
57
+
58
+ checker = Dependabot::UpdateCheckers.for_package_manager("devbox").new(
59
+ dependency: dependency,
60
+ dependency_files: files,
61
+ credentials: credentials,
62
+ update_cooldown: cooldown
63
+ )
64
+
65
+ if checker.up_to_date?
66
+ puts " up to date"
67
+ return nil
68
+ end
69
+
70
+ requirements_to_unlock = checker.requirements_unlocked_or_can_be? ? :own : :none
71
+
72
+ begin
73
+ checker.updated_dependencies(requirements_to_unlock: requirements_to_unlock)
74
+ rescue Dependabot::AllVersionsIgnored
75
+ puts " all versions ignored"
76
+ nil
77
+ end
78
+ end
79
+
80
+ sig do
81
+ params(
82
+ source: T.untyped,
83
+ commit: String,
84
+ dependencies: T::Array[Dependabot::Dependency],
85
+ files: T::Array[Dependabot::DependencyFile],
86
+ credentials: T::Array[T.untyped],
87
+ indent: String
88
+ ).void
89
+ end
90
+ def self.open_pr(source, commit, dependencies, files, credentials, indent: "")
91
+ updater = Dependabot::FileUpdaters.for_package_manager("devbox").new(
92
+ dependencies: dependencies,
93
+ dependency_files: files,
94
+ credentials: credentials
95
+ )
96
+
97
+ updated_files = updater.updated_dependency_files
98
+
99
+ pr_creator = Dependabot::PullRequestCreator.new(
100
+ source: source,
101
+ base_commit: commit,
102
+ dependencies: dependencies,
103
+ files: updated_files,
104
+ credentials: credentials,
105
+ label_language: true
106
+ )
107
+
108
+ pr = pr_creator.create
109
+ if pr
110
+ puts "#{indent}PR created: #{pr.html_url}"
111
+ else
112
+ puts "#{indent}PR already exists or no changes needed"
113
+ end
114
+ rescue Dependabot::PullRequestCreator::UnmergedPRExists => e
115
+ puts "#{indent}Skipping: #{e.message} (closed but not merged — delete the branch to recreate)"
116
+ rescue Dependabot::PullRequestCreator::BranchAlreadyExists => e
117
+ puts "#{indent}Skipping: #{e.message} (stale branch with no open PR — delete it to retry)"
118
+ end
119
+ end
120
+ end
121
+ end
@@ -20,7 +20,7 @@ module Dependabot
20
20
  # A value that sorts above any realistic nixpkgs version, used as the
21
21
  # internal representation of the "latest" sentinel since Gem::Version
22
22
  # cannot parse the word itself.
23
- LATEST_SENTINEL = T.let("999999", String)
23
+ LATEST_SENTINEL = "999999"
24
24
 
25
25
  sig { override.params(version: VersionParameter).returns(T::Boolean) }
26
26
  def self.correct?(version)
@@ -32,6 +32,7 @@ require "dependabot/devbox/package/package_details_fetcher"
32
32
  require "dependabot/devbox/helpers"
33
33
  require "dependabot/devbox/version"
34
34
  require "dependabot/devbox/requirement"
35
+ require "dependabot/devbox/update_runner"
35
36
 
36
37
  require "dependabot/pull_request_creator/labeler"
37
38
  Dependabot::PullRequestCreator::Labeler
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-devbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andoni A.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-29 00:00:00.000000000 Z
11
+ date: 2026-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -44,6 +44,7 @@ files:
44
44
  - lib/dependabot/devbox/requirement.rb
45
45
  - lib/dependabot/devbox/update_checker.rb
46
46
  - lib/dependabot/devbox/update_checker/latest_version_finder.rb
47
+ - lib/dependabot/devbox/update_runner.rb
47
48
  - lib/dependabot/devbox/version.rb
48
49
  homepage: https://github.com/andoniaf/dependabot-devbox
49
50
  licenses: