diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 5c3d2ad..be3d36b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: ./
with:
- search-paths: test/good.json test/good.yaml test/good.toml test/good.xml test/good.ini
+ search-paths: test/good.json test/good.yaml test/good.toml test/good.xml test/good.ini test/justfile
# Validate that bad files cause a non-zero exit
test-failing-files:
@@ -206,3 +206,47 @@ jobs:
echo "Expected validation to fail when schema is required but not declared"
exit 1
fi
+
+ # Embedded schemastore with remote fetching
+ test-schemastore:
+ name: "Test: schemastore (embedded)"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ - uses: ./
+ with:
+ search-paths: test/good.json
+ schemastore: "true"
+
+ # Validate only changed files
+ test-only-changed:
+ name: "Test: only-changed"
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+ steps:
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ with:
+ fetch-depth: 0
+ - uses: ./
+ with:
+ only-changed: "true"
+
+ # Verify outputs are set
+ test-outputs:
+ name: "Test: outputs"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ - uses: ./
+ id: validate
+ with:
+ search-paths: test/good.json
+ - name: Verify outputs
+ run: |
+ if [ -z "${{ steps.validate.outputs.files-validated }}" ]; then
+ echo "Missing files-validated output"
+ exit 1
+ fi
+ echo "files-validated=${{ steps.validate.outputs.files-validated }}"
+ echo "files-failed=${{ steps.validate.outputs.files-failed }}"
+ echo "exit-code=${{ steps.validate.outputs.exit-code }}"
diff --git a/Dockerfile b/Dockerfile
index b8c2810..29fe7f4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,6 +7,7 @@ COPY cmd/ cmd/
RUN CGO_ENABLED=0 go build -ldflags='-w -s' -o /entrypoint cmd/entrypoint/main.go
FROM alpine:3.23@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659
+RUN apk --no-cache add git
COPY --from=builder /entrypoint /entrypoint
RUN chmod 0755 /entrypoint
WORKDIR /github/workspace
diff --git a/README.md b/README.md
index 9e88e63..d2997a1 100644
--- a/README.md
+++ b/README.md
@@ -9,258 +9,282 @@
-:octocat: Github Action to validate your config files using the [config-file-validator](https://github.com/Boeing/config-file-validator). The config-file-validator will recursively scan the provided search path for the following configuration file types:
+Validate every config file in your repo — JSON, YAML, TOML, XML, INI, HCL, and [more](#inputs). If something's wrong, you'll see the error right on the PR diff, exactly where it broke.
-* Apple PList XML
-* CSV
-* EDITORCONFIG
-* ENV
-* HCL
-* HOCON
-* INI
-* JSON
-* Properties
-* SARIF
-* TOML
-* TOON
-* XML
-* YAML
+Files with a schema declaration (JSON Schema, XSD) get validated against it automatically. You can optionally add [SchemaStore](#schemastore) to get schema validation for hundreds of common files like `package.json`, `tsconfig.json`, and GitHub Actions workflows with no additional configuration.
-Each file will get validated for the correct syntax and the results collected into a report showing the path of the file and if it is invalid or valid. If the file is invalid an error will be displayed along with the line number and column where the error ocurred. By default the `$GITHUB_WORKDIR` is scanned.
+## Quick Start
-Files that declare a schema (JSON Schema for JSON/YAML/TOML/TOON, XSD for XML) are automatically validated against it.
+```yaml
+- uses: Boeing/validate-configs-action@v2
+```
-## PR inline annotations
+## Usage
-Validation errors automatically appear as inline annotations on pull request diffs. When a config file fails validation, the action emits GitHub Actions workflow commands that annotate the exact file and line where the error occurred. For config types that do not support line numbers in the error output, an annotation will be added at line 1.
+```yaml
+jobs:
+ validate:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: Boeing/validate-configs-action@v2
+```
-## Inputs
+### Only changed files
+
+For large repos, you probably don't want to validate everything on every PR. This only checks files that were actually changed:
+
+```yaml
+- uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+- uses: Boeing/validate-configs-action@v2
+ with:
+ only-changed: "true"
+```
+
+`fetch-depth: 0` is required so git has the history to determine which files changed.
+
+### Exclude directories
+
+Skip directories you don't care about — vendored deps, test fixtures, generated files:
+
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ exclude-dirs: "vendor,testdata,node_modules"
+```
+
+### SchemaStore
+
+[SchemaStore](https://www.schemastore.org/) is a community catalog of JSON Schemas for common config files. Turn it on and files like `package.json`, `tsconfig.json`, and GitHub Actions workflows get validated against their schema automatically — no `$schema` declarations needed in your files:
+
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ schemastore: "true"
+```
+
+### Outputs
+
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ id: validate
+ continue-on-error: true
+- run: |
+ echo "Files validated: ${{ steps.validate.outputs.files-validated }}"
+ echo "Files failed: ${{ steps.validate.outputs.files-failed }}"
+```
-| Input | Required | Default Value | Description |
-| ------------------ | -------- | ------------- | ----------- |
-| search-paths | false | `"."` | The path that will be recursively searched for configuration files |
-| exclude-dirs | false | `""` | A comma-separated list of subdirectories to exclude from validation |
-| exclude-file-types | false | `""` | A comma-separated list of file extensions to exclude |
-| file-types | false | `""` | A comma-separated list of file types to validate. Cannot be used with exclude-file-types |
-| depth | false | `""` | An integer value limiting the depth of recursion for the search paths. Setting depth to 0 disables recursion |
-| reporter | false | `"standard"` | Comma-separated report formats with optional output paths. Format: `type:path`. Options are `standard`, `json`, `junit`, and `sarif` |
-| group-by | false | `""` | Group output by `filetype`, `directory`, or `pass-fail` |
-| quiet | false | `"false"` | If set to `true`, suppresses all output to stdout |
-| globbing | false | `"false"` | If set to `true`, enables glob pattern matching for search paths |
-| require-schema | false | `"false"` | If set to `true`, fail validation for files that support schema validation but do not declare a schema |
-| no-schema | false | `"false"` | If set to `true`, disable all schema validation (syntax-only). Cannot be used with `require-schema`, `schema-map`, or `schemastore` |
-| schemastore | false | `""` | Path to a local SchemaStore clone for automatic schema lookup by filename |
-| type-map | false | `""` | Comma-separated glob pattern to file type mappings. Format: `pattern:type` |
-| schema-map | false | `""` | Comma-separated glob pattern to schema file mappings. Format: `pattern:schema_path` |
+## Inputs
+| Input | Default | Description |
+|-------|---------|-------------|
+| `search-paths` | `"."` | Space-separated list of directories or files to scan |
+| `exclude-dirs` | `""` | Comma-separated list of subdirectories to exclude |
+| `exclude-file-types` | `""` | Comma-separated list of file extensions to exclude |
+| `file-types` | `""` | Comma-separated list of file types to validate. Cannot be used with `exclude-file-types` |
+| `depth` | `""` | Recursion depth limit. `0` disables recursion |
+| `reporter` | `"standard"` | Report format(s). Options: `standard`, `json`, `junit`, `sarif`. Supports `type:path` for file output. Multiple reporters can be comma-separated. If you only specify file reporters, add `standard` to keep console output |
+| `group-by` | `""` | Group output by `filetype`, `directory`, or `pass-fail` |
+| `quiet` | `"false"` | Suppress all output to stdout |
+| `globbing` | `"false"` | Enable glob pattern matching for search paths. Cannot be used with `exclude-dirs`, `exclude-file-types`, or `file-types` |
+| `require-schema` | `"false"` | Fail files that support schema validation but don't declare a schema. Cannot be used with `no-schema` |
+| `no-schema` | `"false"` | Disable all schema validation (syntax-only). Cannot be used with `require-schema`, `schema-map`, or `schemastore` |
+| `schemastore` | `"false"` | Enable automatic schema lookup using the embedded [SchemaStore](https://www.schemastore.org/) catalog |
+| `schemastore-path` | `""` | Path to a local SchemaStore clone. For air-gapped environments. Implies `schemastore` |
+| `type-map` | `""` | Map glob patterns to file types. Format: `pattern:type`. Valid types: `csv`, `editorconfig`, `env`, `hcl`, `hocon`, `ini`, `json`, `jsonc`, `plist`, `properties`, `sarif`, `toml`, `toon`, `xml`, `yaml` |
+| `schema-map` | `""` | Map glob patterns to schema files. Format: `pattern:schema_path`. Use JSON Schema (`.json`) for JSON/JSONC/YAML/TOML/TOON, XSD (`.xsd`) for XML. Paths are relative to the repo root |
+| `only-changed` | `"false"` | Only validate files changed in the current pull request |
+| `gitignore` | `"false"` | Skip files and directories matched by `.gitignore` patterns |
## Outputs
-N/A
+| Output | Description |
+|--------|-------------|
+| `files-validated` | Total number of files scanned |
+| `files-failed` | Number of files that failed validation |
+| `exit-code` | Exit code from validation (0=success, 1=validation errors, 2=runtime error) |
-## Example usage
+## Examples
-### Standard Run
+
+Filtering
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
+#### Exclude directories
+
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ exclude-dirs: "tests,vendor,node_modules"
```
-### Custom search path
+#### Exclude file types
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- search-paths: ./project/configs
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ exclude-file-types: "json,xml"
```
-### Multiple search paths
+#### Include only specific file types
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- search-paths: ./project/configs ./project/devops
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ file-types: "json,yaml"
```
-### Exclude a directory
+#### Disable recursive scanning
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- exclude-dirs: "tests,vendor"
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ depth: 0
```
-### Exclude file type
+#### Glob pattern matching
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- exclude-file-types: "json,xml"
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ globbing: "true"
+ search-paths: "**/*.json"
```
-### Include only specific file types
+
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- file-types: "json,yaml"
+
+Reporters
+
+#### JSON report
+
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ reporter: "json"
```
-### Disable recursive scanning
+#### Multiple reporters with file output
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- depth: 0
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ reporter: "json:output.json,junit:results.xml,sarif:results.sarif"
```
-### JSON Report
+#### Group by pass/fail
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- reporter: "json"
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ group-by: "pass-fail"
```
-### Multiple reporters with output files
+#### Quiet mode
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- reporter: "json:output.json,junit:results.xml"
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ quiet: "true"
```
-### Group By Pass/Fail
+
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- group-by: "pass-fail"
+
+Schema Validation
+
+#### Automatic schema lookup with SchemaStore
+
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ schemastore: "true"
```
-### Quiet mode
+#### Local SchemaStore clone (air-gapped)
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- quiet: "true"
+```yaml
+- run: git clone --depth=1 https://github.com/SchemaStore/schemastore.git
+- uses: Boeing/validate-configs-action@v2
+ with:
+ schemastore-path: "./schemastore"
```
-### Glob pattern matching
+#### Require schema declarations
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- globbing: "true"
- search-paths: "**/*.json"
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ require-schema: "true"
```
-### Require schema declarations
+#### Disable schema validation
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- require-schema: "true"
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ no-schema: "true"
```
-### Disable schema validation
+#### Map schemas to files
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- no-schema: "true"
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ schema-map: "**/package.json:schemas/package.schema.json,**/config.xml:schemas/config.xsd"
```
-### Automatic schema lookup with SchemaStore
+
+
+
+Advanced
+
+#### Combined options
+
+```yaml
+- uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+- uses: Boeing/validate-configs-action@v2
+ id: validate
+ with:
+ only-changed: "true"
+ exclude-dirs: "vendor,generated,testdata"
+ schema-map: "**/app-config.json:schemas/app.schema.json,**/deploy.xml:schemas/deploy.xsd"
+ type-map: "**/inventory:ini,**/.env.*:env"
+ reporter: "standard,junit:results.xml"
+ schemastore: "true"
+```
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - run: git clone --depth=1 https://github.com/SchemaStore/schemastore.git
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- schemastore: "./schemastore"
+#### Map file types with glob patterns
+
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ with:
+ type-map: "**/inventory:ini,**/*.cfg:json"
```
-### Map file types with glob patterns
+#### Validate only changed files in a PR
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- type-map: "**/inventory:ini,**/*.cfg:json"
+```yaml
+- uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+- uses: Boeing/validate-configs-action@v2
+ with:
+ only-changed: "true"
```
-### Map schemas to files
+#### Using outputs
-```yml
-jobs:
- validate-config-files:
- runs-on: ubuntu-latest
- steps:
- - uses: Boeing/validate-configs-action@v2.0.0
- with:
- schema-map: "**/package.json:schemas/package.schema.json,**/config.xml:schemas/config.xsd"
-```
\ No newline at end of file
+```yaml
+- uses: Boeing/validate-configs-action@v2
+ id: validate
+ continue-on-error: true
+- run: |
+ echo "Files validated: ${{ steps.validate.outputs.files-validated }}"
+ echo "Files failed: ${{ steps.validate.outputs.files-failed }}"
+```
+
+
diff --git a/action.yaml b/action.yaml
index 35806ef..914d18c 100644
--- a/action.yaml
+++ b/action.yaml
@@ -49,7 +49,11 @@ inputs:
required: false
default: "false"
schemastore:
- description: 'Path to a local SchemaStore clone for automatic schema lookup by filename'
+ description: 'If set to true, enables automatic schema lookup using the embedded SchemaStore catalog with remote fetching'
+ required: false
+ default: "false"
+ schemastore-path:
+ description: 'Path to a local SchemaStore clone for automatic schema lookup (implies schemastore). For air-gapped environments'
required: false
default: ""
type-map:
@@ -60,6 +64,22 @@ inputs:
description: 'Comma separated list of glob pattern to schema file mappings. Format: pattern:schema_path. Example: "**/package.json:schemas/pkg.json,**/config.xml:schemas/config.xsd"'
required: false
default: ""
+ gitignore:
+ description: 'If set to true, skip files and directories matched by .gitignore patterns'
+ required: false
+ default: "false"
+ only-changed:
+ description: 'If set to true, only validate files changed in the current pull request'
+ required: false
+ default: "false"
+
+outputs:
+ files-validated:
+ description: 'Total number of files scanned'
+ files-failed:
+ description: 'Number of files that failed validation'
+ exit-code:
+ description: 'Exit code from validation (0=success, 1=validation errors, 2=runtime error)'
runs:
using: 'docker'
@@ -77,8 +97,11 @@ runs:
- ${{ inputs.require-schema }}
- ${{ inputs.no-schema }}
- ${{ inputs.schemastore }}
+ - ${{ inputs.schemastore-path }}
- ${{ inputs.type-map }}
- ${{ inputs.schema-map }}
+ - ${{ inputs.gitignore }}
+ - ${{ inputs.only-changed }}
branding:
color: 'blue'
diff --git a/cmd/entrypoint/main.go b/cmd/entrypoint/main.go
index 2cd5244..f4d34e5 100644
--- a/cmd/entrypoint/main.go
+++ b/cmd/entrypoint/main.go
@@ -3,6 +3,8 @@ package main
import (
"fmt"
"os"
+ "os/exec"
+ "path/filepath"
"regexp"
"strconv"
"strings"
@@ -22,7 +24,6 @@ var (
reColNum = regexp.MustCompile(`column (\d+)`)
)
-// captureReporter captures reports for annotation processing
type captureReporter struct {
reports []reporter.Report
}
@@ -48,9 +49,12 @@ func run() int {
globbing := os.Args[9]
requireSchema := os.Args[10]
noSchema := os.Args[11]
- schemaStorePath := os.Args[12]
- typeMap := os.Args[13]
- schemaMap := os.Args[14]
+ schemaStoreEnabled := os.Args[12]
+ schemaStorePath := os.Args[13]
+ typeMap := os.Args[14]
+ schemaMap := os.Args[15]
+ gitignoreEnabled := os.Args[16]
+ onlyChanged := os.Args[17]
// Build finder options
var fsOpts []finder.FSFinderOptions
@@ -63,7 +67,7 @@ func run() int {
expanded, err := expandGlobs(paths)
if err != nil {
fmt.Fprintf(os.Stderr, "Error expanding globs: %v\n", err)
- return 1
+ return 2
}
paths = expanded
}
@@ -104,15 +108,31 @@ func run() int {
overrides, err := parseTypeMap(typeMap)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing type-map: %v\n", err)
- return 1
+ return 2
}
fsOpts = append(fsOpts, finder.WithTypeOverrides(overrides))
}
+ if gitignoreEnabled == "true" {
+ fsOpts = append(fsOpts, finder.WithGitignore(true))
+ }
+
// Build CLI options
var cliOpts []cli.Option
- fileFinder := finder.FileSystemFinderInit(fsOpts...)
+ var fileFinder finder.FileFinder
+ fileFinder = finder.FileSystemFinderInit(fsOpts...)
+
+ // Filter to only changed files if requested
+ if onlyChanged == "true" {
+ changed, err := getChangedFiles()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: could not determine changed files: %v\n", err)
+ } else if len(changed) > 0 {
+ fileFinder = &changedFilesFilter{inner: fileFinder, changed: changed}
+ }
+ }
+
cliOpts = append(cliOpts, cli.WithFinder(fileFinder))
if quiet == "true" {
@@ -131,7 +151,7 @@ func run() int {
sm, err := parseSchemaMap(schemaMap)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing schema-map: %v\n", err)
- return 1
+ return 2
}
cliOpts = append(cliOpts, cli.WithSchemaMap(sm))
}
@@ -139,12 +159,18 @@ func run() int {
store, err := schemastore.Open(schemaStorePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening schemastore: %v\n", err)
- return 1
+ return 2
+ }
+ cliOpts = append(cliOpts, cli.WithSchemaStore(store))
+ } else if schemaStoreEnabled == "true" {
+ store, err := schemastore.OpenEmbedded()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error opening embedded schemastore: %v\n", err)
+ return 2
}
cliOpts = append(cliOpts, cli.WithSchemaStore(store))
}
- // Build reporters — user-specified plus a capture reporter for annotations
capture := &captureReporter{}
reporters := buildReporters(reporterArg)
reporters = append(reporters, capture)
@@ -157,10 +183,162 @@ func run() int {
}
emitAnnotations(capture.reports)
+ emitNotes(capture.reports)
+ writeOutputs(capture.reports, exitStatus)
+ writeJobSummary(capture.reports)
return exitStatus
}
+// changedFilesFilter wraps a FileFinder and filters results to only changed files.
+type changedFilesFilter struct {
+ inner finder.FileFinder
+ changed map[string]struct{}
+}
+
+func (f *changedFilesFilter) Find() ([]finder.FileMetadata, error) {
+ all, err := f.inner.Find()
+ if err != nil {
+ return nil, err
+ }
+ var filtered []finder.FileMetadata
+ for _, file := range all {
+ rel := file.Path
+ if abs, err := filepath.Abs(file.Path); err == nil {
+ if wd, err := os.Getwd(); err == nil {
+ if r, err := filepath.Rel(wd, abs); err == nil {
+ rel = r
+ }
+ }
+ }
+ if _, ok := f.changed[rel]; ok {
+ filtered = append(filtered, file)
+ }
+ }
+ return filtered, nil
+}
+
+func getChangedFiles() (map[string]struct{}, error) {
+ baseBranch := os.Getenv("GITHUB_BASE_REF")
+ if baseBranch == "" {
+ return nil, fmt.Errorf("GITHUB_BASE_REF not set (not a pull request?)")
+ }
+
+ // Docker containers run as root but the workspace is owned by the runner user
+ safe := exec.Command("git", "config", "--global", "--add", "safe.directory", "/github/workspace")
+ safe.Run()
+
+ fetch := exec.Command("git", "fetch", "origin", baseBranch, "--depth=1")
+ fetch.Stderr = os.Stderr
+ if err := fetch.Run(); err != nil {
+ return nil, fmt.Errorf("git fetch: %w", err)
+ }
+
+ cmd := exec.Command("git", "diff", "--name-only", "origin/"+baseBranch+"...HEAD")
+ out, err := cmd.Output()
+ if err != nil {
+ return nil, fmt.Errorf("git diff: %w", err)
+ }
+
+ changed := make(map[string]struct{})
+ for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
+ if line != "" {
+ changed[line] = struct{}{}
+ }
+ }
+ return changed, nil
+}
+
+func writeOutputs(reports []reporter.Report, exitCode int) {
+ outputFile := os.Getenv("GITHUB_OUTPUT")
+ if outputFile == "" {
+ return
+ }
+
+ total := len(reports)
+ failed := 0
+ for _, r := range reports {
+ if !r.IsValid {
+ failed++
+ }
+ }
+
+ f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY, 0644)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+
+ fmt.Fprintf(f, "files-validated=%d\n", total)
+ fmt.Fprintf(f, "files-failed=%d\n", failed)
+ fmt.Fprintf(f, "exit-code=%d\n", exitCode)
+}
+
+func writeJobSummary(reports []reporter.Report) {
+ summaryFile := os.Getenv("GITHUB_STEP_SUMMARY")
+ if summaryFile == "" {
+ return
+ }
+
+ total := len(reports)
+ passed := 0
+ failed := 0
+ var failedReports []reporter.Report
+ for _, r := range reports {
+ if r.IsValid {
+ passed++
+ } else {
+ failed++
+ failedReports = append(failedReports, r)
+ }
+ }
+
+ f, err := os.OpenFile(summaryFile, os.O_APPEND|os.O_WRONLY, 0644)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+
+ if failed == 0 {
+ fmt.Fprintf(f, "### ✅ Config Validation Passed\n\n")
+ fmt.Fprintf(f, "All **%d** configuration files are valid.\n", total)
+ return
+ }
+
+ fmt.Fprintf(f, "### ❌ Config Validation Failed\n\n")
+ fmt.Fprintf(f, "| | Count |\n|---|---|\n")
+ fmt.Fprintf(f, "| ✅ Passed | %d |\n", passed)
+ fmt.Fprintf(f, "| ❌ Failed | %d |\n", failed)
+ fmt.Fprintf(f, "| **Total** | **%d** |\n\n", total)
+
+ fmt.Fprintf(f, "#### Failed Files\n\n")
+ fmt.Fprintf(f, "| File | Errors |\n|---|---|\n")
+ for _, r := range failedReports {
+ path := r.FilePath
+ if strings.HasPrefix(path, "/github/workspace/") {
+ path = path[len("/github/workspace/"):]
+ }
+ errors := strings.Join(r.ValidationErrors, "
")
+ fmt.Fprintf(f, "| `%s` | %s |\n", path, errors)
+ }
+}
+
+func emitNotes(reports []reporter.Report) {
+ const workspacePrefix = "/github/workspace/"
+ for _, r := range reports {
+ if len(r.Notes) == 0 {
+ continue
+ }
+ path := r.FilePath
+ if strings.HasPrefix(path, workspacePrefix) {
+ path = path[len(workspacePrefix):]
+ }
+ for _, note := range r.Notes {
+ fmt.Printf("::notice file=%s,title=Note::%s\n", path, escapeAnnotation(note))
+ }
+ }
+}
+
func buildReporters(arg string) []reporter.Reporter {
if arg == "" {
return []reporter.Reporter{reporter.NewStdoutReporter("")}
@@ -278,7 +456,7 @@ func emitAnnotations(reports []reporter.Report) {
path = path[len(workspacePrefix):]
}
- for _, errMsg := range r.ValidationErrors {
+ for i, errMsg := range r.ValidationErrors {
title := "Validation Error"
msg := errMsg
if strings.HasPrefix(errMsg, "schema: ") {
@@ -289,7 +467,19 @@ func emitAnnotations(reports []reporter.Report) {
msg = errMsg[8:]
}
- line, col := parseLine(msg)
+ // Use per-error positions from report when available,
+ // fall back to regex parsing for compatibility.
+ var line, col int
+ if i < len(r.ErrorLines) && r.ErrorLines[i] > 0 {
+ line = r.ErrorLines[i]
+ }
+ if i < len(r.ErrorColumns) && r.ErrorColumns[i] > 0 {
+ col = r.ErrorColumns[i]
+ }
+ if line == 0 {
+ line, col = parseLine(msg)
+ }
+
key := fmt.Sprintf("%s|%d|%d|%s", path, line, col, title)
if a, ok := groups[key]; ok {
a.msgs = append(a.msgs, msg)
diff --git a/go.mod b/go.mod
index 936d077..a682577 100644
--- a/go.mod
+++ b/go.mod
@@ -3,21 +3,27 @@ module github.com/Boeing/validate-configs-action
go 1.26.1
require (
- github.com/Boeing/config-file-validator/v2 v2.1.0
+ github.com/Boeing/config-file-validator/v2 v2.2.0
github.com/bmatcuk/doublestar/v4 v4.10.0
)
require (
+ github.com/Boeing/go-just v0.0.0 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
+ github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4 // indirect
github.com/fatih/color v1.19.0 // indirect
+ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
+ github.com/go-git/go-billy/v5 v5.8.0 // indirect
+ github.com/go-git/go-git/v5 v5.18.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gurkankaymak/hocon v1.2.23 // indirect
github.com/hashicorp/go-envparse v0.1.0 // indirect
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
+ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/lestrrat-go/helium v0.0.1 // indirect
github.com/lestrrat-go/pdebug v0.0.0-20210111095411-35b07dbf089b // indirect
github.com/magiconair/properties v1.8.10 // indirect
@@ -26,17 +32,20 @@ require (
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/owenrumney/go-sarif/v3 v3.3.0 // indirect
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
+ github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd // indirect
github.com/toon-format/toon-go v0.0.0-20251108125615-44b4cd22477f // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/zclconf/go-cty v1.13.0 // indirect
- golang.org/x/mod v0.33.0 // indirect
+ golang.org/x/mod v0.34.0 // indirect
+ golang.org/x/net v0.52.0 // indirect
golang.org/x/sync v0.20.0 // indirect
- golang.org/x/sys v0.42.0 // indirect
- golang.org/x/text v0.35.0 // indirect
- golang.org/x/tools v0.42.0 // indirect
+ golang.org/x/sys v0.43.0 // indirect
+ golang.org/x/text v0.36.0 // indirect
+ golang.org/x/tools v0.43.0 // indirect
gopkg.in/ini.v1 v1.67.1 // indirect
+ gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 7ad44f2..81454a1 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,3 @@
-github.com/Boeing/config-file-validator/v2 v2.1.0 h1:3OrOjsx6ySZ/UoLqDFoa8425Ov0E3ItE8oqvZMrKxvA=
-github.com/Boeing/config-file-validator/v2 v2.1.0/go.mod h1:ZlcTuAwvot3E55UyN5muDRsob0ThU1Lc3PAtiZhim8Y=
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
@@ -8,6 +6,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
+github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
+github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -15,6 +15,12 @@ github.com/editorconfig/editorconfig-core-go/v2 v2.6.4 h1:CHwUbBVVyKWRX9kt5A/Otw
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4/go.mod h1:JWRVKHdVW+dkv6F8p+xGCa6a+TyMrqsFbFkSs/aQkrQ=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
+github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
+github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
+github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM=
+github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -27,7 +33,13 @@ github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdm
github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/helium v0.0.1 h1:jNr9x4TOWH9JQpFPtJzAyWiuFuimILNuySL/RJwEf8Y=
github.com/lestrrat-go/helium v0.0.1/go.mod h1:IWo9QUgTVpC8nSbOr+NlOF+t6CXnViTvfR0p/k9IhQQ=
github.com/lestrrat-go/pdebug v0.0.0-20210111095411-35b07dbf089b h1:2v0K4PeWeccG1wpznCE71PqO5scFzSj3jZGkQaVYEWg=
@@ -40,12 +52,18 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
+github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/owenrumney/go-sarif/v3 v3.3.0 h1:p5oSxEV0uPWBRpAspTmwWr4t1YZyKUpdoFzSB7WE90A=
github.com/owenrumney/go-sarif/v3 v3.3.0/go.mod h1:72MaugkExDexbSauRuPq6BvUAAqAX0TwoNYMIQyZCMw=
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -57,6 +75,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd h1:Rf9uhF1+VJ7ZHqxrG8pJ6YacmHvVCmByDmGbAWCc/gA=
+github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo=
github.com/toon-format/toon-go v0.0.0-20251108125615-44b4cd22477f h1:qIMJqAPGPH7S4uVRaHflMfJ/ZenGp7W1tWECmVoJitM=
github.com/toon-format/toon-go v0.0.0-20251108125615-44b4cd22477f/go.mod h1:j/BOnpF2ihnz4lELs99h9mwGJBx/zdleOUCnLLRPCsc=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
@@ -69,21 +89,28 @@ github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0
github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
-golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
-golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
+golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
+golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
+golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
+golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
-golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
-golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
-golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
-golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
-golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
+golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
+golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
+golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
+golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/test/justfile b/test/justfile
new file mode 100644
index 0000000..1477033
--- /dev/null
+++ b/test/justfile
@@ -0,0 +1,2 @@
+default:
+ echo "hello"