Module: Esp::ProjectMarker

Defined in:
lib/esp/project_marker.rb

Overview

Reads, writes, and discovers the ‘.esp/project.json` marker that identifies a directory as an esp project. Used by:

  • ‘esp init` and `Esp::Operations.projects_new` to write the marker.

  • ‘Esp::Operations.open_project` to read the marker’s ‘game:` field so the plugin registry knows which Operations module owns this project.

  • The CLI cwd walk-up so ‘esp build` works from anywhere inside a project tree without `–root`.

Marker filename rename (step 23.5 slice 3): the canonical location is now ‘.esp/project.json`. Projects scaffolded by an earlier ESPresso release wrote `.espresso/project.json`; readers accept both, writers only emit the new name. The back-compat lookup goes away when the release that drops it ships (no auto-migration — we don’t silently rewrite a user’s project files).

Constant Summary collapse

FILENAME =
'project.json'.freeze
DIRECTORY =
'.esp'.freeze
LEGACY_DIRECTORY =
'.espresso'.freeze
WALK_DEPTH_CAP =

Cap the cwd walk-up depth to defend against symlink loops and pathological filesystem layouts. 32 levels is deeper than any sane project tree (and matches git’s own MAX_DEPTH for similar reasons).

32
SCHEMA_VERSION =
1

Class Method Summary collapse

Class Method Details

.find_in(root) ⇒ Object

The marker path under ‘root`, preferring the new `.esp/` location but accepting a legacy `.espresso/` marker. Returns nil if neither exists.



42
43
44
45
46
47
48
49
50
# File 'lib/esp/project_marker.rb', line 42

def find_in(root)
  new_path = path_in(root)
  return new_path if File.exist?(new_path)

  legacy = File.join(root, LEGACY_DIRECTORY, FILENAME)
  return legacy if File.exist?(legacy)

  nil
end

.find_walking_up(start_dir) ⇒ Object

Walk up from ‘start_dir` looking for a project marker. Returns the project root (the directory containing the marker dir), not the marker path. Nil if no marker is found before the filesystem root. Bounded by WALK_DEPTH_CAP so a symlink loop can’t hang us.



85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/esp/project_marker.rb', line 85

def find_walking_up(start_dir)
  current = File.expand_path(start_dir)
  WALK_DEPTH_CAP.times do
    return current if find_in(current)

    parent = File.dirname(current)
    return nil if parent == current # reached filesystem root

    current = parent
  end
  nil
end

.path_in(root) ⇒ Object

The canonical write path for a project rooted at ‘root`.



35
36
37
# File 'lib/esp/project_marker.rb', line 35

def path_in(root)
  File.join(root, DIRECTORY, FILENAME)
end

.read(root) ⇒ Object

Read + parse the marker at ‘root`. Returns nil if no marker exists or the JSON is malformed — callers default to the registry’s default plugin (every pre-22.5 project is Morrowind) rather than surface a sharp edge.



56
57
58
59
60
61
62
63
# File 'lib/esp/project_marker.rb', line 56

def read(root)
  path = find_in(root)
  return nil unless path

  JSON.parse(File.read(path))
rescue JSON::ParserError
  nil
end

.write(root, name:, game:) ⇒ Object

Write the canonical marker. Always emits the new ‘.esp/` location; never touches a legacy `.espresso/` marker the project might already carry. Returns the path written.



68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/esp/project_marker.rb', line 68

def write(root, name:, game:)
  path = path_in(root)
  FileUtils.mkdir_p(File.dirname(path))
  payload = {
    'name' => name,
    'schema' => SCHEMA_VERSION,
    'game' => game,
    'created_at' => Time.now.utc.iso8601
  }
  File.write(path, "#{JSON.pretty_generate(payload)}\n")
  path
end