Compare commits

..

1 commit

Author SHA1 Message Date
8925f3da6d
WIP: refactor: port to JS
Some checks failed
/ check (treefmt) (push) Successful in 4s
/ report-size (push) Failing after 4s
/ report-download-check (push) Has been skipped
This should hopefully reduce the complexity of the action
2025-07-11 17:54:10 +02:00
25 changed files with 215080 additions and 195 deletions

View file

@ -2,7 +2,7 @@
end_of_line = lf end_of_line = lf
charset = utf-8 charset = utf-8
[*.{nix,json,sh}] [*.{nix,json,sh,js}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
trim_trailing_whitespace = true trim_trailing_whitespace = true

View file

@ -8,7 +8,7 @@ jobs:
check: check:
- treefmt - treefmt
steps: steps:
- uses: "https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" # v4 - uses: 'https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # v4
- name: Run checks - name: Run checks
run: | run: |
nix --version nix --version
@ -18,23 +18,25 @@ jobs:
runs-on: nixos runs-on: nixos
needs: check needs: check
steps: steps:
- uses: "https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" # v4 - uses: 'https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # v4
- run: nix --version - run: nix --version
- name: Create Size Report - name: Create Size Report
uses: ./ uses: ./
with: with:
system: x86_64-linux
# Create a comment on the associated PR # Create a comment on the associated PR
comment-on-pr: 'false' comment-on-pr: ${{ github.ref_name != 'main' }}
# Generate artifacts on main (to speed up comparisons) # Generate artifacts on main (to speed up comparisons)
# generate-artifact: ${{ github.ref_name == 'main' }} # generate-artifact: ${{ github.ref_name == 'main' }}
# Always generate artifacts for testing purposes # Always generate artifacts for testing purposes
generate-artifact: 'true' generate-artifact: 'true'
# This job's name (so we can find the previous artifacts) # Generate comparisons to main
do-comparison: 'true'
# This job's name (so we can find the artifacts)
job-name: report-size job-name: report-size
report-download-check: report-download-check:
runs-on: nixos runs-on: nixos
needs: report-size-push needs: report-size
if: github.event_name == 'push'
steps: steps:
- name: Download previous report - name: Download previous report
uses: https://git.salame.cl/actions/download-artifact@d8d0a99033603453ad2255e58720b460a0555e1e # v4 uses: https://git.salame.cl/actions/download-artifact@d8d0a99033603453ad2255e58720b460a0555e1e # v4

View file

@ -7,7 +7,7 @@ jobs:
check-renovaterc: check-renovaterc:
runs-on: nixos runs-on: nixos
steps: steps:
- uses: "https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" # v4 - uses: 'https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # v4
- name: Validate renovaterc.json - name: Validate renovaterc.json
run: | run: |
nix --version nix --version

View file

@ -1,33 +0,0 @@
on:
pull_request:
jobs:
report-size-pr:
runs-on: nixos
steps:
- uses: "https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" # v4
- run: nix --version
- name: Create Size Report
uses: ./
with:
# Create a comment on the associated PR
comment-on-pr: 'true'
# Generate artifacts on main (to speed up comparisons)
# generate-artifact: ${{ github.ref_name == 'main' }}
# Always generate artifacts for testing purposes
generate-artifact: 'true'
# Generate comparisons to main
do-comparison: 'true'
# Get the previous artifacts from report-size-push (since those run on main)
job-name: report-size
artifact-name: report.json
report-download-check-pr:
runs-on: nixos
needs: report-size-pr
steps:
- name: Download previous report
uses: https://git.salame.cl/actions/download-artifact@d8d0a99033603453ad2255e58720b460a0555e1e # v4
with:
name: report.json
- name: Verify report exists
run: |
cat report.json

3
.gitattributes vendored Normal file
View file

@ -0,0 +1,3 @@
* text=auto eol=lf
dist/** -diff linguist-generated=true

103
.gitignore vendored
View file

@ -1 +1,104 @@
result* result*
# Dependency directory
node_modules
# Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# OS metadata
.DS_Store
Thumbs.db
# Ignore built ts files
__tests__/runner/*
# IDE files
.idea
*.code-workspace

5
.prettierignore Normal file
View file

@ -0,0 +1,5 @@
.DS_Store
.licenses/
dist/
node_modules/
coverage/

15
.prettierrc.yml Normal file
View file

@ -0,0 +1,15 @@
# See: https://prettier.io/docs/en/configuration
printWidth: 80
tabWidth: 2
useTabs: false
semi: false
singleQuote: true
quoteProps: as-needed
jsxSingleQuote: false
trailingComma: none
bracketSpacing: true
bracketSameLine: true
arrowParens: always
proseWrap: always
htmlWhitespaceSensitivity: css
endOfLine: lf

View file

@ -2,9 +2,11 @@
Use `nix path-info` to query the size of flake outputs and produce a report. Use `nix path-info` to query the size of flake outputs and produce a report.
This repost can be posted to a PR (as formatted markdown) and/or uploaded as a workflow artifact. This repost can be posted to a PR (as formatted markdown) and/or uploaded as a
workflow artifact.
Requires `nix`, `jq`, `curl`, `sed`, `gunzip`, `tar` and `coreutils` to be in the runner's path. Requires `nix`, `jq`, `curl`, `sed`, `gunzip`, `tar` and `coreutils` to be in
the runner's path.
## Example ## Example
@ -39,26 +41,32 @@ For more details see the [action.yaml](./action.yml) file.
**Definitions:** **Definitions:**
- `Name`: the name of the package/configuration. - `Name`: the name of the package/configuration.
- `Size`: the closure size (size on disk/NAR size + all transitive dependencies). - `Size`: the closure size (size on disk/NAR size + all transitive
dependencies).
- `NAR Size`: the size of the build output (package without the dependencies). - `NAR Size`: the size of the build output (package without the dependencies).
- `[NAR] Size Change`: the amount changed compared to the main branch. - `[NAR] Size Change`: the amount changed compared to the main branch.
**Tips on reading this data:** **Tips on reading this data:**
- For NixOS configurations you generally care only about the `Size` (closure size/size on disk). - For NixOS configurations you generally care only about the `Size` (closure
size/size on disk).
- Reduce the `Size` by disabling unneeded services/default packages. - Reduce the `Size` by disabling unneeded services/default packages.
- For Packages you care about both the `Size` and the `NAR Size`. - For Packages you care about both the `Size` and the `NAR Size`.
- Reduce the `NAR Size` by reducing the size of the build outputs, e.g. don't copy unnecessary data to the $out dir, optimize binaries for size, etc. - Reduce the `NAR Size` by reducing the size of the build outputs, e.g. don't
copy unnecessary data to the $out dir, optimize binaries for size, etc.
- Reduce the `Size` by reducing the dependencies (e.g. `buildInputs`). - Reduce the `Size` by reducing the dependencies (e.g. `buildInputs`).
- Don't worry too much about size, some dependencies are deduplicated, e.g. `glibc` adds ~40MiB to the `Size`, but is generally shared by ~every binary on the system, so, chances are, you are already including it from somewhere else and statically linking with e.g. `musl` is not gonna improve things. - Don't worry too much about size, some dependencies are deduplicated, e.g.
`glibc` adds ~40MiB to the `Size`, but is generally shared by ~every binary
on the system, so, chances are, you are already including it from somewhere
else and statically linking with e.g. `musl` is not gonna improve things.
# NixOS Configurations # NixOS Configurations
| Name | Size | Size Change | NAR Size | NAR Size Change | | Name | Size | Size Change | NAR Size | NAR Size Change |
|------|-----:|------------:|---------:|----------------:| | -------- | ----: | ----------: | -------: | --------------: |
| `gemini` | 11Gi | -2.4Mi | 28Ki | 0 | | `gemini` | 11Gi | -2.4Mi | 28Ki | 0 |
| `leo` | 1.6Gi | 0 | 25Ki | 0 | | `leo` | 1.6Gi | 0 | 25Ki | 0 |
| `libra` | 9.4Gi | -2.4Mi | 28Ki | 0 | | `libra` | 9.4Gi | -2.4Mi | 28Ki | 0 |
| `taurus` | 7.6Gi | 0 | 34Ki | 0 | | `taurus` | 7.6Gi | 0 | 34Ki | 0 |
</details> </details>

View file

@ -32,6 +32,9 @@ inputs:
This is a no-op in case no PR is associated with the current branch. This is a no-op in case no PR is associated with the current branch.
default: 'true' default: 'true'
system:
description: |
The nix system name to query the packages of (e.g. x86_64-linux)
# Generate workflow artifact # Generate workflow artifact
generate-artifact: generate-artifact:
description: Export the generated markdown document as a workflow artifact. description: Export the generated markdown document as a workflow artifact.
@ -65,58 +68,5 @@ inputs:
default: ${{ github.base_ref }} default: ${{ github.base_ref }}
outputs: outputs:
runs: runs:
using: 'composite' using: node20
steps: main: dist/index.js
- name: Run
id: pr-number
env:
BASE_BRANCH: ${{ inputs.base-branch }}
COMMENT: ${{ inputs.comment-on-pr }}
DO_COMPARISON: ${{ inputs.do-comparison }}
GENERATE_ARTIFACT: ${{ inputs.generate-artifact }}
JOB_NAME: ${{ inputs.job-name }}
run: |
"$GITHUB_ACTION_PATH/scripts/run.sh"
- name: Find previous comment (if present)
# We want to generate a comment and we we able to find the PR number
if: inputs.comment-on-pr == 'true' && steps.pr-number.outputs.pr-number != ''
id: find-comment
uses: https://github.com/peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
direction: first
body-includes: "<!-- AUTOGENERATED by nix-flake-outputs-size action -->"
- name: Create report and comment on PR
# We want to generate a comment and we we able to find the PR number
if: inputs.comment-on-pr == 'true' && steps.pr-number.outputs.pr-number != ''
env:
ARTIFACT_NAME: ${{ inputs.artifact-name }}
BASE_BRANCH: ${{ inputs.base-branch }}
COMMENT_ID: ${{ steps.find-comment.outputs.comment-id }}
DO_COMPARISON: ${{ inputs.do-comparison }}
JOB_NAME: ${{ inputs.job-name }}
PR_ID: ${{ steps.pr-number.outputs.pr-number }}
run: |
. "$GITHUB_ACTION_PATH/scripts/utils.sh"
# Try to do a comparison report
if [ "$DO_COMPARISON" = 'true' ]; then
if "$GITHUB_ACTION_PATH/scripts/retrieve-old-report.sh" && [ -f old-report.json ]; then
log "Reporting on sizes and comparing to sizes in $BASE_BRANCH"
"$GITHUB_ACTION_PATH/scripts/comment_on_pr.sh" report.json old-report.json
exit 0
else
error 'Failed to do comparison, fallback to posting the report without them'
fi
fi
# Just report values
log 'Reporting on sizes'
"$GITHUB_ACTION_PATH/scripts/comment_on_pr.sh" report.json
- name: Upload Artifact
uses: https://git.salame.cl/actions/upload-artifact@v4
if: inputs.generate-artifact == 'true'
with:
path: report.json
name: ${{ inputs.artifact-name }}

202419
dist/index.js generated vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/index.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

61
eslint.config.mjs Normal file
View file

@ -0,0 +1,61 @@
// See: https://eslint.org/docs/latest/use/configure/configuration-files
import { fixupPluginRules } from '@eslint/compat'
import { FlatCompat } from '@eslint/eslintrc'
import js from '@eslint/js'
import _import from 'eslint-plugin-import'
import jest from 'eslint-plugin-jest'
import prettier from 'eslint-plugin-prettier'
import globals from 'globals'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
})
export default [
{
ignores: ['**/coverage', '**/dist', '**/linter', '**/node_modules']
},
...compat.extends(
'eslint:recommended',
'plugin:jest/recommended',
'plugin:prettier/recommended'
),
{
plugins: {
import: fixupPluginRules(_import),
jest,
prettier
},
languageOptions: {
globals: {
...globals.node,
...globals.jest,
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
},
ecmaVersion: 2023,
sourceType: 'module'
},
rules: {
camelcase: 'off',
'eslint-comments/no-use': 'off',
'eslint-comments/no-unused-disable': 'off',
'i18n-text/no-en': 'off',
'import/no-namespace': 'off',
'no-console': 'off',
'no-shadow': 'off',
'no-unused-vars': 'off',
'prettier/prettier': 'error'
}
}
]

View file

@ -26,10 +26,12 @@
# Setup formatters # Setup formatters
treefmt = { treefmt = {
# Ignore images # Ignore images
settings.global.excludes = [ "*.png" ]; settings.global.excludes = [
"*.png"
"dist/*"
];
projectRootFile = "flake.nix"; projectRootFile = "flake.nix";
programs = { programs = {
mdformat.enable = true;
nixfmt.enable = true; nixfmt.enable = true;
shfmt.enable = true; shfmt.enable = true;
shellcheck.enable = true; shellcheck.enable = true;

30
jest.config.js Normal file
View file

@ -0,0 +1,30 @@
// See: https://jestjs.io/docs/configuration
/** @type {import('jest').Config} */
const jestConfig = {
clearMocks: true,
collectCoverage: true,
collectCoverageFrom: ['./src/**'],
coverageDirectory: './coverage',
coveragePathIgnorePatterns: ['/node_modules/', '/dist/'],
coverageReporters: ['json-summary', 'text', 'lcov'],
// Uncomment the below lines if you would like to enforce a coverage threshold
// for your action. This will fail the build if the coverage is below the
// specified thresholds.
// coverageThreshold: {
// global: {
// branches: 100,
// functions: 100,
// lines: 100,
// statements: 100
// }
// },
moduleFileExtensions: ['js'],
reporters: ['default'],
testEnvironment: 'node',
testMatch: ['**/*.test.js'],
testPathIgnorePatterns: ['/dist/', '/node_modules/'],
verbose: true
}
export default jestConfig

12141
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

66
package.json Normal file
View file

@ -0,0 +1,66 @@
{
"name": "nix-flake-outputs-size-report",
"description": "Use 'nix-path-info' to query the size of outputs and produce a markdown report",
"version": "0.1.0",
"author": "Jalil David Salamé Messina",
"type": "module",
"private": true,
"homepage": "https://git.salame.cl/jalil/nix-flake-outputs-size#readme",
"repository": {
"type": "git",
"url": "git+https://git.salame.cl/jalil/nix-flake-outputs-size.git"
},
"bugs": {
"url": "https://git.salame.cl/jalil/nix-flake-outputs-size/issues"
},
"keywords": [
"actions"
],
"exports": {
".": "./dist/index.js"
},
"engines": {
"node": ">=20"
},
"scripts": {
"bundle": "npm run format:write && npm run package",
"ci-test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest",
"coverage": "npx make-coverage-badge --output-path ./badges/coverage.svg",
"format:write": "npx prettier --write .",
"format:check": "npx prettier --check .",
"lint": "npx eslint .",
"local-action": "npx @github/local-action . src/main.js .env",
"package": "npx rollup --config rollup.config.js",
"package:watch": "npm run package -- --watch",
"test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest",
"all": "npm run format:write && npm run lint && npm run test && npm run coverage && npm run package"
},
"license": "MIT",
"dependencies": {
"@actions/artifact": "^2.3.2",
"@actions/core": "^1.11.1",
"@actions/exec": "^1.1.1",
"@actions/github": "^6.0.1"
},
"devDependencies": {
"@eslint/compat": "^1.3.1",
"@github/local-action": "^3.2.1",
"@jest/globals": "^30.0.4",
"@rollup/plugin-commonjs": "^28.0.6",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"eslint": "^9.30.1",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-prettier": "^5.5.1",
"jest": "^30.0.4",
"make-coverage-badge": "^1.2.0",
"prettier": "^3.6.2",
"prettier-eslint": "^16.4.2",
"rollup": "^4.44.2"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "*"
}
}

11
report.json Normal file
View file

@ -0,0 +1,11 @@
{
"packages": [
{
"name": "hello",
"size": 33159640,
"narSize": 234680
}
],
"nixosConfigurations": [],
"homeConfigurations": []
}

18
rollup.config.js Normal file
View file

@ -0,0 +1,18 @@
// See: https://rollupjs.org/introduction/
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import { nodeResolve } from '@rollup/plugin-node-resolve'
const config = {
input: 'src/index.js',
output: {
esModule: true,
file: 'dist/index.js',
format: 'es',
sourcemap: true
},
plugins: [commonjs(), json(), nodeResolve({ preferBuiltins: true })]
}
export default config

View file

@ -7,25 +7,24 @@ util_path="${GITHUB_ACTION_PATH:-.}/scripts/utils.sh"
# shellcheck source=scripts/utils.sh # shellcheck source=scripts/utils.sh
. "${util_path}" . "${util_path}"
group 'Retrieving Flake information'
flake_info=$(nix flake show --json --quiet --quiet)
endgroup
system=$(nix eval --impure --json --expr 'builtins.currentSystem') system=$(nix eval --impure --json --expr 'builtins.currentSystem')
# Extract the names of a flake attrset group 'Show Packages'
get_names() { packages=$(echo "$flake_info" | jq --raw-output --argjson system "$system" 'getpath(["packages", $system]) | select(. != null) | keys[]')
nix eval --json --apply builtins.attrNames "$1" 2>/dev/null | jq --raw-output '.[]'
}
group "Show Packages for $system"
packages=$(get_names .#packages."$system")
[ -z "$packages" ] || log "$packages" [ -z "$packages" ] || log "$packages"
endgroup endgroup
group 'Show Home Manager Configurations' group 'Show Home Manager Configurations'
hmConfigs=$(get_names .#homeConfigurations) hmConfigs=$(echo "$flake_info" | jq --raw-output '.homeConfigurations | select(. != null) | keys[]')
[ -z "$hmConfigs" ] || log "$hmConfigs" [ -z "$hmConfigs" ] || log "$hmConfigs"
endgroup endgroup
group 'Show NixOS Configurations' group 'Show NixOS Configurations'
nixosConfigs=$(get_names .#nixosConfigurations) nixosConfigs=$(echo "$flake_info" | jq --raw-output '.nixosConfigurations | select(. != null) | keys[]')
[ -z "$nixosConfigs" ] || log "$nixosConfigs" [ -z "$nixosConfigs" ] || log "$nixosConfigs"
endgroup endgroup
@ -52,7 +51,7 @@ pkgs_json() {
hm_configs_json() { hm_configs_json() {
for config in $hmConfigs; do for config in $hmConfigs; do
log "Building $config" log "Building $config"
path=$(nix build --print-out-paths ".#homeConfigurations.$config.config.home.activationPackage") path=$(nix build --print-out-paths ".#homeConfigurations.$config.activationPackages")
closure_size "$config" "$path" closure_size "$config" "$path"
done done
} }

View file

@ -40,9 +40,7 @@ has_report() {
# If a base branch is not provided, use the default branch # If a base branch is not provided, use the default branch
base_branch=${BASE_BRANCH:-$(default_branch)} base_branch=${BASE_BRANCH:-$(default_branch)}
if in_private_repo; then if [ "$(in_private_repo)" != 'true' ] && [ "$JOB_NAME" ]; then
warn 'Detected that this is a private repo cannot retrieve old report'
elif [ "$JOB_NAME" ]; then
url=$(base_report_url "$base_branch") url=$(base_report_url "$base_branch")
log "Found previous run at: $url" log "Found previous run at: $url"
@ -58,14 +56,13 @@ elif [ "$JOB_NAME" ]; then
exit 0 exit 0
fi fi
error "Failed to find previous report, expected at: $report_url" error "Failed to find previous report, expected at: $report_url"
else
panic 'job-name is missing, therefore we cannot find the previous report'
fi fi
warn "Couldn't retrieve old report: warn "Couldn't retrieve old report:"
note: This usually happens when running on private repos or when job-name is not set. warn ' This usuially happens when running on private repos'
warn ' or when job-name is not set.'
See the README for more details" warn
warn ' See the README for more details'
error "Falling back to slow method (checkout $base_branch and generate the report)" error "Falling back to slow method (checkout $base_branch and generate the report)"

View file

@ -1,61 +0,0 @@
#!/bin/sh
. "$GITHUB_ACTION_PATH/scripts/utils.sh"
# Input validation
if [ "$COMMENT" != "true" ] && [ "$GENERATE_ARTIFACT" != "true" ]; then
panic 'Neither comment-on-pr nor generate-artifact is set
note: this looks like an error; if it isn'"'"'t disable this action with "step.if"'
fi
if [ "$DO_COMPARISON" = 'true' ] && [ -z "$JOB_NAME" ]; then
panic 'Requested a comparison report but job-name wasn'"'"'t set'
fi
# Create Size Report (will be uploaded by the upload-artifact action)
"$GITHUB_ACTION_PATH/scripts/create-report.sh" report.json
# Nothing else to do
if [ "$COMMENT" != "true" ]; then exit 0; fi
# Find the PR for this commit so we can post a comment on it
pr_number=
case "$GITHUB_EVENT_NAME" in
"pull_request")
pr_number=$(jq .number "$GITHUB_EVENT_PATH")
log "Triggered by a pull request with index: $pr_number"
;;
"push")
log "Triggered by a push to $GITHUB_REF_NAME autodetecting PR number"
log "Get PR number for $GITHUB_REF_NAME"
prs=$(curl -X 'GET' \
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/pulls?state=open&sort=recentupdate" \
-H "Authorization: token $GITHUB_TOKEN" \
-H 'Accept: application/json')
log "Found these open PRs: $(echo "$prs" | jq '[.[] | .number]')"
pr_number=$(echo "$prs" |
jq --arg ref "$GITHUB_REF_NAME" '.[] | select(.head.ref == $ref) | .number')
# Protect against running before a PR is made or if it is triggered on the main branch
if [ -z "$pr_number" ]; then
warn "No PR created for this commit"
exit 0
fi
log "The PR we found for $GITHUB_REF_NAME is $pr_number"
;;
*)
panic "Unexpected event $GITHUB_EVENT_NAME for commenting on a PR, expected push or pull_request"
;;
esac
log "Expected PR URL: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pulls/$pr_number"
# This seems to create the file???
log "GITHUB_OUTPUT=$GITHUB_OUTPUT"
log "$(ls -l "$GITHUB_OUTPUT")"
echo "pr-number=$pr_number" >>"$GITHUB_OUTPUT"

View file

@ -12,11 +12,6 @@ error() {
log "\e[0;31m[WARN]:" "$@" "\e[0m" log "\e[0;31m[WARN]:" "$@" "\e[0m"
} }
panic() {
error "$@"
exit 1
}
group() { group() {
echo "::group::$1" echo "::group::$1"
} }

8
src/index.js Normal file
View file

@ -0,0 +1,8 @@
/**
* The entrypoint for the action. This file simply imports and runs the action's
* main logic.
*/
import { run } from './main.js'
/* istanbul ignore next */
run()

145
src/main.js Normal file
View file

@ -0,0 +1,145 @@
import * as core from '@actions/core'
import * as exec from '@actions/exec'
import * as github from '@actions/github'
import { DefaultArtifactClient } from '@actions/artifact'
import * as fs from 'node:fs/promises'
/**
* The main function for the action.
*
* @returns {Promise<void>} Resolves when the action is complete.
*/
export async function run() {
try {
const comment = core.getBooleanInput('comment-on-pr', { required: false })
const upload = core.getBooleanInput('generate-artifact', {
required: false
})
const artifactName = core.getInput('artifact-name', { required: false })
const compare = core.getBooleanInput('do-comparison', { required: false })
const jobName = core.getInput('job-name', { required: false })
const baseBranch = core.getInput('base-branch', { required: false })
const system = core.getInput('system', { required: true })
if (!comment && !upload) {
core.error(
'Both comment-on-pr and generate-artifact were set to false, nothing to do, consider disabling the action instead'
)
core.setFailed('Neither commenting nor uploading a report ... why?')
return
}
const flakeInfo = JSON.parse(
await collectOutput('nix', ['flake', 'show', '--json'])
)
core.debug(`nix flake show --json: ${flakeInfo}`)
const report = await core.group('Generating size report', () =>
generateReport(flakeInfo, system)
)
if (upload) {
const artifact = new DefaultArtifactClient()
await fs.writeFile(artifactName, JSON.stringify(report))
const { id, size } = artifact.uploadArtifact(artifactName, [artifactName])
core.info(`Uploaded report ${artifactName} (${size}B) with id ${id}`)
}
// Done
if (!comment) {
return
}
// TODO: compare reports and create comment
} catch (error) {
// Fail the workflow run if an error occurs
if (error instanceof Error) core.setFailed(error.message)
}
}
async function generateReport(flakeInfo, system) {
const packages = getPackages(flakeInfo, system)
const hmConfigs = getKeys(flakeInfo, 'homeConfigurations')
const nixosConfigs = getKeys(flakeInfo, 'nixosConfigurations')
core.info(`packages: ${packages}`)
core.info(`homeConfigurations: ${hmConfigs}`)
core.info(`nixosConfigurations: ${nixosConfigs}`)
const pkgSizes = await core.group('Calculating size of packages', () =>
calculateSizeOf(packages, (pkg) => `.#${pkg}`)
)
const hmConfigsSizes = await core.group(
'Calculating size of Home-Manager Configurations',
() =>
calculateSizeOf(
hmConfigs,
(config) => `.#homeConfigurations.${config}.activationPackages`
)
)
const nixosConfigsSizes = await core.group(
'Calculating size of NixOS Configurations',
() =>
calculateSizeOf(
nixosConfigs,
(config) =>
`.#nixosConfigurations.${config}.config.system.build.toplevel`
)
)
return {
packages: pkgSizes,
nixosConfigurations: nixosConfigsSizes,
homeConfigurations: hmConfigsSizes
}
}
async function calculateSizeOf(names, nameToInstallable) {
let sizes = []
for (const name of names) {
sizes.push(await calculateSize(nameToInstallable(name)))
}
return sizes
}
/**
* Get the packages from a `nix flake show --json` blob
*
* @returns {Array<String>} the packages in the current flake for the specific system
*/
function getPackages(flakeInfo, system) {
return 'packages' in flakeInfo ? getKeys(flakeInfo.packages, system) : []
}
function getKeys(flakeInfo, key) {
return key in flakeInfo ? Object.keys(flakeInfo[key]) : []
}
async function calculateSize(installable) {
const path = await collectOutput('nix', [
'build',
'--print-out-paths',
installable
])
const data = JSON.parse(
await collectOutput('nix', ['path-info', '--closure-size', '--json', path])
)
data.path = path
return data
}
async function collectOutput(cmd, args) {
let output = ''
await exec.exec(cmd, args, {
listeners: {
stdout: (data) => {
output += data.toString()
}
}
})
return output.trim()
}