Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

steps:
- name: Harden Runner
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:

steps:
- name: Harden Runner
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

- name: 'Checkout Repository'
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: 'Dependency Review'
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

steps:
- name: Harden Runner
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

Expand All @@ -38,7 +38,7 @@ jobs:
bun-version: latest

- name: "Setup: Cosign"
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2

- name: "Setup: Install Dependencies"
run: bun install --frozen-lockfile
Expand Down Expand Up @@ -89,7 +89,7 @@ jobs:
"$ARTIFACT"

- name: "Attest: Build Provenance"
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: ${{ env.ARTIFACT }}

Expand All @@ -100,7 +100,7 @@ jobs:
sbom-path: artifacts/sbom.spdx.json

- name: "Release: Create"
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
generate_release_notes: true
make_latest: true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scorecards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:

steps:
- name: Harden Runner
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

Expand Down
53 changes: 48 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,29 @@ This checks all `.java` and `.kt` files in the repository for correct formatting
fail-on-error: false
```

**List affected files after a failed check**
```yaml
- uses: elide-dev/format
with:
output-mode: file
```

**Print diffs for unformatted files (check mode only)**
```yaml
- uses: elide-dev/format
with:
mode: check
output-mode: diff
```

**Print a fix command instead of file names**
```yaml
- uses: elide-dev/format
with:
output-mode: command
output-mode-command: make format # omit to generate an elide command automatically
```

**Pass extra arguments to the formatters**
```yaml
- uses: elide-dev/format
Expand All @@ -87,6 +110,22 @@ This checks all `.java` and `.kt` files in the repository for correct formatting
echo "Files checked: ${{ steps.format.outputs.files-checked }}"
```

**Pass failing files to a subsequent step**
```yaml
- name: "Format: Check"
id: format
uses: elide-dev/format
with:
output-mode: file
fail-on-error: false

- name: "Annotate"
run: |
echo "${{ steps.format.outputs.files-failed }}" | while read f; do
echo "::warning file=$f::File needs formatting"
done
```

## Inputs

| Input | Type | Default | Description |
Expand All @@ -101,17 +140,21 @@ This checks all `.java` and `.kt` files in the repository for correct formatting
| `include-kts` | `boolean` | `false` | Include `.kts` (Kotlin script) files when running ktfmt |
| `fail-on-error` | `boolean` | `true` | Fail the workflow when formatting check fails |
| `telemetry` | `boolean` | `true` | Enable anonymous error telemetry ([details](#telemetry)) |
| `output-mode` | `string` | `none` | Output mode after the formatter runs: `none`, `file` (list affected files), `diff` (print diffs, check mode only), or `command` (print a fix command) |
| `output-mode-diffs` | `integer` | | When `output-mode` is `diff`, limit the number of diffs shown. When exceeded, falls back to listing file paths. Must be a positive integer. |
| `output-mode-command` | `string` | | When `output-mode` is `command`, print this string instead of the generated elide command (e.g. `make format`) |

## Outputs

| Output | Description |
|-----------------|--------------------------------------|
| `result` | Check result: `success` or `failure` |
| `files-checked` | Number of files checked |
| Output | Description |
|-----------------|----------------------------------------------------------------------------------------------------|
| `result` | Check result: `success` or `failure` |
| `files-checked` | Number of files checked |
| `files-failed` | Newline-separated list of files affected by the formatter: files that failed the format check (check mode) or were reformatted (write mode). Only set when `output-mode` is `file`. |

## File Discovery

When `files` is not set, the action scans `working-directory` recursively for source files by extension (`.java` for `javaformat`, `.kt` and optionally `.kts` for `ktfmt`). The following directories are always skipped during scanning: `node_modules`, `.git`, `build`, `dist`, `target`, `.gradle`, `.idea`, `out`.
When `files` is not set, the action scans `working-directory` recursively for source files by extension (`.java` for `javaformat`, `.kt` and optionally `.kts` for `ktfmt`).

When `files` is provided, each entry can be an individual file or a directory. Directories are expanded recursively using the same extension filter. The `exclude` patterns are applied after all file resolution.

Expand Down
70 changes: 58 additions & 12 deletions __tests__/command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('buildFormatterArgs', () => {

it('should build javaformat write args', () => {
expect(buildFormatterArgs('javaformat', 'write', ['Foo.java'], [])).toEqual(
['javaformat', '--', 'Foo.java']
['javaformat', '--', '-r', 'Foo.java']
)
})

Expand Down Expand Up @@ -83,13 +83,45 @@ describe('buildFormatterArgs', () => {
it('should place extra args before files in write mode', () => {
expect(
buildFormatterArgs('javaformat', 'write', ['Foo.java'], ['--aosp'])
).toEqual(['javaformat', '--', '--aosp', 'Foo.java'])
).toEqual(['javaformat', '--', '-r', '--aosp', 'Foo.java'])
})

it('should always include the -- separator', () => {
const args = buildFormatterArgs('ktfmt', 'write', ['Main.kt'], [])
expect(args[1]).toBe('--')
})

it('should insert elide flags between formatter name and --', () => {
expect(
buildFormatterArgs('ktfmt', 'check', ['Main.kt'], [], ['--list-files'])
).toEqual([
'ktfmt',
'--list-files',
'--',
'--dry-run',
'--set-exit-if-changed',
'Main.kt'
])
})

it('should support multiple elide flags', () => {
expect(
buildFormatterArgs(
'javaformat',
'write',
['Foo.java'],
[],
['--list-files', '--verbose']
)
).toEqual([
'javaformat',
'--list-files',
'--verbose',
'--',
'-r',
'Foo.java'
])
})
})

describe('runFormatter', () => {
Expand All @@ -114,23 +146,22 @@ describe('runFormatter', () => {
expect(argfileArg).toBeDefined()
expect(passedArgs).not.toContain('Main.kt')
expect(passedArgs).not.toContain('Foo.kt')
// ktfmt: flags and files all go in the argfile
// ktfmt: only files go in the argfile (flags go in exec args)
expect(writeFileSyncMock).toHaveBeenCalledWith(
expect.stringContaining('format-ktfmt.txt'),
'--dry-run\n--set-exit-if-changed\nMain.kt\nFoo.kt',
'Main.kt\nFoo.kt',
'utf-8'
)
})

it('should call exec with elide and correct mode args', async () => {
await runFormatter('ktfmt', 'check', ['Main.kt'], [], '/workspace')
// ktfmt: only @argfile follows --, flags are written into the file
// ktfmt: check flags are in exec args, not argfile
const passedArgs: string[] = execMock.mock.calls[0][1]
expect(passedArgs[0]).toBe('ktfmt')
expect(passedArgs[1]).toBe('--')
expect(passedArgs[2]).toMatch(/^@.*format-ktfmt\.txt$/)
expect(passedArgs).not.toContain('--dry-run')
expect(passedArgs).not.toContain('--set-exit-if-changed')
expect(passedArgs).toContain('--dry-run')
expect(passedArgs).toContain('--set-exit-if-changed')
expect(execMock).toHaveBeenCalledWith(
'elide',
expect.anything(),
Expand All @@ -153,31 +184,46 @@ describe('runFormatter', () => {
)
})

it('should write extraArgs into the ktfmt argfile', async () => {
it('should pass extraArgs in exec args for ktfmt, not in argfile', async () => {
await runFormatter(
'ktfmt',
'check',
['Main.kt'],
['--google-style'],
'/workspace'
)
// Only files in argfile
expect(writeFileSyncMock).toHaveBeenCalledWith(
expect.stringContaining('format-ktfmt.txt'),
'--dry-run\n--set-exit-if-changed\n--google-style\nMain.kt',
'Main.kt',
'utf-8'
)
// Extra args in exec args
const passedArgs: string[] = execMock.mock.calls[0][1]
expect(passedArgs).toContain('--google-style')
})

it('should return the exit code', async () => {
execMock.mockResolvedValue(1)
const code = await runFormatter(
const result = await runFormatter(
'ktfmt',
'check',
['Main.kt'],
[],
'/workspace'
)
expect(code).toBe(1)
expect(result.exitCode).toBe(1)
})

it('should pass elide flags before -- in exec args', async () => {
await runFormatter('ktfmt', 'check', ['Main.kt'], [], '/workspace', [
'--list-files'
])
const passedArgs: string[] = execMock.mock.calls[0][1]
expect(passedArgs[0]).toBe('ktfmt')
expect(passedArgs[1]).toBe('--list-files')
const dashDashIdx = passedArgs.indexOf('--')
expect(dashDashIdx).toBe(2)
})

it('should log a debug message with file count', async () => {
Expand Down
Loading
Loading