Skip to content

Add support for multiple configuration formats (YAML, TOML, INI, HCL, JSON)#74

Merged
rprtr258 merged 1 commit intomasterfrom
langs
Feb 1, 2026
Merged

Add support for multiple configuration formats (YAML, TOML, INI, HCL, JSON)#74
rprtr258 merged 1 commit intomasterfrom
langs

Conversation

@rprtr258
Copy link
Owner

@rprtr258 rprtr258 commented Jan 29, 2026

Summary by CodeRabbit

  • New Features

    • Added support for YAML, TOML, INI, HCL, and JSON configuration formats in addition to JSONNet.
  • Documentation

    • Added example configurations for each supported format.
    • Updated configuration schema: process name is now required.
  • Tests

    • Expanded test coverage to validate loading and parsing across all supported config formats.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

Adds multi-format configuration support and parsing: JSONNet, JSON, YAML, TOML, INI, and HCL. Introduces per-format loaders, DTO mapping to RunConfig, new tests and testdata, example files and docs updates, plus dependency and linter configuration adjustments.

Changes

Cohort / File(s) Summary
Linter Configuration
\.golangci\.yaml
Removed exhaustruct from enabled linters and its exclude block; added goconst with min-occurrences: 4. Review for linter expectations.
Docs examples
docs/examples/config.hcl, docs/examples/config.ini, docs/examples/config.json, docs/examples/config.jsonnet, docs/examples/config.toml, docs/examples/config.yaml
Added example config files for HCL, INI, JSON, Jsonnet, TOML, and YAML demonstrating a web-server process.
Documentation/runtime
docs/main.ts, readme.md
Replaced embedded Jsonnet sample with runtime-loaded examples for all formats; expanded Supported Formats and marked name field as required. Verify doc rendering and sample loading paths.
Dependencies
go.mod
Added/updated dependencies to support HCL, TOML, INI, YAML parsing and transitive libs (hcl/v2, pelletier/go-toml/v2, ini.v1, yaml.v3, etc.). Check go.sum and module compatibility.
Core parsing logic
internal/core/runconfig.go
Introduced loadConfigs dispatcher, per-format loaders (loadJSONConfigs, loadYAMLConfigs, loadTOMLConfigs, loadINIConfigs, loadHCLConfigs, JSONNet support), DTO types (configScanDTO, configNamedKeyDTO), robust arg/env handling, cwd resolution relative to config file, and error wrapping. High logic density — requires careful review.
Tests
internal/core/runconfig_test.go
Added comprehensive tests covering multi-format loading, validation error cases, helpers for testdata and temporary files. Confirm coverage of edge cases and flakiness.
Test data
internal/core/testdata/*
Added multiple test fixtures: hcl_simple.hcl, hcl_multi_process.hcl, ini_multi_process.ini, json_simple.json, toml_simple.toml, and various yaml_*.yaml files. Ensure testdata matches parser expectations and encodings.

Sequence Diagram

sequenceDiagram
    participant File as Config File<br/>(YAML/TOML/HCL/JSON/INI/JSONNet)
    participant Dispatcher as loadConfigs<br/>(Dispatcher)
    participant Parser as Format Parser
    participant DTO as DTO Converter
    participant Validator as Validation & Defaults
    participant RunConfig as RunConfig

    File->>Dispatcher: read file contents
    Dispatcher->>Dispatcher: detect format (by extension / Jsonnet)
    Dispatcher->>Parser: dispatch to format-specific loader
    Parser->>DTO: parse into configScanDTO / named DTOs
    DTO->>Validator: map fields, resolve types and cwd
    Validator->>RunConfig: apply defaults and return populated RunConfig
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • update docs #75: Related changes to docs/main.ts and example rendering; both PRs alter how examples are produced/loaded in the docs.
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'add config langs' is vague and does not clearly convey what was actually changed in the pull request, which adds support for multiple configuration file formats (YAML, TOML, INI, HCL, JSON). Consider using a more descriptive title such as 'Add support for multiple config formats' or 'Add YAML, TOML, INI, HCL, JSON config support' to clearly indicate the scope of changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch langs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rprtr258 rprtr258 force-pushed the langs branch 19 times, most recently from a753c19 to 97abd88 Compare February 1, 2026 09:25
@rprtr258
Copy link
Owner Author

rprtr258 commented Feb 1, 2026

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
go.mod (1)

5-35: 🧹 Nitpick | 🔵 Trivial

Consider updating minor/patch versions and address gopkg.in supply-chain risk.

  • hcl/v2 v2.24.0 and yaml.v3 v3.0.1 are at latest versions with no known vulnerabilities.
  • go-toml/v2 v2.2.3 has a patch update available (v2.2.4, Apr 2025).
  • go-cty v1.16.3 has a minor update available (v1.17.0, Sep 2025).
  • ini.v1 v1.67.0 has a patch update available (v1.67.1, Jan 2026).
  • ⚠️ Supply-chain risk: The gopkg.in domain (used by ini.v1 and yaml.v3) is reported as expiring Mar 5, 2026. Consider a long-term migration strategy or ensure renewal/maintenance of these dependencies.

No active CVEs detected for the current versions; yaml.v3 v3.0.1 includes a fix for CVE-2022-28948 (denial-of-service on invalid input).

internal/core/runconfig.go (1)

369-385: ⚠️ Potential issue | 🟠 Major

Schema fields are silently dropped during DTO mapping.

stdout_file, stderr_file, kill_timeout, autorestart, and max_restarts are in the schema/docs, but the DTOs don’t capture them and they’re hard-coded to zero values here. As a result, config values for these fields are ignored across all formats. Please add these fields to the DTOs/loaders and map them into RunConfig (or remove them from the schema if they’re intentionally unsupported).

🤖 Fix all issues with AI agents
In `@docs/examples/config.toml`:
- Around line 1-13: The example contains an unsupported TOML table array section
`[[web-server]]` that the parser will ignore; remove the entire `[[web-server]]`
block (the duplicate example starting with `[[web-server]]` and its following
keys) so only the supported `[[processes]]` example remains (keep the existing
`name = "web-server"`, `command = "node"`, `args = ["server.js"]`, `env`,
`tags`, and `startup` under `[[processes]]` and delete the alternate
`[[web-server]]` block to avoid confusion).

In `@internal/core/runconfig_test.go`:
- Around line 46-53: The test loop in runconfig_test.go mistakenly asserts
expected values against themselves: change the comparisons in the loop that
currently use config.Watch.Valid and config.Watch.Value.String() on both sides
to compare expected (config) against actual by using actual[i].Watch.Valid and
actual[i].Watch.Value.String() on the RHS; keep the existing niling of
config.Watch.Value and actual[i].Watch.Value to ignore regexp internals. This
specifically affects the assertions that reference config.Watch.Valid and
config.Watch.Value.String() inside the for i, config := range expected loop.
- Around line 225-229: Remove the duplicate table-driven test entry named
"unsupported_format_duplicate" from the tests in runconfig_test.go; it is
identical to the existing "unsupported_format" case (same content, configFile,
and errMsg), so delete the "unsupported_format_duplicate" map entry to avoid
redundant tests and keep only the original "unsupported_format" case.

In `@readme.md`:
- Around line 135-137: The table in the generated README violates MD060 (missing
spaces around pipes); update the markdown generator (look for functions named
generateMarkdownTable, formatTableRow or renderTableSeparator) to emit spaced
pipes (" | ") between cells for the header and separator rows rather than
unspaced pipes, and add/adjust a unit test for table output to assert spaced
pipes for headers and separators.
- Around line 51-83: The README examples don't match the actual loaders: update
the docs generator to emit YAML as a mapping keyed by process name (matching the
YAML loader used in tests) instead of a list-with-name, and update the TOML
example to only show the supported [[processes]] array form (remove the invalid
[[web-server]] direct-table example); if you prefer to change code instead,
extend the YAML loader (e.g., LoadYAML / parseYAML) to accept both map-by-name
and list-of-objects-with-name, and adjust the TOML loader (e.g., LoadTOML /
parseTOML) to accept direct table arrays like [[web-server]]—but per the
comment, modify the README generator to produce the map-keyed YAML and only the
[[processes]] TOML form so docs and loaders stay consistent.
- Around line 51-52: The heading "#### YAML (.yaml, .yml)" is missing a blank
line after it and the adjacent link uses an empty link text
("[YAML](https://yaml.org/)[ ](https://noyaml.com/)"), triggering markdownlint
rules MD022 and MD039; update the generator (or the source) to always emit a
blank line after headings and replace the empty link text with a visible label
(e.g., change the empty "[ ](https://noyaml.com/)" to a meaningful label like
"[No YAML](https://noyaml.com/)"), and apply the same fixes to the other
affected headings/links with the same pattern.

Comment on lines 51 to 83
#### YAML (.yaml, .yml)
[YAML](https://yaml.org/)[ ](https://noyaml.com/)- Human-readable data serialization standard.

```yaml
- name: "web-server"
command: "node"
args: ["server.js"]
env:
PORT: "3000"
NODE_ENV: "production"
tags: ["web"]
startup: true
```

#### TOML (.toml)
[TOML](https://toml.io/) - Tom's Obvious, Minimal Language configuration format.

```toml
[[processes]]
name = "web-server"
command = "node"
args = ["server.js"]
env = { PORT = "3000", NODE_ENV = "production" }
tags = ["web"]
startup = true

# Or direct array format:
[[web-server]]
name = "web-server"
command = "node"
args = ["server.js"]
```

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

YAML/TOML examples don’t match the current loaders.

YAML loading currently expects a map keyed by process name (as used in testdata), while the example is a list with name. TOML loading supports [[processes]] / array-style entries, but the [[web-server]] example won’t parse. Please align docs with the loader, or extend the loader to accept these shapes. Since this file is generated, update the source examples/docs generator accordingly.

🧰 Tools
🪛 markdownlint-cli2 (0.20.0)

[warning] 51-51: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 52-52: Spaces inside link text

(MD039, no-space-in-links)


[warning] 52-52: Spaces inside link text

(MD039, no-space-in-links)


[warning] 65-65: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
In `@readme.md` around lines 51 - 83, The README examples don't match the actual
loaders: update the docs generator to emit YAML as a mapping keyed by process
name (matching the YAML loader used in tests) instead of a list-with-name, and
update the TOML example to only show the supported [[processes]] array form
(remove the invalid [[web-server]] direct-table example); if you prefer to
change code instead, extend the YAML loader (e.g., LoadYAML / parseYAML) to
accept both map-by-name and list-of-objects-with-name, and adjust the TOML
loader (e.g., LoadTOML / parseTOML) to accept direct table arrays like
[[web-server]]—but per the comment, modify the README generator to produce the
map-keyed YAML and only the [[processes]] TOML form so docs and loaders stay
consistent.

Comment on lines +51 to +52
#### YAML (.yaml, .yml)
[YAML](https://yaml.org/)[ ](https://noyaml.com/)- Human-readable data serialization standard.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

markdownlint: add blank lines after headings and avoid empty link text.

MD022 is triggered by missing blank lines after these new headings, and MD039 by the empty link text in the YAML line. Please adjust the generator (or source docs) to insert a blank line after headings and give the noyaml link a visible label.

Also applies to: 65-66, 84-85, 97-98, 113-114

🧰 Tools
🪛 markdownlint-cli2 (0.20.0)

[warning] 51-51: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 52-52: Spaces inside link text

(MD039, no-space-in-links)


[warning] 52-52: Spaces inside link text

(MD039, no-space-in-links)

🤖 Prompt for AI Agents
In `@readme.md` around lines 51 - 52, The heading "#### YAML (.yaml, .yml)" is
missing a blank line after it and the adjacent link uses an empty link text
("[YAML](https://yaml.org/)[ ](https://noyaml.com/)"), triggering markdownlint
rules MD022 and MD039; update the generator (or the source) to always emit a
blank line after headings and replace the empty link text with a visible label
(e.g., change the empty "[ ](https://noyaml.com/)" to a meaningful label like
"[No YAML](https://noyaml.com/)"), and apply the same fixes to the other
affected headings/links with the same pattern.

Comment on lines 135 to +137
| Field | Type | Description | Required |
|-------|------|-------------|----------|
| `name` | `string` | Process name (auto-generated if omitted) | No |
| `name` | `string` | Process name | Yes |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

markdownlint: table pipe spacing.

MD060 indicates missing spaces around pipes in the header row. Please update the markdown generator to emit spaced pipes for tables.

🧰 Tools
🪛 markdownlint-cli2 (0.20.0)

[warning] 136-136: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 136-136: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 136-136: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 136-136: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 136-136: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 136-136: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 136-136: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 136-136: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)

🤖 Prompt for AI Agents
In `@readme.md` around lines 135 - 137, The table in the generated README violates
MD060 (missing spaces around pipes); update the markdown generator (look for
functions named generateMarkdownTable, formatTableRow or renderTableSeparator)
to emit spaced pipes (" | ") between cells for the header and separator rows
rather than unspaced pipes, and add/adjust a unit test for table output to
assert spaced pipes for headers and separators.

@rprtr258
Copy link
Owner Author

rprtr258 commented Feb 1, 2026

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

✅ Actions performed

Full review triggered.

@rprtr258 rprtr258 changed the title add config langs Add support for multiple configuration formats (YAML, TOML, INI, HCL, JSON) Feb 1, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/core/runconfig.go (1)

325-391: ⚠️ Potential issue | 🟠 Major

Allow missing names and generate a fallback name to preserve documented behavior.

RunConfig.Name is documented as generated when absent, and namegen.New() already exists. The current validation rejects nil names, so configs without explicit name now error and the fallback is unreachable. Generate a name when missing (or empty) instead of returning “missing name.”

🛠️ Proposed fix
-		if config.Name == nil {
-			return errors.New("missing name")
-		}
+		name := ""
+		if config.Name != nil && strings.TrimSpace(*config.Name) != "" {
+			name = *config.Name
+		} else {
+			name = namegen.New()
+		}
 
 		return RunConfig{
-			Name:        *config.Name,
+			Name:        name,
 			Command:     config.Command,
 			Args:        args,
+	"github.com/rprtr258/pm/internal/core/namegen"
🤖 Fix all issues with AI agents
In `@docs/main.ts`:
- Line 447: The docs mark the RunConfig field "name" as required but comments
say it is auto-generated if omitted; verify the actual RunConfig behavior
(constructor/validation) and then either (a) update the docs table row
containing code("name") to mark it optional ("No") or (b) update the RunConfig
validation/constructor to enforce presence of name; ensure you modify the single
table entry for code("name") in docs/main.ts or the RunConfig validation logic
accordingly so docs and runtime behavior match.
- Around line 427-429: The YAML list entry currently includes a_external(" ",
"https://noyaml.com/") which creates an empty-label accessible link; either
remove that a_external call or replace the first argument with visible text
(e.g., "No YAML") so the link has an accessible label. Update the array item in
docs/main.ts where the YAML description is built (the line containing a_external
and codeblock(configExamples.yaml, "yaml")) to ensure every a_external(...) uses
non-empty link text or is omitted.

In `@internal/core/runconfig_test.go`:
- Around line 41-56: In assertConfig, stop unconditionally zeroing
actual[i].Cwd; instead only normalize Cwd when the expected Cwd is empty (i.e.,
if expected[i].Cwd == "" then set actual[i].Cwd = ""), and update the test
fixtures that should have absolute Cwd values to set expected.Cwd to the
absolute path so those are validated; apply the same change to the duplicate
logic around lines referenced (the second block at 152-168) so both comparisons
only clear actual Cwd when expected is empty and otherwise compare absolute
paths.

In `@internal/core/runconfig.go`:
- Around line 46-87: The DependsOn fields in configScanDTO and configNamedKeyDTO
are missing toml struct tags so go-toml/v2 won't map TOML's depends_on key;
update the struct definitions (configScanDTO.DependsOn and
configNamedKeyDTO.DependsOn) to include `toml:"depends_on"` alongside the
existing json/yaml tags so TOML unmarshalling preserves dependency ordering.

Comment on lines +427 to +429
["YAML (.yaml, .yml)",
p(a_external("YAML", "https://yaml.org/"), a_external(" ", "https://noyaml.com/"), "- Human-readable data serialization standard."),
codeblock(configExamples.yaml, "yaml")],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid an empty-text external link in the YAML description.
The a_external(" ", ...) renders a link with no accessible label. Give it visible text or remove it.

💡 Suggested adjustment
-        ["YAML (.yaml, .yml)",
-          p(a_external("YAML", "https://yaml.org/"), a_external(" ", "https://noyaml.com/"), "- Human-readable data serialization standard."),
+        ["YAML (.yaml, .yml)",
+          p(a_external("YAML", "https://yaml.org/"), " / ", a_external("No YAML", "https://noyaml.com/"), " - Human-readable data serialization standard."),
           codeblock(configExamples.yaml, "yaml")],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
["YAML (.yaml, .yml)",
p(a_external("YAML", "https://yaml.org/"), a_external(" ", "https://noyaml.com/"), "- Human-readable data serialization standard."),
codeblock(configExamples.yaml, "yaml")],
["YAML (.yaml, .yml)",
p(a_external("YAML", "https://yaml.org/"), " / ", a_external("No YAML", "https://noyaml.com/"), " - Human-readable data serialization standard."),
codeblock(configExamples.yaml, "yaml")],
🤖 Prompt for AI Agents
In `@docs/main.ts` around lines 427 - 429, The YAML list entry currently includes
a_external(" ", "https://noyaml.com/") which creates an empty-label accessible
link; either remove that a_external call or replace the first argument with
visible text (e.g., "No YAML") so the link has an accessible label. Update the
array item in docs/main.ts where the YAML description is built (the line
containing a_external and codeblock(configExamples.yaml, "yaml")) to ensure
every a_external(...) uses non-empty link text or is omitted.

table(
["Field", "Type", "Description", "Required"],
[code("name"), code("string"), "Process name (auto-generated if omitted)", "No"],
[code("name"), code("string"), "Process name", "Yes"],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Double-check whether name is truly required.
RunConfig comments indicate the name is auto-generated if omitted; if that still holds, keep this field optional in docs (or update validation to enforce it).

📌 Possible doc alignment
-          [code("name"),         code("string"),   "Process name",                              "Yes"],
+          [code("name"),         code("string"),   "Process name",                              "No"],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[code("name"), code("string"), "Process name", "Yes"],
[code("name"), code("string"), "Process name", "No"],
🤖 Prompt for AI Agents
In `@docs/main.ts` at line 447, The docs mark the RunConfig field "name" as
required but comments say it is auto-generated if omitted; verify the actual
RunConfig behavior (constructor/validation) and then either (a) update the docs
table row containing code("name") to mark it optional ("No") or (b) update the
RunConfig validation/constructor to enforce presence of name; ensure you modify
the single table entry for code("name") in docs/main.ts or the RunConfig
validation logic accordingly so docs and runtime behavior match.

Comment on lines +41 to +56
func assertConfig(t *testing.T, expected, actual []RunConfig) {
t.Helper()
must.Eq(t, len(expected), len(actual))
slices.SortFunc(expected, func(a, b RunConfig) int { return cmp.Compare(a.Name, b.Name) })
slices.SortFunc(actual, func(a, b RunConfig) int { return cmp.Compare(a.Name, b.Name) })
for i, config := range expected {
must.Eq(t, config.Watch.Valid, actual[i].Watch.Valid)
if config.Watch.Valid {
must.Eq(t, config.Watch.Value.String(), actual[i].Watch.Value.String())
// NOTE: regexp removal
config.Watch.Value = nil
actual[i].Watch.Value = nil
}
actual[i].Cwd = "" // NOTE: temp dir removal
must.Eq(t, config, actual[i])
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don’t blanket-clear Cwd; validate absolute paths in fixtures.
Zeroing actual[i].Cwd for every config hides regressions for fixtures that set absolute Cwd (e.g., JSON/TOML). Consider clearing only when the expected Cwd is empty, and set expected Cwd for those fixtures.

✅ Suggested fix
 func assertConfig(t *testing.T, expected, actual []RunConfig) {
   t.Helper()
   must.Eq(t, len(expected), len(actual))
   slices.SortFunc(expected, func(a, b RunConfig) int { return cmp.Compare(a.Name, b.Name) })
   slices.SortFunc(actual, func(a, b RunConfig) int { return cmp.Compare(a.Name, b.Name) })
   for i, config := range expected {
     must.Eq(t, config.Watch.Valid, actual[i].Watch.Valid)
     if config.Watch.Valid {
       must.Eq(t, config.Watch.Value.String(), actual[i].Watch.Value.String())
       // NOTE: regexp removal
       config.Watch.Value = nil
       actual[i].Watch.Value = nil
     }
-    actual[i].Cwd = "" // NOTE: temp dir removal
+    if config.Cwd == "" {
+      actual[i].Cwd = "" // NOTE: temp dir removal
+    }
     must.Eq(t, config, actual[i])
   }
 }
   "json_simple.json": {
     {
       Name:    "simple-service",
       Command: "echo",
       Args:    []string{"Hello", "JSON!"},
+      Cwd:     "/tmp",
       Env:     map[string]string{"SERVICE_NAME": "test", "VERSION": "1.0.0"},
       Tags:    []string{"simple", "test"},
     },
   },
   "toml_simple.toml": {
     {
       Name:    "simple-service",
       Command: "echo",
       Args:    []string{"Hello", "TOML!"},
+      Cwd:     "/tmp",
       Env:     map[string]string{"SERVICE_NAME": "test", "VERSION": "1.0.0"},
       Tags:    []string{"simple", "test"},
     },
   },

Also applies to: 152-168

🤖 Prompt for AI Agents
In `@internal/core/runconfig_test.go` around lines 41 - 56, In assertConfig, stop
unconditionally zeroing actual[i].Cwd; instead only normalize Cwd when the
expected Cwd is empty (i.e., if expected[i].Cwd == "" then set actual[i].Cwd =
""), and update the test fixtures that should have absolute Cwd values to set
expected.Cwd to the absolute path so those are validated; apply the same change
to the duplicate logic around lines referenced (the second block at 152-168) so
both comparisons only clear actual Cwd when expected is empty and otherwise
compare absolute paths.

Comment on lines +46 to 87
type configScanDTO struct {
Name *string `json:"name"`
Cwd *string `json:"cwd"`
Env map[string]string `json:"env"`
Command string `json:"command"`
Args []any `json:"args"`
Tags []string `json:"tags"`
Watch *string `json:"watch"`
Startup bool `json:"startup"`
DependsOn []string `json:"depends_on" yaml:"depends_on"`
Cron *string `json:"cron"`
}

type configNamedKeyDTO struct {
Cwd *string `json:"cwd"`
Env map[string]string `json:"env"`
Command string `json:"command"`
Args []any `json:"args"`
Tags []string `json:"tags"`
Watch *string `json:"watch"`
Startup bool `json:"startup"`
DependsOn []string `json:"depends_on" yaml:"depends_on"`
Cron *string `json:"cron"`
}

return !stat.IsDir()
type hclProcess struct {
Name string `hcl:"name,label"`
Cwd *string `hcl:"cwd,optional"`
Env map[string]string `hcl:"env,optional"`
Command string `hcl:"command"`
Args []string `hcl:"args,optional"`
Tags []string `hcl:"tags,optional"`
Watch *string `hcl:"watch,optional"`
Startup bool `hcl:"startup,optional"`
DependsOn []string `hcl:"depends_on,optional"`
Cron *string `hcl:"cron,optional"`
Body hcl.Body `hcl:",remain"`
}

type hclConfigFile struct {
Processes []*hclProcess `hcl:"process,block"`
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

go-toml/v2 struct tags json tag behavior unmarshalling

💡 Result:

In github.com/pelletier/go-toml/v2, struct field naming/options for both marshaling and unmarshaling are controlled by the toml struct tag (not json). The toml tag’s format intentionally mirrors encoding/json: toml:"name,opt1,opt2" (name can be empty to specify only options). Supported options include multiline, inline, omitempty, and commented (and a separate comment tag exists for emitting comments). [1]

For unmarshaling, v2 matches keys to struct fields in a case-insensitive way (similar to encoding/json). If you need exact control (e.g., to avoid ambiguous casing), you should explicitly set field names with toml:"...". [2]

Implication: if your struct only has json:"..." tags, go-toml/v2 will not use those names; add toml:"..." tags to get the desired mapping. [1]

Sources:
[1] https://pkg.go.dev/github.com/pelletier/go-toml/v2 (Struct tags section)
[2] https://pkg.go.dev/github.com/pelletier/go-toml/v2 (Migrating from v1 → Automatic field name guessing)


🏁 Script executed:

# Check how TOML unmarshalling is used in the file
rg -A 5 -B 5 "json.Unmarshal|toml.Unmarshal|yaml.Unmarshal" internal/core/runconfig.go | head -80

Repository: rprtr258/pm

Length of output: 1003


Add explicit toml tags for depends_on to ensure TOML configs parse dependency ordering.

go-toml/v2 does not honor json struct tags during unmarshalling—it uses only toml tags. Without toml:"depends_on" tags on the DependsOn fields in configScanDTO and configNamedKeyDTO, TOML-formatted configs will silently drop the depends_on key and lose dependency ordering.

🔧 Proposed fix
 type configScanDTO struct {
 	Name      *string           `json:"name"`
 	Cwd       *string           `json:"cwd"`
 	Env       map[string]string `json:"env"`
 	Command   string            `json:"command"`
 	Args      []any             `json:"args"`
 	Tags      []string          `json:"tags"`
 	Watch     *string           `json:"watch"`
 	Startup   bool              `json:"startup"`
-	DependsOn []string          `json:"depends_on" yaml:"depends_on"`
+	DependsOn []string          `json:"depends_on" yaml:"depends_on" toml:"depends_on"`
 	Cron      *string           `json:"cron"`
 }
 
 type configNamedKeyDTO struct {
 	Cwd       *string           `json:"cwd"`
 	Env       map[string]string `json:"env"`
 	Command   string            `json:"command"`
 	Args      []any             `json:"args"`
 	Tags      []string          `json:"tags"`
 	Watch     *string           `json:"watch"`
 	Startup   bool              `json:"startup"`
-	DependsOn []string          `json:"depends_on" yaml:"depends_on"`
+	DependsOn []string          `json:"depends_on" yaml:"depends_on" toml:"depends_on"`
 	Cron      *string           `json:"cron"`
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type configScanDTO struct {
Name *string `json:"name"`
Cwd *string `json:"cwd"`
Env map[string]string `json:"env"`
Command string `json:"command"`
Args []any `json:"args"`
Tags []string `json:"tags"`
Watch *string `json:"watch"`
Startup bool `json:"startup"`
DependsOn []string `json:"depends_on" yaml:"depends_on"`
Cron *string `json:"cron"`
}
type configNamedKeyDTO struct {
Cwd *string `json:"cwd"`
Env map[string]string `json:"env"`
Command string `json:"command"`
Args []any `json:"args"`
Tags []string `json:"tags"`
Watch *string `json:"watch"`
Startup bool `json:"startup"`
DependsOn []string `json:"depends_on" yaml:"depends_on"`
Cron *string `json:"cron"`
}
return !stat.IsDir()
type hclProcess struct {
Name string `hcl:"name,label"`
Cwd *string `hcl:"cwd,optional"`
Env map[string]string `hcl:"env,optional"`
Command string `hcl:"command"`
Args []string `hcl:"args,optional"`
Tags []string `hcl:"tags,optional"`
Watch *string `hcl:"watch,optional"`
Startup bool `hcl:"startup,optional"`
DependsOn []string `hcl:"depends_on,optional"`
Cron *string `hcl:"cron,optional"`
Body hcl.Body `hcl:",remain"`
}
type hclConfigFile struct {
Processes []*hclProcess `hcl:"process,block"`
}
type configScanDTO struct {
Name *string `json:"name"`
Cwd *string `json:"cwd"`
Env map[string]string `json:"env"`
Command string `json:"command"`
Args []any `json:"args"`
Tags []string `json:"tags"`
Watch *string `json:"watch"`
Startup bool `json:"startup"`
DependsOn []string `json:"depends_on" yaml:"depends_on" toml:"depends_on"`
Cron *string `json:"cron"`
}
type configNamedKeyDTO struct {
Cwd *string `json:"cwd"`
Env map[string]string `json:"env"`
Command string `json:"command"`
Args []any `json:"args"`
Tags []string `json:"tags"`
Watch *string `json:"watch"`
Startup bool `json:"startup"`
DependsOn []string `json:"depends_on" yaml:"depends_on" toml:"depends_on"`
Cron *string `json:"cron"`
}
type hclProcess struct {
Name string `hcl:"name,label"`
Cwd *string `hcl:"cwd,optional"`
Env map[string]string `hcl:"env,optional"`
Command string `hcl:"command"`
Args []string `hcl:"args,optional"`
Tags []string `hcl:"tags,optional"`
Watch *string `hcl:"watch,optional"`
Startup bool `hcl:"startup,optional"`
DependsOn []string `hcl:"depends_on,optional"`
Cron *string `hcl:"cron,optional"`
Body hcl.Body `hcl:",remain"`
}
type hclConfigFile struct {
Processes []*hclProcess `hcl:"process,block"`
}
🤖 Prompt for AI Agents
In `@internal/core/runconfig.go` around lines 46 - 87, The DependsOn fields in
configScanDTO and configNamedKeyDTO are missing toml struct tags so go-toml/v2
won't map TOML's depends_on key; update the struct definitions
(configScanDTO.DependsOn and configNamedKeyDTO.DependsOn) to include
`toml:"depends_on"` alongside the existing json/yaml tags so TOML unmarshalling
preserves dependency ordering.

@rprtr258 rprtr258 merged commit b9a5084 into master Feb 1, 2026
4 checks passed
@rprtr258 rprtr258 deleted the langs branch February 1, 2026 10:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant