Class: Rails::Hyperdrive::LockFile

Inherits:
Object
  • Object
show all
Defined in:
lib/rails/hyperdrive/lock_file.rb

Overview

installed_at is volatile metadata, never an input to any comparison.

Defined Under Namespace

Classes: Entry

Constant Summary collapse

SCHEMA_VERSION =
2
STATE_PRESENT =
"present".freeze
STATE_REMOVED =
"removed-by-user".freeze
DISABLED_KEYS =
{ skill: "skills", guideline: "guidelines", agent: "agents", command: "commands" }.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ LockFile

Returns a new instance of LockFile.



37
38
39
40
41
42
43
44
45
# File 'lib/rails/hyperdrive/lock_file.rb', line 37

def initialize(path)
  @path = path.to_s
  @claude_md_state = nil # nil = no lock has been written yet
  @files = {}            # path(String) => Entry
  @document = {}         # raw parsed YAML, kept so unknown keys survive
  @disabled = empty_disabled
  @enabled = []
  @schema_version = nil
end

Instance Attribute Details

#claude_md_stateObject

Returns the value of attribute claude_md_state.



23
24
25
# File 'lib/rails/hyperdrive/lock_file.rb', line 23

def claude_md_state
  @claude_md_state
end

#pathObject (readonly)

Returns the value of attribute path.



22
23
24
# File 'lib/rails/hyperdrive/lock_file.rb', line 22

def path
  @path
end

#schema_versionObject (readonly)

Returns the value of attribute schema_version.



22
23
24
# File 'lib/rails/hyperdrive/lock_file.rb', line 22

def schema_version
  @schema_version
end

Class Method Details

.load(path) ⇒ Object

Load existing lock state from disk (absent file → empty lock).



26
27
28
# File 'lib/rails/hyperdrive/lock_file.rb', line 26

def self.load(path)
  new(path).tap(&:read)
end

.orphan_reason(source_label:, source_gem:, bundled:) ⇒ Object

Every surface reporting an orphan must name the same two cases, or a sync and a bundle install would disagree about why a file is stranded.



32
33
34
35
# File 'lib/rails/hyperdrive/lock_file.rb', line 32

def self.orphan_reason(source_label:, source_gem:, bundled:)
  return "no longer shipped by #{source_label}" unless bundled
  "#{source_gem} is still bundled but did not offer this file"
end

Instance Method Details

#carry(entry) ⇒ Object



123
124
125
126
# File 'lib/rails/hyperdrive/lock_file.rb', line 123

def carry(entry)
  return unless entry && entry.path
  @files[entry.path] = entry
end

#carry_settings(other) ⇒ Object

Adopt the state that is not derived from installed content, so rewriting the file preserves it.



105
106
107
108
109
110
# File 'lib/rails/hyperdrive/lock_file.rb', line 105

def carry_settings(other)
  @document = other.document.dup
  @disabled = other.disabled_lists.dup
  @enabled = other.enabled_gems.dup
  self
end

#disabled?(type, name) ⇒ Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/rails/hyperdrive/lock_file.rb', line 94

def disabled?(type, name)
  Array(@disabled[type.to_sym]).include?(name.to_s)
end

#each_entry(&block) ⇒ Object



90
91
92
# File 'lib/rails/hyperdrive/lock_file.rb', line 90

def each_entry(&block)
  @files.values.each(&block)
end

#enabled_gemsObject

Hand-editable list of gems the app opts into as companions.



99
100
101
# File 'lib/rails/hyperdrive/lock_file.rb', line 99

def enabled_gems
  @enabled
end

#entry(file_path) ⇒ Object



82
83
84
# File 'lib/rails/hyperdrive/lock_file.rb', line 82

def entry(file_path)
  @files[file_path.to_s]
end

#guideline_pathsObject



86
87
88
# File 'lib/rails/hyperdrive/lock_file.rb', line 86

def guideline_paths
  @files.values.select { |e| e.kind == "guideline" }.map(&:path)
end

#readObject



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/rails/hyperdrive/lock_file.rb', line 47

def read
  return self unless File.exist?(@path)

  data = YAML.safe_load(File.read(@path))
  return self unless data.is_a?(Hash)

  @document = data
  @schema_version = data["version"]
  claude_md = data["claude_md"]
  @claude_md_state = claude_md["state"] if claude_md.is_a?(Hash)
  @disabled = parse_disabled(data["disabled"])
  @enabled = parse_enabled(data["enabled"])
  Array(data["files"]).each do |raw|
    next unless raw.is_a?(Hash)
    entry = build_entry(raw)
    @files[entry.path] = entry if entry.path
  end
  self
rescue Psych::SyntaxError
  self
end

#schema_ahead?Boolean

A lock written by a newer installer holds state this one cannot read, so rewriting it would silently drop the user's settings. A missing or non-numeric version reads as not ahead: every lock ever written carries an integer version, and a hand-broken one must not block a sync.

Returns:

  • (Boolean)


73
74
75
# File 'lib/rails/hyperdrive/lock_file.rb', line 73

def schema_ahead?
  @schema_version.is_a?(Numeric) && @schema_version > SCHEMA_VERSION
end

#schema_ahead_message(display_path) ⇒ Object



77
78
79
80
# File 'lib/rails/hyperdrive/lock_file.rb', line 77

def schema_ahead_message(display_path)
  "#{display_path} was written by a newer rails-hyperdrive (lock schema #{@schema_version}, " \
    "this installer supports #{SCHEMA_VERSION}); upgrade rails-hyperdrive"
end

#to_yamlObject



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/rails/hyperdrive/lock_file.rb', line 128

def to_yaml
  carried = @document["claude_md"]
  carried = {} unless carried.is_a?(Hash)

  document = @document.merge(
    "version"   => SCHEMA_VERSION,
    "claude_md" => carried.merge("state" => @claude_md_state),
    "disabled"  => DISABLED_KEYS.each_with_object({}) { |(type, key), h| h[key] = @disabled[type] },
    "enabled"   => @enabled,
    "files"     => @files.values.sort_by(&:path).map { |e| serialize_entry(e) }
  )
  # A nil state means no import line is being managed. Recording one anyway
  # would make the next run read the absent line as a deletion the user
  # made, and never add it back.
  document.delete("claude_md") if @claude_md_state.nil?
  document.to_yaml
end

#upsert(path:, kind:, source_gem:, source_version:, source_sha:, installed_at:) ⇒ Object



112
113
114
115
116
117
118
119
120
121
# File 'lib/rails/hyperdrive/lock_file.rb', line 112

def upsert(path:, kind:, source_gem:, source_version:, source_sha:, installed_at:)
  @files[path.to_s] = Entry.new(
    path: path.to_s,
    kind: kind.to_s,
    source_gem: source_gem.to_s,
    source_version: source_version.to_s,
    source_sha: source_sha.to_s,
    installed_at: installed_at.to_s
  )
end