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.



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

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

Instance Attribute Details

#commentsObject (readonly)

Returns the value of attribute comments.



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

def comments
  @comments
end

#configObject (readonly)

Returns the value of attribute config.



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

def config
  @config
end

#devcontainer_pathObject (readonly)

Returns the value of attribute devcontainer_path.



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

def devcontainer_path
  @devcontainer_path
end

#project_dirObject (readonly)

Returns the value of attribute project_dir.



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

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



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

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)


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

def devcontainer_exists?
  !detect.nil?
end

#extract_customizationsHash

Extract VS Code customizations

Returns:

  • (Hash)

    VS Code extensions and settings



167
168
169
170
171
172
173
174
175
176
177
# File 'lib/aidp/setup/devcontainer/parser.rb', line 167

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)



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

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



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

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



195
196
197
198
199
200
201
202
203
204
# File 'lib/aidp/setup/devcontainer/parser.rb', line 195

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



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
109
110
111
# File 'lib/aidp/setup/devcontainer/parser.rb', line 83

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



155
156
157
158
159
160
161
162
163
# File 'lib/aidp/setup/devcontainer/parser.rb', line 155

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



181
182
183
184
# File 'lib/aidp/setup/devcontainer/parser.rb', line 181

def extract_remote_user
  ensure_parsed
  @config["remoteUser"]
end

#extract_workspace_folderString?

Extract working directory

Returns:

  • (String, nil)

    Working directory path



188
189
190
191
# File 'lib/aidp/setup/devcontainer/parser.rb', line 188

def extract_workspace_folder
  ensure_parsed
  @config["workspaceFolder"]
end

#parseHash

Parse devcontainer.json and extract configuration

Returns:

  • (Hash)

    Parsed configuration

Raises:



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

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)
    document = JsoncDocument.parse(content)
    @config = document.data
    @comments = document.comments
    Aidp.log_debug("devcontainer_parser", "parsed devcontainer",
      features_count: extract_features.size,
      ports_count: extract_ports.size)
    @config
  rescue JsoncDocument::ParseError, 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



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

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