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/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ body:
id: inputs
attributes:
label: Action inputs
description: Include values such as `path`, `package-name`, `files`, `output-file`, and whether `comment-pr` is enabled.
description: Include values such as `path`, `package-name`, `release-stream`, `files`, `output-file`, and `markdown-output-file`.
render: yaml
validations:
required: false
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/bundle-size.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ jobs:
permissions:
contents: read
checks: write
pull-requests: write

steps:
- name: Checkout repository
Expand Down Expand Up @@ -98,5 +97,3 @@ jobs:
files: |
index.js
output-file: "bundle-size-comparison.json"
comment-pr: true
github-token: ${{ github.token }}
3 changes: 1 addition & 2 deletions COLLABORATOR_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ Give extra scrutiny to changes involving:
- `src/paths.ts` path normalization or containment checks.
- `src/tarball.ts` archive download, decompression, parsing, entry type handling, or file lookup behavior.
- `src/report.ts` report output paths and report contents.
- `src/comment.ts` Markdown rendering and escaping.
- `src/pr-comment.ts` GitHub API calls, token usage, event parsing, or permission handling.
- `src/comment.ts` Markdown rendering and escaping used by documentation examples.
- `action.yml` inputs, outputs, runtime version, or entrypoint.
- Workflow permissions, third-party actions, or dependency updates.

Expand Down
11 changes: 5 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ Keep `src/index.ts` thin. Prefer focused modules for behavior:
- `src/paths.ts`: path normalization and traversal protection.
- `src/tarball.ts`: tarball download and parsing.
- `src/comparison.ts`: gzip size calculation and report construction.
- `src/report.ts`: JSON report writing.
- `src/comment.ts` and `src/pr-comment.ts`: optional pull request comments.
- `src/report.ts`: JSON and Markdown report writing.
- `src/comment.ts`: Markdown rendering kept in sync with the documented pull request comment recipe.

## Making Changes

- Keep changes small and focused.
- Add or update tests for behavior changes.
- Preserve the JSON report as machine-readable output.
- Preserve the JSON report as machine-readable output and the Markdown report as comment-ready output.
- Treat configured paths, npm metadata, tarball contents, and pull request input as untrusted.
- Avoid new runtime dependencies unless there is a clear need.
- Do not make the action build the caller's project; workflows should build artifacts before invoking this action.
Expand Down Expand Up @@ -82,8 +82,7 @@ Security-sensitive changes should include tests for relevant edge cases, especia
- Tarball path normalization and package-root stripping.
- Missing local or baseline files.
- Malformed or truncated tarball entries.
- Markdown escaping in pull request comments.
- Token permission failures when PR comments are enabled.
- Markdown escaping in the generated pull request comment Markdown.

## OpenSpec Changes

Expand All @@ -106,7 +105,7 @@ Important security expectations:
- Keep local reads and report writes inside the configured `path` root.
- Do not extract tarball entries to disk.
- Treat tarball bytes and tar entry metadata as untrusted.
- Keep `github-token` out of logs, reports, outputs, and comments.
- Treat generated reports as untrusted input when external workflows publish comments from public fork runs.
- Prefer immutable `https:` baseline tarball URLs in examples and workflows.
- Keep GitHub workflow permissions minimal.

Expand Down
179 changes: 162 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ There is no normal `lib/` workflow. Vite bundles the action directly from TypeSc
| `release-stream` | No | | Optional leading major version number, such as `0` or `1`, used to select npm release baselines from that release stream. |
| `files` | Yes | | Newline-delimited file paths to compare in the local project and npm release baselines. |
| `output-file` | No | `bundle-size-comparison.json` | Path, relative to `path`, where the JSON comparison report will be written. |
| `comment-pr` | No | `false` | Post or update a bundle size summary comment on pull requests. |
| `github-token` | No | | GitHub token used to post pull request comments when `comment-pr` is enabled. |
| `markdown-output-file` | No | `bundle-size-comparison.md` | Path, relative to `path`, where the Markdown comparison report will be written. |

## Outputs

| Name | Description |
|------|-------------|
| `size` | Total current gzip size in bytes for all compared files. |
| `comparison-file` | Absolute path to the generated JSON comparison file. |
| `markdown-file` | Absolute path to the generated Markdown comparison file. |
| `total-current-gzip-size` | Total current gzip size in bytes for all compared files. |
| `total-baseline-gzip-size` | Total selected primary-release baseline gzip size in bytes for all compared files. |
| `total-delta-gzip-size` | Difference in gzip bytes between current and selected primary-release baseline totals. |
Expand Down Expand Up @@ -97,54 +97,197 @@ steps:
output-file: 'bundle-size-comparison.json'
```

To post the comparison directly on pull requests, enable `comment-pr` and provide a token with comment write permission:
To compare against a maintained major-version stream instead of the npm `latest` stream, set `release-stream` to the leading major version. For example, this compares the local build against the newest stable axios `1.x` release and up to 10 previous stable `1.x` releases, even if npm `latest` points to another major version:

```yaml
permissions:
contents: read
pull-requests: write

steps:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build artifacts
run: pnpm run build

- name: Compare Bundle Size
- name: Compare Bundle Size Against Axios 1.x
uses: axios/bundle-size@main
with:
package-name: 'axios'
release-stream: '1'
files: |
dist/axios.js
dist/axios.min.js
comment-pr: true
github-token: ${{ github.token }}
```

To compare against a maintained major-version stream instead of the npm `latest` stream, set `release-stream` to the leading major version. For example, this compares the local build against the newest stable axios `1.x` release and up to 10 previous stable `1.x` releases, even if npm `latest` points to another major version:
The action does not post pull request comments itself. It writes both a JSON report and a Markdown report. The Markdown file contains the same body the built-in comment path used to post, including the hidden marker used for updates.

For same-repository pull requests, add a follow-up step that reads the Markdown report and posts or updates the marked comment:

```yaml
on:
pull_request:

permissions:
contents: read
pull-requests: write

steps:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build artifacts
run: pnpm run build

- name: Compare Bundle Size Against Axios 1.x
- name: Compare Bundle Size
uses: axios/bundle-size@main
with:
package-name: 'axios'
release-stream: '1'
files: |
dist/axios.js
dist/axios.min.js

- name: Post Bundle Size Comment
uses: actions/github-script@v8
env:
BUNDLE_SIZE_MARKDOWN: bundle-size-comparison.md
with:
script: |
const fs = require('node:fs');

const marker = '<!-- axios-bundle-size-comment -->';
const issue_number = context.payload.pull_request?.number;
if (!issue_number) {
core.info('Skipping bundle size comment because this run is not for a pull request.');
return;
}

const { owner, repo } = context.repo;
const body = fs.readFileSync(process.env.BUNDLE_SIZE_MARKDOWN, 'utf8');
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number,
per_page: 100,
});
const existing = comments.find((comment) => comment.body?.includes(marker));

if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body });
}
```

That same-workflow comment recipe does not bypass GitHub's public fork restrictions. Fork pull requests receive a read-only `GITHUB_TOKEN` in `pull_request` workflows regardless of requested permissions. For public repositories that accept fork PRs, run comparison without write credentials, upload the Markdown report, and publish the comment from a trusted `workflow_run` workflow.

Comparison workflow:

```yaml
name: Bundle Size

on:
pull_request:

permissions:
contents: read

jobs:
bundle-size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build artifacts
run: pnpm run build

- name: Compare Bundle Size
uses: axios/bundle-size@main
with:
package-name: 'axios'
files: |
dist/axios.js
dist/axios.min.js

- name: Upload bundle size report
uses: actions/upload-artifact@v5
with:
name: bundle-size-report
path: bundle-size-comparison.md
```

When `comment-pr` is enabled outside a pull request event, the action writes the JSON report and skips commenting. Fork pull requests receive a read-only `GITHUB_TOKEN` regardless of the workflow's requested permissions; in that case the action logs a warning, skips commenting, and still produces the JSON report and outputs.
Trusted comment workflow:

The comparison file is JSON:
```yaml
name: Bundle Size Comment

on:
workflow_run:
workflows: ['Bundle Size']
types: [completed]

permissions:
actions: read
contents: read
pull-requests: write

jobs:
comment:
if: >-
${{ github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- name: Download bundle size report
uses: actions/download-artifact@v6
with:
name: bundle-size-report
path: bundle-size-report
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}

- name: Post Bundle Size Comment
uses: actions/github-script@v8
env:
BUNDLE_SIZE_MARKDOWN: bundle-size-report/bundle-size-comparison.md
with:
script: |
const fs = require('node:fs');

const marker = '<!-- axios-bundle-size-comment -->';
const pr = context.payload.workflow_run.pull_requests[0];
if (!pr) {
core.info('Skipping bundle size comment because no pull request is associated with this workflow run.');
return;
}

const { owner, repo } = context.repo;
const pull = await github.rest.pulls.get({ owner, repo, pull_number: pr.number });
if (pull.data.head.sha !== context.payload.workflow_run.head_sha) {
core.info('Skipping stale bundle size comment for an outdated workflow run.');
return;
}

const issue_number = pr.number;
const body = fs.readFileSync(process.env.BUNDLE_SIZE_MARKDOWN, 'utf8');
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number,
per_page: 100,
});
const existing = comments.find((comment) => comment.body?.includes(marker));

if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body });
}
```

Do not use `pull_request_target` to checkout, install, build, or otherwise execute pull-request-controlled code with writable credentials. `pull_request_target` can be appropriate for trusted metadata-only automation, but bundle-size comparison requires building untrusted PR code before the report exists.

The JSON comparison file is machine-readable:

```json
{
Expand Down Expand Up @@ -206,7 +349,9 @@ The comparison file is JSON:
}
```

When `release-stream` is omitted, the npm `latest` release is the primary baseline for top-level `baseline`, `files`, `totals`, and action outputs. The `history` array includes the latest release plus up to 10 previous stable releases ordered by npm publish time. When `release-stream` is configured, the newest stable release in that major-version stream is the primary baseline and `history` is limited to that stream; the report includes a top-level `releaseStream` number and pull request comments describe the configured stream. Previous releases that do not contain every configured file are marked as incomplete instead of failing the action.
The Markdown comparison file is comment-ready and uses the same bundle-size report structure shown in the pull request examples above.

When `release-stream` is omitted, the npm `latest` release is the primary baseline for top-level `baseline`, `files`, `totals`, and action outputs. The `history` array includes the latest release plus up to 10 previous stable releases ordered by npm publish time. When `release-stream` is configured, the newest stable release in that major-version stream is the primary baseline and `history` is limited to that stream; the JSON report includes a top-level `releaseStream` number and the Markdown report describes the configured stream. Previous releases that do not contain every configured file are marked as incomplete instead of failing the action.

Because this repository *is* the action, a workflow inside the same repo can reference it with `./` after preparing local files to compare:

Expand Down Expand Up @@ -295,7 +440,7 @@ env \

## Current Behavior

The action fetches npm registry metadata for `package-name`, resolves either the `latest` dist-tag plus up to 10 previous stable releases or the configured `release-stream` plus up to 10 previous stable releases in that major version, downloads each selected release tarball, reads regular file entries, strips a single shared top-level directory such as `package/`, and compares the configured paths against local files under `path`. It measures gzip-compressed bytes for each file and writes a JSON report. It does not enforce budgets or target sizes.
The action fetches npm registry metadata for `package-name`, resolves either the `latest` dist-tag plus up to 10 previous stable releases or the configured `release-stream` plus up to 10 previous stable releases in that major version, downloads each selected release tarball, reads regular file entries, strips a single shared top-level directory such as `package/`, and compares the configured paths against local files under `path`. It measures gzip-compressed bytes for each file and writes JSON and Markdown reports. It does not enforce budgets or target sizes.

Suggested next steps:

Expand Down
6 changes: 3 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ In scope examples:

- Path traversal that reads or writes outside the configured `path` root.
- Tarball parsing flaws that allow workspace overwrite, unexpected file selection, or denial of service.
- Leaks of `github-token`, `GITHUB_TOKEN`, or other secrets through logs, reports, outputs, or comments.
- Leaks of `GITHUB_TOKEN` or other secrets through logs, reports, outputs, or comments.
- Pull request comment injection that can mislead reviewers or expose sensitive data.
- Runtime bundle divergence that causes reviewed source to differ from executed action behavior.
- Workflow permission weaknesses in this repository's own CI configuration.
Expand All @@ -59,8 +59,8 @@ Out of scope examples:

- Configure the intended npm `package-name` and review reports for the resolved latest release version.
- Keep `files` and `output-file` relative to the configured `path` root.
- Use minimal workflow permissions. JSON-only comparisons should not need write permissions.
- Enable `comment-pr` only when PR comments are needed, and pass the default `${{ github.token }}` unless a stronger token is strictly required.
- Use minimal workflow permissions. Generating reports should not need write permissions.
- Publish PR comments from external workflow steps that read the Markdown report. For public fork PRs, use a trusted `workflow_run` workflow rather than running PR-controlled code with writable credentials.
- Do not include generated files that may contain secrets in the configured `files` list.
- Pin third-party actions in workflows and avoid persisting checkout credentials unless needed.

Expand Down
Loading