Class: Aidp::Setup::Devcontainer::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/aidp/setup/devcontainer/parser.rb

Overview

Parses existing devcontainer.json files and extracts configuration for pre-filling wizard defaults.

Defined Under Namespace

Classes: DevcontainerNotFoundError, InvalidDevcontainerError

Constant Summary collapse

STANDARD_LOCATIONS =
[
  ".devcontainer/devcontainer.json",
  ".devcontainer.json",
  "devcontainer.json"
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project_dir = Dir.pwd) ⇒ Parser

Returns a new instance of Parser.



23
24
25
26
27
# File 'lib/aidp/setup/devcontainer/parser.rb', line 23

def initialize(project_dir = Dir.pwd)
  @project_dir = project_dir
  @devcontainer_path = nil
  @config = nil
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



21
22
23
# File 'lib/aidp/setup/devcontainer/parser.rb', line 21

def config
  @config
end

#devcontainer_pathObject (readonly)

Returns the value of attribute devcontainer_path.



21
22
23
# File 'lib/aidp/setup/devcontainer/parser.rb', line 21

def devcontainer_path
  @devcontainer_path
end

#project_dirObject (readonly)

Returns the value of attribute project_dir.



21
22
23
# File 'lib/aidp/setup/devcontainer/parser.rb', line 21

def project_dir
  @project_dir
end

Instance Method Details

#detectString?

Detect devcontainer.json in standard locations

Returns:

  • (String, nil)

    Path to devcontainer.json or nil if not found



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/aidp/setup/devcontainer/parser.rb', line 31

def detect
  STANDARD_LOCATIONS.each do |location|
    path = File.join(project_dir, location)
    if File.exist?(path)
      @devcontainer_path = path
      Aidp.log_debug("devcontainer_parser", "detected devcontainer", path: path)
      return path
    end
  end

  Aidp.log_debug("devcontainer_parser", "no devcontainer found")
  nil
end

#devcontainer_exists?Boolean

Check if devcontainer exists

Returns:

  • (Boolean)


47
48
49
# File 'lib/aidp/setup/devcontainer/parser.rb', line 47

def devcontainer_exists?
  !detect.nil?
end

#extract_customizationsHash

Extract VS Code customizations

Returns:

  • (Hash)

    VS Code extensions and settings



164
165
166
167
168
169
170
171
172
173
174
# File 'lib/aidp/setup/devcontainer/parser.rb', line 164

def extract_customizations
  ensure_parsed

  customizations = @config["customizations"] || {}
  vscode = customizations["vscode"] || {}

  {
    extensions: Array(vscode["extensions"]),
    settings: vscode["settings"] || {}
  }
end

#extract_envHash

Extract container environment variables

Returns:

  • (Hash)

    Environment variables (excluding secrets)



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/aidp/setup/devcontainer/parser.rb', line 133

def extract_env
  ensure_parsed

  env = @config["containerEnv"] || @config["remoteEnv"] || {}
  env = {} unless env.is_a?(Hash)

  # Filter out sensitive values
  filtered_env = env.reject { |key, value|
    sensitive_key?(key) || sensitive_value?(value)
  }

  Aidp.log_debug("devcontainer_parser", "extracted env vars",
    total: env.size,
    filtered: filtered_env.size)
  filtered_env
end

#extract_featuresArray<String>

Extract devcontainer features

Returns:

  • (Array<String>)

    Array of feature identifiers



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/aidp/setup/devcontainer/parser.rb', line 112

def extract_features
  ensure_parsed

  features = @config["features"] || {}

  # Handle both object and array format
  feature_list = case features
  when Hash
    features.keys
  when Array
    features
  else
    []
  end

  Aidp.log_debug("devcontainer_parser", "extracted features", count: feature_list.size)
  feature_list
end

#extract_image_configHash

Extract the base image or dockerfile reference

Returns:

  • (Hash)

    Image configuration



192
193
194
195
196
197
198
199
200
201
# File 'lib/aidp/setup/devcontainer/parser.rb', line 192

def extract_image_config
  ensure_parsed

  {
    image: @config["image"],
    dockerfile: @config["dockerFile"] || @config["dockerfile"],
    context: @config["context"],
    build: @config["build"]
  }.compact
end

#extract_portsArray<Hash>

Extract port forwarding configuration

Returns:

  • (Array<Hash>)

    Array of port configurations



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/aidp/setup/devcontainer/parser.rb', line 80

def extract_ports
  ensure_parsed

  ports = []

  # Extract from forwardPorts array
  forward_ports = @config["forwardPorts"] || []
  forward_ports = [forward_ports] unless forward_ports.is_a?(Array)

  # Get port attributes for labels
  port_attrs = @config["portsAttributes"] || {}

  forward_ports.each do |port|
    port_num = port.to_i
    next if port_num <= 0

    attrs = port_attrs[port.to_s] || port_attrs[port_num.to_s] || {}

    ports << {
      number: port_num,
      label: attrs["label"],
      protocol: attrs["protocol"] || "http",
      on_auto_forward: attrs["onAutoForward"] || "notify"
    }
  end

  Aidp.log_debug("devcontainer_parser", "extracted ports", count: ports.size)
  ports
end

#extract_post_commandsHash

Extract post-create and post-start commands

Returns:

  • (Hash)

    Commands configuration



152
153
154
155
156
157
158
159
160
# File 'lib/aidp/setup/devcontainer/parser.rb', line 152

def extract_post_commands
  ensure_parsed

  {
    post_create: @config["postCreateCommand"],
    post_start: @config["postStartCommand"],
    post_attach: @config["postAttachCommand"]
  }.compact
end

#extract_remote_userString?

Extract remote user setting

Returns:

  • (String, nil)

    Remote user name



178
179
180
181
# File 'lib/aidp/setup/devcontainer/parser.rb', line 178

def extract_remote_user
  ensure_parsed
  @config["remoteUser"]
end

#extract_workspace_folderString?

Extract working directory

Returns:

  • (String, nil)

    Working directory path



185
186
187
188
# File 'lib/aidp/setup/devcontainer/parser.rb', line 185

def extract_workspace_folder
  ensure_parsed
  @config["workspaceFolder"]
end

#parseHash

Parse devcontainer.json and extract configuration

Returns:

  • (Hash)

    Parsed configuration

Raises:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/aidp/setup/devcontainer/parser.rb', line 55

def parse
  detect unless @devcontainer_path

  unless @devcontainer_path
    raise DevcontainerNotFoundError, "No devcontainer.json found in #{project_dir}"
  end

  begin
    content = File.read(@devcontainer_path)
    @config = JSON.parse(content)
    Aidp.log_debug("devcontainer_parser", "parsed devcontainer",
      features_count: extract_features.size,
      ports_count: extract_ports.size)
    @config
  rescue JSON::ParserError => e
    Aidp.log_error("devcontainer_parser", "invalid JSON", error: e.message, path: @devcontainer_path)
    raise InvalidDevcontainerError, "Invalid JSON in #{@devcontainer_path}: #{e.message}"
  rescue => e
    Aidp.log_error("devcontainer_parser", "failed to read devcontainer", error: e.message)
    raise InvalidDevcontainerError, "Failed to read #{@devcontainer_path}: #{e.message}"
  end
end

#to_hHash

Get complete parsed configuration as hash

Returns:

  • (Hash)

    All extracted configuration



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/aidp/setup/devcontainer/parser.rb', line 205

def to_h
  ensure_parsed

  {
    path: @devcontainer_path,
    ports: extract_ports,
    features: extract_features,
    env: extract_env,
    post_commands: extract_post_commands,
    customizations: extract_customizations,
    remote_user: extract_remote_user,
    workspace_folder: extract_workspace_folder,
    image_config: extract_image_config,
    raw: @config
  }
end