Compare commits

...

86 Commits

Author SHA1 Message Date
Graham Christensen c5a866b6ab Update to determinate by default (#208)
* Update to determinate by default

Implements https://github.com/DeterminateSystems/nix-src/issues/201

* Fixup the version verification script
2025-11-10 09:48:50 -05:00
Graham Christensen 89b1f59ae9 Drop x86_64-darwin (#207)
* Drop x86_64-darwin

Implements https://github.com/DeterminateSystems/nix-src/issues/224

* Force back to v3.12.2 for users who don't otherwise specify a tag

* ci: don't try to render the devshell on intel macOS

* Try an error?

* Update messaging to be more action-oriented
2025-11-09 22:55:22 -05:00
Luc Perkins 86a5f59de1 Merge pull request #206 from detsys-pr-bot/detsys-ts-update-cb28f5861548d8a85d054c4d6e8e0cd3d3c94329
Update `detsys-ts`: Merge pull request #114 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-47c548f420
2025-11-07 13:02:42 -03:00
lucperkins 64a1e1d1cb Update detsys-ts for: Merge pull request #114 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-47c548f420 (cb28f5861548d8a85d054c4d6e8e0cd3d3c94329) 2025-11-07 15:52:50 +00:00
Graham Christensen 2fd3724578 If determinate is false, pass --prefer-upstream (#202)
* If determinate is false, pass --prefer-upstream

* Regenerate

* Update to an installer version that supports preferring upstream

* Delete the rev test, since rev uploads appoar to have broken a long time ago

* Fixup: expect upstream nix at the moment
2025-10-24 22:24:28 -04:00
Luc Perkins 45a18a6995 Merge pull request #203 from detsys-pr-bot/detsys-ts-update-285768c0d657f9709cb689893f12895c36923093
Update `detsys-ts`: Merge pull request #109 from DeterminateSystems/tsup-to-tsdown
2025-09-12 14:50:59 +02:00
lucperkins d3ddf0223c Update detsys-ts for: Merge pull request #109 from DeterminateSystems/tsup-to-tsdown (285768c0d657f9709cb689893f12895c36923093) 2025-09-12 12:39:38 +00:00
gustavderdrache 786fff0690 Apply fixups from CodeRabbit review (#200) 2025-09-09 10:54:24 -04:00
gustavderdrache f161ab07ed Treat FlakeHub logins as a funnel (#199) 2025-09-09 10:40:05 -04:00
gustavderdrache 61ce7897f4 Merge pull request #198 from DeterminateSystems/gustavderdrache/push-mzwwpswkrrup
Record events if authentication is skipped
2025-09-09 09:58:05 -04:00
gustavderdrache 44f3801e21 Improve messages for when auth isn't available 2025-09-09 09:48:10 -04:00
gustavderdrache cb6d4e86fa Standardize login failure events 2025-09-09 09:44:32 -04:00
gustavderdrache e686131f84 Record events if authentication is skipped 2025-09-08 16:04:19 -04:00
Graham Christensen 2c3a2981f1 Report the number of nix builds that passed / failed (#196)
* Report the number of nix builds that passed / failed

* Upgrade
2025-09-03 12:03:09 -04:00
detsys-pr-bot 18b667a294 Update detsys-ts for: Capture the version of Nix in addition to the nix store version (#108) (c7303495f43d348cac78091ef434443b1ef22485) (#197)
Co-authored-by: grahamc <76716+grahamc@users.noreply.github.com>
2025-09-03 15:37:20 +00:00
detsys-pr-bot 428f3c64a3 Update detsys-ts for: Merge pull request #106 from DeterminateSystems/fix-dependabot-warning (6d3f1c5a5781e58b3cd8060cfb578c0c95eeb51e) (#194)
Co-authored-by: lucperkins <1523104+lucperkins@users.noreply.github.com>
2025-07-30 15:59:25 +00:00
detsys-pr-bot 90bb610b90 Update detsys-ts for: Await the request promise so we can cover it with the timout handler (#105) (20c4962e328c1eba8f04da00bbb7a7e307d511e0) (#191)
Co-authored-by: grahamc <76716+grahamc@users.noreply.github.com>
2025-07-14 22:53:06 +00:00
detsys-pr-bot c723f3a885 Update detsys-ts for: Skip complicated rewrites, record groups. (#104) (0095c476e55f64d04f1aa1e1bcc2524c329d073a) (#190)
Co-authored-by: grahamc <76716+grahamc@users.noreply.github.com>
2025-07-03 09:38:17 -04:00
Luc Perkins 41e0dcf215 Merge pull request #188 from detsys-pr-bot/detsys-ts-update-5084fa8e3263a0bed2383f46e407e6c2936e8289
Update `detsys-ts`: Remove FHC action since it's composite (#103)
2025-06-24 12:56:06 -07:00
grahamc e455bc9d67 Update detsys-ts for: Remove FHC action since it's composite (#103) (5084fa8e3263a0bed2383f46e407e6c2936e8289) 2025-06-23 19:56:57 +00:00
detsys-pr-bot b336b210d0 Update detsys-ts for: Merge pull request #101 from DeterminateSystems/gustavderdrache/write-correlation (e252a66f00e041869f7e402e579141f7b8ab1edf) (#187)
Co-authored-by: gustavderdrache <194893+gustavderdrache@users.noreply.github.com>
2025-06-23 18:40:45 +00:00
Luc Perkins 999d616271 Merge pull request #184 from detsys-pr-bot/detsys-ts-update-4bf247b1cb6b057abe94721ea1bfa131618e2b7f
Update `detsys-ts`: Merge pull request #97 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-a9a1a26a5c
2025-06-02 11:59:04 -07:00
lucperkins 2a6922c6a5 Update detsys-ts for: Merge pull request #97 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-a9a1a26a5c (4bf247b1cb6b057abe94721ea1bfa131618e2b7f) 2025-06-02 18:51:49 +00:00
Luc Perkins 4a562136e6 Merge pull request #182 from DeterminateSystems/graham/fh-813-create-a-determinate-nix-action-with-pinned-releases
Add a note about pinning
2025-05-16 15:46:58 -04:00
Luc Perkins b89da14ed6 Remove unnecessary future tense 2025-05-16 15:38:25 -04:00
Luc Perkins a0000cd639 Remove redundant word 2025-05-16 15:38:09 -04:00
Luc Perkins 9793b3bc29 Fix capitalization 2025-05-16 15:37:31 -04:00
Luc Perkins 3489e2d8c3 Fix verb conjugation 2025-05-16 15:37:23 -04:00
Graham Christensen e4760a7aa0 Add a note about pinning 2025-05-16 15:28:31 -04:00
detsys-pr-bot 2f3746ebff Update detsys-ts for: Merge pull request #95 from DeterminateSystems/graham/fh-813-create-a-determinate-nix-action-with-pinned-releases (74999c82de35e73feb13af2c0e0c3ebb92a17c66) (#181)
Co-authored-by: lucperkins <1523104+lucperkins@users.noreply.github.com>
2025-05-15 21:20:56 +00:00
Graham Christensen 780ad73c03 Ditch the docker shim in favor of direct execution (#180)
Co-authored-by: gustavderdrache <alex.ford@determinate.systems>
2025-05-13 17:34:37 -04:00
Luc Perkins affd83b251 Merge pull request #178 from detsys-pr-bot/detsys-ts-update-e0a5f196fd4e17f2ba1d6ad03300fe532717f7e8
Update `detsys-ts`: Merge pull request #94 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-dde80b0a8d
2025-05-12 07:19:06 -03:00
lucperkins 7e20c99dbd Update detsys-ts for: Merge pull request #94 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-dde80b0a8d (e0a5f196fd4e17f2ba1d6ad03300fe532717f7e8) 2025-05-12 10:12:01 +00:00
Graham Christensen 88762f81a9 Fix an obscure error case where if the only log fails, the rendering is wack (#177)
* Omit extraneous "m"s

* Fix an obscure error case where if the only log fails, the rendering is wack

* Collapse seconds on the  mermaid diagram into MmSs

* Add missing possessive

* Fixup tests, rename collapseSeconds to formatDuration

---------

Co-authored-by: Luc Perkins <lucperkins@gmail.com>
2025-05-08 12:56:31 -04:00
Luc Perkins 7b9fe29d91 Merge pull request #176 from detsys-pr-bot/detsys-ts-update-87c69ec3d48cb353229e74cc2ba8abdef2eb2590f
Update `detsys-ts`: Merge pull request #93 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-a403fbca50
2025-05-04 21:47:10 -03:00
lucperkins 4795fa54eb Update detsys-ts for: Merge pull request #93 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-a403fbca50 (87c69ec3d48cb353229e74cc2ba8abdef2eb2590) 2025-05-05 00:38:20 +00:00
Luc Perkins bec3e50af8 Merge pull request #174 from detsys-pr-bot/detsys-ts-update-19c948d15e766687225515331fbebc216a14ed06
Update `detsys-ts`: Merge pull request #92 from DeterminateSystems/dependabot/npm_and_yarn/npm_and_yarn-de653eece3
2025-04-30 15:49:45 -03:00
lucperkins 138c4e748c Update detsys-ts for: Merge pull request #92 from DeterminateSystems/dependabot/npm_and_yarn/npm_and_yarn-de653eece3 (19c948d15e766687225515331fbebc216a14ed06) 2025-04-30 18:42:20 +00:00
gustavderdrache 7b2eab0f02 Merge pull request #173 from DeterminateSystems/hash-mismatch-summary
Add hash mismatches to summary
2025-04-24 17:33:34 -04:00
Luc Perkins 7ee09f94c3 Provide a link to our guide rather than a URL 2025-04-24 18:26:43 -03:00
gustavderdrache dfa057fae0 s/INFO/TIP/ 2025-04-24 17:12:37 -04:00
gustavderdrache a79af99b14 Add hash mismatches to summary 2025-04-24 16:47:04 -04:00
gustavderdrache 65f0526420 Merge pull request #172 from DeterminateSystems/test-penance
Test penance
2025-04-24 14:44:24 -04:00
gustavderdrache b00ad13101 Clean up unneeded brackets 2025-04-24 14:36:34 -04:00
gustavderdrache 5f843f60e2 Add test for empty build summary case 2025-04-24 14:35:39 -04:00
Luc Perkins 01d7370b1d Merge pull request #171 from DeterminateSystems/suppress-empty-summaries
Correctly suppress empty summaries
2025-04-24 15:30:04 -03:00
gustavderdrache 7c49e2a674 Correctly suppress empty summaries 2025-04-24 14:19:48 -04:00
Graham Christensen 21a544727d Sometimes, two heads are NOT as good as one. (#170) 2025-04-23 19:45:24 -04:00
gustavderdrache b669a07c99 Merge pull request #141 from JTKBowers/main
Check whether the current user has access to KVM before attempting to add udev rule
2025-04-23 18:18:46 -04:00
gustavderdrache 520fb5ebbe Merge remote-tracking branch 'upstream/main' 2025-04-23 18:04:43 -04:00
gustavderdrache 47a222377c Merge pull request #139 from dpc/dpc/jj-vqymqvyntouw
chore: detect existing `nix` installations
2025-04-23 17:59:45 -04:00
gustavderdrache c6e05d595d Merge remote-tracking branch 'upstream/main' into dpc/jj-vqymqvyntouw 2025-04-23 17:51:06 -04:00
gustavderdrache c56aa5176f Regenerate bundle 2025-04-23 17:50:58 -04:00
gustavderdrache ed5212da54 Apply suggestions from code review 2025-04-23 17:43:48 -04:00
gustavderdrache d614ddf8ca Merge pull request #169 from DeterminateSystems/flakehub-login-diagnostics
Show diagnostics for FlakeHub login issues
2025-04-23 17:41:44 -04:00
gustavderdrache e1cdf1927d Show diagnostics for FlakeHub login issues 2025-04-23 17:25:59 -04:00
Graham Christensen aacc1657a2 Don't blow the limits of summaries, by not printing logs if it blows the limit (#168)
* Don't blow the limits of summaries, by not printing logs if it blows the limit

* Print out log summaries for unrendered logs at the END of the console log, so they're easier to find

* fixup

* Pull the default max summary length out
2025-04-23 20:05:44 +00:00
Graham Christensen ce0da527df Summarize the build timeline (#165)
* Show build failure summary

* Record the start timestamp after the selftest

* Cleanup: use default parameter

* Cleanup: don't reassign arguments

* Cleanup: simplify do-while logic

* Cleanup: extract truncation helper

* Cleanup: use locals

* cleanup: minor nits

* fix lints

* build

* Use determinate-nixd's --since flag

* Re-render...

* Fixup the note about 50k being the max

---------

Co-authored-by: gustavderdrache <alex.ford@determinate.systems>
2025-04-16 13:25:51 -07:00
gustavderdrache 902d820b25 Annotate hash mismatches when Determinate features are enabled (#158)
* Render hash mismatches as feedback
* Remove superfluous punctuation
* Use determinate-nixd's hash fix feature
* Feature gate annotations
* Add annotation telemetry
* Remove .drv when rendering pretty derivation names
2025-04-14 12:40:23 -04:00
Luc Perkins 17a3ce76e0 Merge pull request #164 from detsys-pr-bot/detsys-ts-update-f42f6003b4044fede4681778f76342e523671e8f
Update `detsys-ts`: Merge pull request #89 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-0b8d2803d6
2025-04-13 23:46:29 -03:00
lucperkins 52cdd33747 Update detsys-ts for: Merge pull request #89 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-0b8d2803d6 (f42f6003b4044fede4681778f76342e523671e8f) 2025-04-14 02:30:08 +00:00
Luc Perkins 0d28deea2b Merge pull request #162 from detsys-pr-bot/detsys-ts-update-e31aa55518cae49b58723c152c6d0e46ee223ec1
Update `detsys-ts`: Merge pull request #87 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-2f3c1638ee
2025-04-06 22:43:26 -03:00
lucperkins 956acc53ac Update detsys-ts for: Merge pull request #87 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-2f3c1638ee (e31aa55518cae49b58723c152c6d0e46ee223ec1) 2025-04-07 01:17:10 +00:00
detsys-pr-bot 741b61d2a0 Update detsys-ts for: Merge pull request #86 from DeterminateSystems/dependabot/npm_and_yarn/npm_and_yarn-2bd33993d4 (dc7c6d9c7a5d2db79ddc6eccf57d5fc09a391347) (#161)
Co-authored-by: lucperkins <1523104+lucperkins@users.noreply.github.com>
2025-04-04 16:26:22 +00:00
detsys-pr-bot 7239c695c3 Update detsys-ts for: Bump vite from 6.2.3 to 6.2.4 in the npm_and_yarn group (#85) (79634332de50a2cde17cf8d6fe41258af3d4bedf) (#160)
Co-authored-by: grahamc <76716+grahamc@users.noreply.github.com>
2025-03-31 19:48:26 +00:00
detsys-pr-bot 28aa4ed62f Update detsys-ts for: Merge pull request #84 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-73588cc3c5 (3ef4baee587df9fe98f70036386e361fa0d6dc7e) (#159)
Co-authored-by: lucperkins <1523104+lucperkins@users.noreply.github.com>
2025-03-31 09:05:09 -04:00
detsys-pr-bot 78d714f6f7 Update detsys-ts: Ignore hyphen-sep'd diags (#83) (#155)
* Update `detsys-ts` for: `Ignore hyphen-sep'd diags (#83)` (`07c7fc924119a8d9879c1c164ae593049d47f648`)

* Wait for the socket to appear

* ...

---------

Co-authored-by: grahamc <76716+grahamc@users.noreply.github.com>
Co-authored-by: Graham Christensen <graham@grahamc.com>
2025-03-28 15:20:00 -04:00
Graham Christensen 3e92d74f28 fail-fast: false (#156) 2025-03-28 14:17:41 -04:00
detsys-pr-bot 84f3b6ba94 Update detsys-ts for: Merge pull request #82 from DeterminateSystems/even-more-crashes (03533d37dcd46f34d9e99385e665615b221a30d9) (#154)
Co-authored-by: grahamc <76716+grahamc@users.noreply.github.com>
2025-03-28 14:19:02 +00:00
detsys-pr-bot 8f9243d6dd Update detsys-ts for: Merge pull request #81 from DeterminateSystems/dont-capture-some-crashes (8d9725c4856301321cd2508f5b8725cfb99366e2) (#153)
Co-authored-by: grahamc <76716+grahamc@users.noreply.github.com>
2025-03-27 16:04:36 +00:00
Luc Perkins 480fcd11bd Merge pull request #152 from detsys-pr-bot/detsys-ts-update-38df301720b69972f084538dd44c181269f264b0
Update `detsys-ts`: Merge pull request #80 from DeterminateSystems/fixup-traces
2025-03-27 11:10:33 -03:00
grahamc 2551261839 Update detsys-ts for: Merge pull request #80 from DeterminateSystems/fixup-traces (38df301720b69972f084538dd44c181269f264b0) 2025-03-27 13:47:59 +00:00
Luc Perkins 7f1ff2b54f Merge pull request #151 from detsys-pr-bot/detsys-ts-update-cf33e9577ca7571bf9ebbfe298b4e3e457a58813
Update `detsys-ts`: Merge pull request #79 from DeterminateSystems/dependabot/npm_and_yarn/npm_and_yarn-14f44f5325
2025-03-26 10:00:20 -03:00
lucperkins 1e22780707 Update detsys-ts for: Merge pull request #79 from DeterminateSystems/dependabot/npm_and_yarn/npm_and_yarn-14f44f5325 (cf33e9577ca7571bf9ebbfe298b4e3e457a58813) 2025-03-26 12:32:03 +00:00
Luc Perkins dc965b7805 Merge pull request #150 from DeterminateSystems/update-deps 2025-03-25 19:27:38 -03:00
Cole Helbling fe228f6faf Update @actions/github to 6.0.0 2025-03-25 14:41:42 -07:00
Luc Perkins 300844e532 Merge pull request #149 from DeterminateSystems/flakehub-cache-action 2025-03-24 15:51:30 -03:00
Luc Perkins 86febfe320 Switch to flakehub-cache-action 2025-03-24 00:49:28 -03:00
Luc Perkins 0c7c6d4acd Merge pull request #148 from detsys-pr-bot/detsys-ts-update-9d2b0e4636787bba8b886ddc720f007850e541fb
Update `detsys-ts`: Merge pull request #78 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-0af3b8ec11
2025-03-24 00:45:22 -03:00
lucperkins fbb684a4bf Update detsys-ts for: Merge pull request #78 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-0af3b8ec11 (9d2b0e4636787bba8b886ddc720f007850e541fb) 2025-03-24 01:42:22 +00:00
detsys-pr-bot 37dc9ba6c4 Update detsys-ts for: Merge pull request #74 from DeterminateSystems/dependabot/npm_and_yarn/npm-deps-eb3d92718e (4c7ff9706e6466d7c03c58a3479ed212d6ffb5ba) (#146)
Co-authored-by: grahamc <76716+grahamc@users.noreply.github.com>
2025-03-19 17:30:45 -04:00
Jamie Bowers 5e80a7bd8b Check whether the current user already has access to KVM before attempting to add a udev rule. 2025-02-20 11:09:13 +00:00
Dawid Ciężarkiewicz 2d9ffd87e4 chore: add a note about how to install everything
JS has a new shiny tool every 6 months, and I'm not a JS dev, so
it's not immediately obvious why `npm run all` doesn't work, despite
being in a dev shell.
2025-02-07 15:21:18 -08:00
Dawid Ciężarkiewicz 4b27401a78 chore: detect existing nix installations 2025-02-07 15:19:55 -08:00
detsys-pr-bot a48face581 Update detsys-ts for: Merge pull request #71 from DeterminateSystems/updates (b3319a2c78c46d5ad6bc00d3453266d3d9fdde44) (#138)
Co-authored-by: grahamc <76716+grahamc@users.noreply.github.com>
2025-02-06 16:54:30 +00:00
detsys-pr-bot dea7810afd Update detsys-ts for: Merge pull request #69 from DeterminateSystems/update-deps (eb87094f35072ac911526ad052c3437c9e0c42d6) (#131)
Co-authored-by: grahamc <76716+grahamc@users.noreply.github.com>
2024-11-26 11:32:08 -05:00
26 changed files with 61030 additions and 60232 deletions
+1 -2
View File
@@ -7,9 +7,8 @@ EXPECTED_VERSION="${1}"
INSTALLED_NIX_VERSION_OUTPUT=$(nix --version)
INSTALLED_NIX_VERSION=$(echo "${INSTALLED_NIX_VERSION_OUTPUT}" | awk '{print $NF}')
EXPECTED_OUTPUT="nix (Nix) ${EXPECTED_VERSION}"
if [ "${INSTALLED_NIX_VERSION_OUTPUT}" != "${EXPECTED_OUTPUT}" ]; then
if [ "${INSTALLED_NIX_VERSION}" != "${EXPECTED_VERSION}" ]; then
echo "Nix version ${INSTALLED_NIX_VERSION} didn't match expected version ${EXPECTED_VERSION}"
exit 1
else
+6 -9
View File
@@ -28,7 +28,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- uses: DeterminateSystems/flakehub-cache-action@main
- name: pnpm install
run: nix develop --command pnpm install
- name: prettier format
@@ -47,6 +47,7 @@ jobs:
install-nix:
name: "Test: ${{ matrix.runner }}${{ matrix.determinate && ' with determinate' || '' }}"
strategy:
fail-fast: false
matrix:
runner:
- ubuntu-latest
@@ -77,7 +78,7 @@ jobs:
run: echo $PATH
- name: Render the devshell
if: success() || failure()
if: (success() || failure()) && matrix.runner != 'macos-13-large' && matrix.runner != 'macos-14-large'
run: |
nix develop --command date
@@ -149,14 +150,10 @@ jobs:
strategy:
matrix:
inputs:
# https://github.com/DeterminateSystems/nix-installer/blob/v0.18.0
# https://github.com/DeterminateSystems/nix-installer/blob/v3.11.3
- key: url
value: https://github.com/DeterminateSystems/nix-installer/releases/download/v0.18.0/nix-installer-x86_64-linux
nix-version: "2.21.2"
# https://github.com/DeterminateSystems/nix-installer/tree/7011c077ec491da410fbc39f68676b0908b9ce7e
- key: revision
value: 7011c077ec491da410fbc39f68676b0908b9ce7e
nix-version: "2.19.2"
value: https://github.com/DeterminateSystems/nix-installer/releases/download/v3.11.3/nix-installer-x86_64-linux
nix-version: "2.31.2" # 3.11.3 is based on 2.31.2
steps:
- uses: actions/checkout@v4
+2
View File
@@ -1,3 +1,5 @@
# Submitting Pull Requests
Run `pnpm install` to install necessary JS tools.
This action is based off https://github.com/actions/javascript-action. As part of your contributing flow you **must** run `npm run all` before we can merge.
+19 -6
View File
@@ -34,7 +34,7 @@ jobs:
### With FlakeHub
To fetch private flakes from FlakeHub and Nix builds from FlakeHub Cache, update the `permissions` block and pass `determinate: true`:
To fetch private flakes from FlakeHub and Nix builds from FlakeHub Cache, update the `permissions` block and use [`determinate-nix-action`][determinate-nix-action]:
```yaml
on:
@@ -51,14 +51,25 @@ jobs:
contents: "read"
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
with:
determinate: true
- uses: DeterminateSystems/determinate-nix-action@v3
- run: nix build .
```
See [`.github/workflows/ci.yml`](.github/workflows/ci.yml) for a full example.
### Pinning the version
This GitHub Action uses the most recent version of the Determinate Nix Installer, even when the Action itself is pinned.
If you wish to pin your CI workflows to a specific version, use the [`determinate-nix-action`][determinate-nix-action].
That Action is updated and tagged for every Determinate release.
The `DeterminateSystems/determinate-nix-action@v3.5.2` reference, for example, always installs Determinate Nix v3.5.2.
Additionally, an extra tag on the major version is kept up to date with the current release.
The `DeterminateSystems/determinate-nix-action@v3` reference, for example, installs the most recent release in the `v3.x.y` series.
If you do tag to a specific version, please [use Dependabot to update your actions][dependabot-actions].
### Advanced Usage
- If KVM is available, the installer sets up KVM so that Nix can use it ,and exports the `DETERMINATE_NIX_KVM` environment variable set to 1.
@@ -85,11 +96,11 @@ Differing from the upstream [Nix](https://github.com/NixOS/nix) installer script
| Parameter | Description | Type | Default |
| :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------- | :------------------------------------------------------------- |
| `backtrace` | The setting for [`RUST_BACKTRACE`][backtrace] | string | |
| `determinate` | Whether to install [Determinate Nix](https://determinate.systems/enterprise) and log in to FlakeHub for private Flakes and binary caches. | Boolean | `false` |
| `determinate` | Whether to install [Determinate Nix](https://determinate.systems/enterprise) and log in to FlakeHub for private Flakes and binary caches. | Boolean | `true` |
| `extra-args` | Extra arguments to pass to the planner (prefer using structured `with:` arguments unless using a custom [planner]!) | string | |
| `extra-conf` | Extra configuration lines for `/etc/nix/nix.conf` (includes `access-tokens` with `secrets.GITHUB_TOKEN` automatically if `github-token` is set) | string | |
| `flakehub` | Deprecated. Implies `determinate`. | Boolean | `false` |
| `force-docker-shim` | Force the use of Docker as a process supervisor. This setting is automatically enabled when necessary. | Boolean | `false` |
| `force-no-systemd` | Force using other methods than systemd to launch the daemon. This setting is automatically enabled when necessary. | Boolean | `false` |
| `github-token` | A [GitHub token] for making authenticated requests (which have a higher rate-limit quota than unauthenticated requests) | string | `${{ github.token }}` |
| `github-server-url` | The URL for the GitHub server, to use with the `github-token` token. Defaults to the current GitHub server, supporting GitHub Enterprise Server automatically. Only change this value if the provided `github-token` is for a different GitHub server than the current server. | string | `${{ github.server }}` |
| `init` | The init system to configure (requires `planner: linux-multi`) | enum (`none` or `systemd`) | |
@@ -123,6 +134,8 @@ Differing from the upstream [Nix](https://github.com/NixOS/nix) installer script
[apfs]: https://en.wikipedia.org/wiki/Apple_File_System
[backtrace]: https://doc.rust-lang.org/std/backtrace/index.html#environment-variables
[dependabot-actions]: https://github.com/DeterminateSystems/determinate-nix-action?tab=readme-ov-file#-automate-updates-with-dependabot
[determinate-nix-action]: https://github.com/DeterminateSystems/determinate-nix-action
[github token]: https://docs.github.com/en/actions/security-guides/automatic-token-authentication
[planner]: https://github.com/determinateSystems/nix-installer#usage
[profile]: https://nixos.org/manual/nix/stable/package-management/profiles
+3 -3
View File
@@ -10,7 +10,7 @@ inputs:
determinate:
description: |
Whether to install [Determinate Nix](https://determinate.systems/enterprise) and log in to FlakeHub for private Flakes and binary caches.
default: false
default: true
extra-args:
description: Extra args to pass to the planner (prefer using structured `with:` arguments unless using a custom planner!)
required: false
@@ -21,8 +21,8 @@ inputs:
description: Deprecated. Implies `determinate`.
required: false
default: false
force-docker-shim:
description: Force the use of Docker as a process supervisor. This setting is automatically enabled when necessary.
force-no-systemd:
description: Force using other methods than systemd to launch the daemon. This setting is automatically enabled when necessary.
required: false
default: false
github-token:
Generated Vendored
BIN
View File
Binary file not shown.
Generated Vendored
BIN
View File
Binary file not shown.
Generated Vendored
+57154 -57993
View File
File diff suppressed because one or more lines are too long
-19
View File
@@ -1,19 +0,0 @@
# Determinate Nix Installer: Docker Shim
#
# This empty image exists to lean on Docker as a process supervisor when
# systemd isn't available. Specifically intended for self-hosted GitHub
# Actions runners using Docker-in-Docker.
#
# See: https://github.com/DeterminateSystems/nix-installer-action
FROM scratch
ENTRYPOINT [ "/nix/var/nix/profiles/default/bin/nix-daemon"]
CMD []
HEALTHCHECK \
--interval=5m \
--timeout=3s \
CMD ["/nix/var/nix/profiles/default/bin/nix", "store", "ping", "--store", "daemon"]
COPY ./Dockerfile /README.md
-52
View File
@@ -1,52 +0,0 @@
# Determinate Nix Installer Action: Docker Shim
The image in this repository is a product of the contained Dockerfile.
It is an otherwise empty image with a configuration layer.
This image is to be used in GitHub Actions runners which don't have systemd available, like self-hosted ARC runners.
The image would have no layers / content at all, however Docker has a bug and refuses to export those images.
This isn't a technical limitation preventing us from creating and distributing that image, but an ease-of-use limitation.
Since some of Docker's inspection tools break on an empty image, the image contains a single layer containing a README.
To build:
```shell
docker build . --tag determinate-nix-shim:latest
docker image save determinate-nix-shim:latest | gzip --best > amd64.tar
```
Then, extract the tarball:
```
mkdir extract
cd extract
tar -xf ../amd64.tar
```
It'll look like this, though the hashes will be different.
```
.
├── 771204abb853cdde06bbbc680001a02642050a1db1a7b0a48cf5f20efa8bdc5d.json
├── c4088111818e553e834adfc81bda8fe6da281afa9a40012eaa82796fb5476e98
│   ├── VERSION
│   ├── json
│   └── layer.tar
├── manifest.json
└── repositories
```
Ignore `manifest.json`, and edit the other two JSON documents to replace `amd64` with `arm64`, both in a key named "architecture:
```
"architecture":"amd64"
```
Then re-create the tar, from within the `extract` directory:
```
tar --options gzip:compression-level=9 -zcf ../arm64.tar.gz .
```
Then `git add` the two .tar.gz's and you're done.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -9,7 +9,7 @@
outputs = { self, flake-schemas, nixpkgs }:
let
supportedSystems = [ "x86_64-linux" "aarch64-darwin" "aarch64-linux" "x86_64-darwin" ];
supportedSystems = [ "x86_64-linux" "aarch64-darwin" "aarch64-linux" ];
forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
pkgs = import nixpkgs { inherit system; };
});
+17 -15
View File
@@ -11,7 +11,8 @@
"check-fmt": "prettier --check .",
"lint": "eslint src/**/*.ts",
"package": "ncc build",
"all": "pnpm run format && pnpm run lint && pnpm run build && pnpm run package"
"test": "vitest --watch false",
"all": "pnpm run test && pnpm run format && pnpm run lint && pnpm run build && pnpm run package"
},
"repository": {
"type": "git",
@@ -25,26 +26,27 @@
},
"homepage": "https://github.com/DeterminateSystems/nix-installer-action#readme",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/core": "^1.11.1",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@actions/github": "^6.0.1",
"detsys-ts": "github:DeterminateSystems/detsys-ts",
"got": "^14.3.0",
"string-argv": "^0.3.2"
"got": "^14.6.2",
"string-argv": "^0.3.2",
"vitest": "^3.2.4"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/node": "^20.14.0",
"@types/node": "^20.19.24",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^7.12.0",
"@vercel/ncc": "^0.38.1",
"eslint": "^8.57.0",
"eslint-import-resolver-typescript": "^3.6.1",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@vercel/ncc": "^0.38.4",
"eslint": "^8.57.1",
"eslint-import-resolver-typescript": "^3.10.1",
"eslint-plugin-github": "^4.10.2",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"prettier": "^3.3.0",
"tsup": "^8.1.0",
"typescript": "^5.4.5"
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.4",
"prettier": "^3.6.2",
"tsup": "^8.5.0",
"typescript": "^5.9.3"
}
}
+2481 -1738
View File
File diff suppressed because it is too large Load Diff
+66
View File
@@ -0,0 +1,66 @@
import * as core from "@actions/core";
import type { Fix, FixHashesOutputV1, Mismatch } from "./fixHashes.js";
function prettyDerivation(derivation: string): string {
return derivation.replace(/\/nix\/store\/\w+-/, "").replace(/.drv$/, "");
}
function annotateSingle(
file: string,
line: number,
{ derivation, replacement }: Mismatch,
): void {
const pretty = prettyDerivation(derivation);
core.error(`To correct the hash mismatch for ${pretty}, use ${replacement}`, {
file,
startLine: line,
});
}
function annotateMultiple(
file: string,
{ line, found, mismatches }: Fix,
): void {
const matches = mismatches
.map(({ derivation, replacement }) => {
const pretty = prettyDerivation(derivation);
return `* For the derivation ${pretty}, use ${replacement}`;
})
.join("\n");
core.error(
`There are multiple replacements for the expression ${found}:\n${matches}`,
{
file,
startLine: line,
},
);
}
function annotate(file: string, fix: Fix): void {
if (fix.mismatches.length === 1) {
annotateSingle(file, fix.line, fix.mismatches[0]);
} else {
annotateMultiple(file, fix);
}
}
/**
* Annotates fixed-output derivation hash mismatches using GitHub Actions'
*
* @param output The output of `determinate-nixd fix hashes --json`
* @returns The number of annotations reported to the user
*/
export function annotateMismatches(output: FixHashesOutputV1): number {
let count = 0;
for (const { file, fixes } of output.files) {
for (const fix of fixes) {
annotate(file, fix);
count++;
}
}
return count;
}
+65
View File
@@ -0,0 +1,65 @@
import { parseEvents, getRecentEvents } from "./events.js";
import { expect, test } from "vitest";
// Handy test for locally making sure you can fetch recent events:
// eslint-disable-next-line no-constant-condition
if (false) {
test("Parsing existing events", async () => {
expect(await getRecentEvents(new Date(Date.now() - 1000000))).toStrictEqual(
[{}],
);
});
}
test("Parsing existing events", () => {
const { events } = parseEvents([
{
v: "1",
c: "BuiltPathResponseEventV1",
drv: "/nix/store/m96zgji4fhi70s2zs6pq5pric6ch7p4h-stdenv-darwin.drv",
outputs: ["/nix/store/dalhfz3l75w4b4q06sxzqgb2wfydvkbv-stdenv-darwin"],
timing: null,
},
{
v: "1",
c: "BuiltPathResponseEventV1",
drv: "/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv",
outputs: ["/nix/store/qwlgz5da3pfb53gqpgdmazaj9jczrnly-dep-1"],
timing: {
startTime: "2025-04-11T14:38:02Z",
stopTime: "2025-04-11T14:38:05Z",
durationSeconds: 3,
},
},
{
v: "1",
c: "BuildFailureResponseEventV1",
drv: "/nix/store/ykvbksjqrza2zpj6nkbycrdfwgfdpr8g-hash-mismatch-md5-base16.drv",
timing: {
startTime: "2025-04-11T14:36:44Z",
stopTime: "2025-04-11T14:36:44Z",
durationSeconds: 0,
},
},
]);
expect(events).toStrictEqual([
{
v: "1",
c: "BuiltPathResponseEventV1",
drv: "/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv",
timing: {
durationSeconds: 3,
startTime: new Date("2025-04-11T14:38:02Z"),
},
},
{
v: "1",
c: "BuildFailureResponseEventV1",
drv: "/nix/store/ykvbksjqrza2zpj6nkbycrdfwgfdpr8g-hash-mismatch-md5-base16.drv",
timing: {
durationSeconds: 0,
startTime: new Date("2025-04-11T14:36:44Z"),
},
},
]);
});
+89
View File
@@ -0,0 +1,89 @@
import got from "got";
export interface DEvent {
v: string;
c: string;
drv: string;
timing: {
startTime: Date;
durationSeconds: number;
};
}
export interface ParsedEventsResult {
readonly events: DEvent[];
readonly hasMismatches: boolean;
}
export function parseEvents(data: unknown): ParsedEventsResult {
let hasMismatches = false;
if (!Array.isArray(data)) {
return { events: [], hasMismatches };
}
const events = data.flatMap((event) => {
// If this was a hash mismatch event, note it and move on
if (event.v === "1" && event.c === "HashMismatchResponseEventV1") {
hasMismatches = true;
return [];
}
// Otherwise, determine if it's an event we're interested in
if (
event.v === "1" &&
(event.c === "BuildFailureResponseEventV1" ||
event.c === "BuiltPathResponseEventV1") &&
Object.hasOwn(event, "drv") &&
typeof event.drv === "string" &&
Object.hasOwn(event, "timing") &&
typeof event.timing === "object" &&
event.timing !== null
) {
const timing = event.timing as { [key: string]: unknown };
if (
Object.hasOwn(timing, "startTime") &&
typeof timing.startTime === "string" &&
Object.hasOwn(timing, "durationSeconds") &&
typeof timing.durationSeconds === "number"
) {
const date = Date.parse(timing.startTime);
if (!Number.isNaN(date)) {
return [
{
v: event.v,
c: event.c,
drv: event.drv,
timing: {
startTime: new Date(date),
durationSeconds: timing.durationSeconds,
},
},
];
}
}
}
return [];
});
return { events, hasMismatches };
}
export async function getRecentEvents(
since: Date,
): Promise<ParsedEventsResult> {
const queryParam = encodeURIComponent(since.toISOString());
const resp = await got
.get(
`http://unix:/nix/var/determinate/determinate-nixd.socket:/events/recent?since=${queryParam}`,
{
enableUnixSockets: true,
},
)
.json();
return parseEvents(resp);
}
+275
View File
@@ -0,0 +1,275 @@
import { expect, test } from "vitest";
import {
FailureSummary,
getBuildFailures,
summarizeFailures,
} from "./failuresummary.js";
/* eslint-disable @typescript-eslint/no-non-null-assertion */
test("Select for failure events", () => {
const events = [
{
v: "1",
c: "BuildFailureResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv`,
timing: {
startTime: new Date(1 * 1000),
durationSeconds: 1,
},
},
{
v: "1",
c: "BuiltPathResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-2.drv`,
timing: {
startTime: new Date(2 * 1000),
durationSeconds: 2,
},
},
{
v: "1",
c: "BuildFailureResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv`,
timing: {
startTime: new Date(3 * 1000),
durationSeconds: 3,
},
},
];
expect(getBuildFailures(events)).toStrictEqual([
{
v: "1",
c: "BuildFailureResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv`,
timing: {
startTime: new Date(1 * 1000),
durationSeconds: 1,
},
},
{
v: "1",
c: "BuildFailureResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv`,
timing: {
startTime: new Date(3 * 1000),
durationSeconds: 3,
},
},
]);
});
test("Summarize Failures", async () => {
const events = [
{
v: "1",
c: "BuildFailureResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv`,
timing: {
startTime: new Date(1 * 1000),
durationSeconds: 1,
},
},
{
v: "1",
c: "BuiltPathResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-2.drv`,
timing: {
startTime: new Date(2 * 1000),
durationSeconds: 2,
},
},
{
v: "1",
c: "BuildFailureResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv`,
timing: {
startTime: new Date(3 * 1000),
durationSeconds: 3,
},
},
];
const logMaker = async (drv: string): Promise<string | undefined> => {
if (drv.includes("dep-1")) {
return `${drv}\n`.repeat(9).trimEnd();
} else {
return `${drv}\n`.repeat(25).trimEnd();
}
};
const summary: FailureSummary = (await summarizeFailures(events, logMaker))!;
expect(summary.markdownLines.join("\n"))
.toStrictEqual(`### Build error review :boom:
> [!NOTE]
> 2 builds failed
<details><summary>Failure log: <code>/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-<strong>dep-1</strong>.drv</code></summary>
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
</details>
<details><summary>Failure log: <code>/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-<strong>dep-3</strong>.drv</code></summary>
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
</details>
`);
expect(summary.logLines.join("\n"))
.toStrictEqual(`\u001b[38;2;255;0;0mBuild logs from 2 failures
The following build logs are also available in the Markdown summary:
::group::Failed build: /nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
::endgroup::
::group::Failed build: /nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
::endgroup::`);
});
test("Omit some logs if there are too many", async () => {
const events = [
{
v: "1",
c: "BuildFailureResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv`,
timing: {
startTime: new Date(1 * 1000),
durationSeconds: 1,
},
},
{
v: "1",
c: "BuiltPathResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-2.drv`,
timing: {
startTime: new Date(2 * 1000),
durationSeconds: 2,
},
},
{
v: "1",
c: "BuildFailureResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv`,
timing: {
startTime: new Date(3 * 1000),
durationSeconds: 3,
},
},
];
const logMaker = async (drv: string): Promise<string | undefined> => {
return `${drv}\n`.repeat(5).trimEnd();
};
const summary: FailureSummary = (await summarizeFailures(
events,
logMaker,
500,
))!;
expect(summary.markdownLines.join("\n"))
.toStrictEqual(`### Build error review :boom:
> [!NOTE]
> 2 builds failed
<details><summary>Failure log: <code>/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-<strong>dep-1</strong>.drv</code></summary>
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
</details>
> [!NOTE]
> The following failure has been omitted due to GitHub Actions' summary length limitations.
> The full logs are available in the post-run phase of the Nix Installer Action.
> * \`/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv\``);
expect(summary.logLines.join("\n"))
.toStrictEqual(`\u001b[38;2;255;0;0mBuild logs from 2 failures
The following build logs are also available in the Markdown summary:
::group::Failed build: /nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv
::endgroup::
The following build logs are NOT available in the Markdown summary:
::group::Failed build: /nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-3.drv
::endgroup::`);
});
+124
View File
@@ -0,0 +1,124 @@
import { getExecOutput } from "@actions/exec";
import { DEvent } from "./events.js";
import { stripVTControlCharacters } from "node:util";
// CI summaries have a max length of "1024k" which I assume to be 1048576 bytes.
// Generously, the mermaid doc is about 50,000 bytes.
// Rounding it all down a bit further for wiggle room, that leaves lots of log space.
const defaultMaxSummaryLength = 995_000;
export function getBuildFailures(events: DEvent[]): DEvent[] {
return events.filter((event: DEvent): Boolean => {
return event.c === "BuildFailureResponseEventV1";
});
}
export interface FailureSummary {
logLines: string[];
markdownLines: string[];
}
export async function summarizeFailures(
events: DEvent[],
getLog: (drv: string) => Promise<string | undefined> = getLogFromNix,
maxLength: number = defaultMaxSummaryLength,
): Promise<FailureSummary | undefined> {
const failures = getBuildFailures(events);
if (failures.length === 0) {
return undefined;
}
const logLines = [];
const markdownLines = [];
logLines.push(
`\u001b[38;2;255;0;0mBuild logs from ${failures.length} failure${failures.length === 1 ? "" : "s"}`,
);
logLines.push(
`The following build logs are also available in the Markdown summary:`,
);
markdownLines.push(`### Build error review :boom:`);
markdownLines.push("> [!NOTE]");
markdownLines.push(
`> ${failures.length} build${failures.length === 1 ? "" : "s"} failed`,
);
const markdownLogChunks: {
drv: string;
txtLines: string[];
mdLines: string[];
}[] = [];
for (const event of failures) {
const markdownLogChunk = [];
const txtLogChunk = [];
txtLogChunk.push(`::group::Failed build: ${event.drv}`);
const log =
(await getLog(event.drv)) ??
"(failure reading the log for this derivation.)";
const indented = log.split("\n").map((line) => ` ${line}`);
markdownLogChunk.push(
`<details><summary>Failure log: <code>${event.drv.replace(/^(\/nix[^-]*-)(.*)(\.drv)$/, "$1<strong>$2</strong>$3")}</code></summary>`,
);
markdownLogChunk.push("");
for (const line of indented) {
txtLogChunk.push(line);
markdownLogChunk.push(stripVTControlCharacters(line));
}
markdownLogChunk.push("");
markdownLogChunk.push("</details>");
markdownLogChunk.push("");
markdownLogChunks.push({
drv: event.drv,
mdLines: markdownLogChunk,
txtLines: txtLogChunk,
});
txtLogChunk.push(`::endgroup::`);
}
const skippedChunks = [];
// Add markdown log chunks until we exceed the max length
let markdownLength = markdownLines.join("\n").length;
for (const chunk of markdownLogChunks) {
const chunkLength = chunk.mdLines.join("\n").length;
if (markdownLength + chunkLength > maxLength) {
skippedChunks.push(chunk);
} else {
logLines.push(...chunk.txtLines);
markdownLines.push(...chunk.mdLines);
markdownLength += chunkLength;
}
}
if (skippedChunks.length > 0) {
markdownLines.push(
"",
"> [!NOTE]",
`> The following ${skippedChunks.length === 1 ? "failure has" : "failures have"} been omitted due to GitHub Actions' summary length limitations.`,
"> The full logs are available in the post-run phase of the Nix Installer Action.",
);
logLines.push(
"The following build logs are NOT available in the Markdown summary:",
);
for (const chunk of skippedChunks) {
markdownLines.push(`> * \`${chunk.drv}\``);
logLines.push(...chunk.txtLines);
}
}
return { logLines, markdownLines };
}
async function getLogFromNix(drv: string): Promise<string | undefined> {
const output = await getExecOutput("nix", ["log", drv], {
silent: true,
});
return output.stdout;
}
+38
View File
@@ -0,0 +1,38 @@
import { getExecOutput } from "@actions/exec";
export interface Mismatch {
readonly derivation: string;
readonly replacement: string;
}
export interface Fix {
readonly line: number;
readonly found: string;
readonly mismatches: readonly Mismatch[];
}
export interface FileFix {
readonly file: string;
readonly fixes: readonly Fix[];
}
export interface FixHashesOutputV1 {
readonly version: "v1";
readonly files: readonly FileFix[];
}
export async function getFixHashes(since: string): Promise<FixHashesOutputV1> {
const output = await getExecOutput(
"determinate-nixd",
["fix", "hashes", "--json", "--since", since],
{ silent: true },
);
if (output.exitCode !== 0) {
throw new Error(
`determinate-nixd fix hashes returned non-zero exit code ${output.exitCode} with the following error output:\n${output.stderr}`,
);
}
return JSON.parse(output.stdout);
}
+381 -392
View File
@@ -1,44 +1,61 @@
import * as actionsCore from "@actions/core";
import * as actionsExec from "@actions/exec";
import { access, readFile } from "node:fs/promises";
import * as github from "@actions/github";
import { access, readFile, stat, writeFile } from "node:fs/promises";
import { join } from "node:path";
import fs from "node:fs";
import fs, { mkdirSync, openSync } from "node:fs";
import { userInfo } from "node:os";
import stringArgv from "string-argv";
import * as path from "path";
import { DetSysAction, inputs, platform, stringifyError } from "detsys-ts";
import { randomUUID } from "node:crypto";
import got from "got";
import { setTimeout } from "node:timers/promises";
import { getFixHashes } from "./fixHashes.js";
import { annotateMismatches } from "./annotate.js";
import { DEvent, getRecentEvents } from "./events.js";
import { makeMermaidReport } from "./mermaid.js";
import { summarizeFailures } from "./failuresummary.js";
import { SpawnOptions, spawn } from "node:child_process";
// Nix installation events
const EVENT_INSTALL_NIX_FAILURE = "install_nix_failure";
const EVENT_INSTALL_NIX_START = "install_nix_start";
const EVENT_INSTALL_NIX_SUCCESS = "install_nix_start";
const EVENT_INSTALL_NIX_SUCCESS = "install_nix_success";
const EVENT_SETUP_KVM = "setup_kvm";
const EVENT_UNINSTALL_NIX = "uninstall";
// Docker events
const EVENT_CLEAN_UP_DOCKER_SHIM = "clean_up_docker_shim";
const EVENT_START_DOCKER_SHIM = "start_docker_shim";
// FlakeHub events
const EVENT_LOGIN_TO_FLAKEHUB = "login_to_flakehub";
const EVENT_LOGIN_START = "flakehub-login:start";
const EVENT_LOGIN_FAILURE = "flakehub-login:failure";
const EVENT_LOGIN_SUCCESS = "flakehub-login:success";
const EVENT_LOGIN_END = "flakehub-login:end";
// Other events
const EVENT_CONCLUDE_JOB = "conclude_job";
const EVENT_FOD_ANNOTATE = "fod_annotate";
const EVENT_NO_SYSTEMD_SHIM_FAILED = "no-systemd-shim-failed";
// Feature flag names
const FEAT_ANNOTATIONS = "hash-mismatch-annotations";
// Facts
const FACT_DETERMINATE_NIX = "determinate_nix";
const FACT_HAS_DOCKER = "has_docker";
const FACT_HAS_SYSTEMD = "has_systemd";
const FACT_IN_ACT = "in_act";
const FACT_IN_NAMESPACE_SO = "in_namespace_so";
const FACT_NIX_INSTALLER_PLANNER = "nix_installer_planner";
const FACT_SENT_SIGTERM = "sent_sigterm";
// Flags
const FLAG_DETERMINATE = "--determinate";
const FLAG_PREFER_UPSTREAM_NIX = "--prefer-upstream-nix";
// Pre/post state keys
const STATE_DAEMONDIR = "DNI_DAEMONDIR";
const STATE_START_DATETIME = "DETERMINATE_NIXD_START_DATETIME";
class NixInstallerAction extends DetSysAction {
private daemonDir: string;
determinate: boolean;
platform: string;
nixPackageUrl: string | null;
@@ -48,7 +65,7 @@ class NixInstallerAction extends DetSysAction {
kvm: boolean;
githubServerUrl: string | null;
githubToken: string | null;
forceDockerShim: boolean;
forceNoSystemd: boolean;
init: string | null;
jobConclusion: string | null;
localRoot: string | null;
@@ -73,6 +90,20 @@ class NixInstallerAction extends DetSysAction {
runnerOs: string | undefined;
constructor() {
if (platform.getArchOs() === "X64-macOS") {
// Holy guacamole this is ugly
actionsCore.error(
"Determinate Nix Installer no longer supports macOS on Intel. Please migrate to Apple Silicon, and use Nix's built-in Rosetta support to build for Intel. See: https://github.com/DeterminateSystems/nix-src/issues/224",
);
const sourceTag = inputs.getStringOrUndefined("source-tag");
if (sourceTag === undefined) {
actionsCore.notice(
"Pinning the installer tag to v3.12.2 (the last version to support Intel Macs) as a temporary fallback.",
);
process.env["INPUT_SOURCE-TAG"] = "v3.12.2";
}
}
super({
name: "nix-installer",
fetchStyle: "nix-style",
@@ -81,6 +112,14 @@ class NixInstallerAction extends DetSysAction {
diagnosticsSuffix: "diagnostic",
});
if (actionsCore.getState(STATE_DAEMONDIR) !== "") {
this.daemonDir = actionsCore.getState(STATE_DAEMONDIR);
} else {
this.daemonDir = this.getTemporaryName();
mkdirSync(this.daemonDir);
actionsCore.saveState(STATE_DAEMONDIR, this.daemonDir);
}
this.determinate =
inputs.getBool("determinate") || inputs.getBool("flakehub");
this.platform = platform.getNixPlatform(platform.getArchOs());
@@ -89,7 +128,7 @@ class NixInstallerAction extends DetSysAction {
this.extraArgs = inputs.getStringOrNull("extra-args");
this.extraConf = inputs.getMultilineStringOrNull("extra-conf");
this.kvm = inputs.getBool("kvm");
this.forceDockerShim = inputs.getBool("force-docker-shim");
this.forceNoSystemd = inputs.getBool("force-no-systemd");
this.githubToken = inputs.getStringOrNull("github-token");
this.githubServerUrl = inputs.getStringOrNull("github-server-url");
this.init = inputs.getStringOrNull("init");
@@ -117,13 +156,22 @@ class NixInstallerAction extends DetSysAction {
}
async main(): Promise<void> {
actionsCore.saveState(STATE_START_DATETIME, new Date().toISOString());
await this.scienceDebugFly();
await this.detectAndForceDockerShim();
await this.detectAndForceNoSystemd();
await this.install();
}
async post(): Promise<void> {
await this.cleanupDockerShim();
await this.annotateMismatches();
try {
await this.summarizeExecution();
} catch (err: unknown) {
this.recordEvent("summarize-execution:error", {
exception: stringifyError(err),
});
}
await this.cleanupNoSystemd();
await this.reportOverall();
}
@@ -184,211 +232,37 @@ class NixInstallerAction extends DetSysAction {
}
}
// Detect if we're in a GHA runner which is Linux, doesn't have Systemd, and does have Docker.
// Detect if we're in a GHA runner which is Linux and doesn't have Systemd.
// This is a common case in self-hosted runners, providers like [Namespace](https://namespace.so/),
// and especially GitHub Enterprise Server.
async detectAndForceDockerShim(): Promise<void> {
async detectAndForceNoSystemd(): Promise<void> {
if (!this.isLinux) {
if (this.forceDockerShim) {
if (this.forceNoSystemd) {
this.forceNoSystemd = false;
actionsCore.warning(
"Ignoring force-docker-shim which is set to true, as it is only supported on Linux.",
"Ignoring force-no-systemd which is set to true, as it is only supported on Linux.",
);
this.forceDockerShim = false;
}
return;
}
if (this.isRunningInAct) {
actionsCore.debug(
"Not bothering to detect if the docker shim should be used, as it is typically incompatible with act.",
);
return;
}
actionsCore.startGroup("Detecting systemd...");
const systemdCheck = fs.statSync("/run/systemd/system", {
throwIfNoEntry: false,
});
if (systemdCheck?.isDirectory()) {
this.addFact(FACT_HAS_SYSTEMD, true);
if (this.forceDockerShim) {
actionsCore.warning(
"Systemd is detected, but ignoring it since force-docker-shim is enabled.",
);
} else {
return;
}
}
this.addFact(FACT_HAS_SYSTEMD, false);
} else {
this.addFact(FACT_HAS_SYSTEMD, false);
actionsCore.debug(
"Linux detected without systemd, testing for Docker with `docker info` as an alternative daemon supervisor.",
);
this.addFact(FACT_HAS_DOCKER, false); // Set to false here, and only in the success case do we set it to true
let exitCode;
try {
exitCode = await actionsExec.exec("docker", ["info"], {
silent: true,
listeners: {
stdout: (data: Buffer) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
stderr: (data: Buffer) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
},
});
} catch {
actionsCore.debug("Docker not detected, not enabling docker shim.");
return;
}
if (exitCode !== 0) {
if (this.forceDockerShim) {
actionsCore.warning(
"docker info check failed, but trying anyway since force-docker-shim is enabled.",
);
} else {
return;
}
}
this.addFact(FACT_HAS_DOCKER, true);
if (
!this.forceDockerShim &&
(await this.detectDockerWithMountedDockerSocket())
) {
actionsCore.debug(
"Detected a Docker container with a Docker socket mounted, not enabling docker shim.",
);
return;
}
actionsCore.startGroup(
"Enabling the Docker shim for running Nix on Linux in CI without Systemd.",
);
if (this.init !== "none") {
actionsCore.info(`Changing init from '${this.init}' to 'none'`);
this.forceNoSystemd = true;
this.init = "none";
}
if (this.planner !== "linux") {
actionsCore.info(`Changing planner from '${this.planner}' to 'linux'`);
this.planner = "linux";
}
this.forceDockerShim = true;
actionsCore.endGroup();
}
// Detect if we are running under `act` or some other system which is not using docker-in-docker,
// and instead using a mounted docker socket.
// In the case of the socket mount solution, the shim will cause issues since the given mount paths will
// equate to mount paths on the host, not mount paths to the docker container in question.
async detectDockerWithMountedDockerSocket(): Promise<boolean> {
let cgroupsBuffer;
try {
// If we are inside a docker container, the last line of `/proc/self/cgroup` should be
// 0::/docker/$SOME_ID
//
// If we are not, the line will likely be `0::/`
cgroupsBuffer = await readFile("/proc/self/cgroup", {
encoding: "utf-8",
});
} catch (e) {
actionsCore.debug(
`Did not detect \`/proc/self/cgroup\` existence, bailing on docker container ID detection:\n${e}`,
);
return false;
}
const cgroups = cgroupsBuffer.trim().split("\n");
const lastCgroup = cgroups[cgroups.length - 1];
const lastCgroupParts = lastCgroup.split(":");
const lastCgroupPath = lastCgroupParts[lastCgroupParts.length - 1];
if (!lastCgroupPath.includes("/docker/")) {
actionsCore.debug(
"Did not detect a container ID, bailing on docker.sock detection",
);
return false;
}
// We are in a docker container, now to determine if this container is visible from
// the `docker` command, and if so, if there is a `docker.socket` mounted.
const lastCgroupPathParts = lastCgroupPath.split("/");
const containerId = lastCgroupPathParts[lastCgroupPathParts.length - 1];
// If we cannot `docker inspect` this discovered container ID, we'll fall through to the `catch` below.
let stdoutBuffer = "";
let stderrBuffer = "";
let exitCode;
try {
exitCode = await actionsExec.exec("docker", ["inspect", containerId], {
silent: true,
listeners: {
stdout: (data: Buffer) => {
stdoutBuffer += data.toString("utf-8");
},
stderr: (data: Buffer) => {
stderrBuffer += data.toString("utf-8");
},
},
});
} catch (e) {
actionsCore.debug(
`Could not execute \`docker inspect ${containerId}\`, bailing on docker container inspection:\n${e}`,
);
return false;
}
if (exitCode !== 0) {
actionsCore.debug(
`Unable to inspect detected docker container with id \`${containerId}\`, bailing on container inspection (exit ${exitCode}):\n${stderrBuffer}`,
);
return false;
}
const output = JSON.parse(stdoutBuffer);
// `docker inspect $ID` prints an array containing objects.
// In our use case, we should only see 1 item in the array.
if (output.length !== 1) {
actionsCore.debug(
`Got \`docker inspect ${containerId}\` output which was not one item (was ${output.length}), bailing on docker.sock detection.`,
);
return false;
}
const item = output[0];
// On this array item we want the `Mounts` field, which is an array
// containing `{ Type, Source, Destination, Mode}`.
// We are looking for a `Destination` ending with `docker.sock`.
const mounts = item["Mounts"];
if (typeof mounts !== "object") {
actionsCore.debug(
`Got non-object in \`Mounts\` field of \`docker inspect ${containerId}\` output, bailing on docker.sock detection.`,
);
return false;
}
let foundDockerSockMount = false;
for (const mount of mounts) {
const destination = mount["Destination"];
if (typeof destination === "string") {
if (destination.endsWith("docker.sock")) {
foundDockerSockMount = true;
break;
}
}
}
return foundDockerSockMount;
}
private async executionEnvironment(): Promise<ExecuteEnvironment> {
const executionEnv: ExecuteEnvironment = {};
@@ -426,7 +300,7 @@ class NixInstallerAction extends DetSysAction {
}
if (this.nixBuildUserBase !== null) {
executionEnv.NIX_INSTALLER_NIX_BUILD_USER_ID_BASE = `${this.nixBuildUserCount}`;
executionEnv.NIX_INSTALLER_NIX_BUILD_USER_ID_BASE = `${this.nixBuildUserBase}`;
}
if (this.nixPackageUrl !== null) {
@@ -573,6 +447,8 @@ class NixInstallerAction extends DetSysAction {
if (this.extraArgs && !this.extraArgs.includes(FLAG_DETERMINATE)) {
args.push(FLAG_DETERMINATE);
}
} else {
args.push(FLAG_PREFER_UPSTREAM_NIX);
}
return args;
@@ -644,8 +520,8 @@ class NixInstallerAction extends DetSysAction {
await this.executeInstall(binaryPath);
actionsCore.endGroup();
if (this.forceDockerShim) {
await this.spawnDockerShim();
if (this.forceNoSystemd) {
await this.spawnDetached();
}
await this.setGithubPath();
@@ -655,212 +531,186 @@ class NixInstallerAction extends DetSysAction {
}
}
async spawnDockerShim(): Promise<void> {
async spawnDetached(): Promise<void> {
actionsCore.startGroup(
"Configuring the Docker shim as the Nix Daemon's process supervisor",
"Directly spawning the daemon, since systemd is not available.",
);
const images: { [key: string]: string } = {
X64: path.join(__dirname, "/../docker-shim/amd64.tar.gz"),
ARM64: path.join(__dirname, "/../docker-shim/arm64.tar.gz"),
const outputPath = path.join(this.daemonDir, "daemon.log");
const output = openSync(outputPath, "a");
const opts: SpawnOptions = {
stdio: ["ignore", output, output],
detached: true,
};
const runnerArch = process.env["RUNNER_ARCH"];
let arch;
const daemonBin = this.determinate
? "/usr/local/bin/determinate-nixd"
: "/nix/var/nix/profiles/default/bin/nix-daemon";
const daemonCliFlags = this.determinate ? ["daemon"] : [];
if (runnerArch === "X64") {
arch = "X64";
} else if (runnerArch === "ARM64") {
arch = "ARM64";
let executable: string;
let args: string[];
if (userInfo().uid === 0) {
executable = daemonBin;
args = daemonCliFlags;
} else {
throw Error("Architecture not supported in Docker shim mode.");
}
actionsCore.debug("Loading image: determinate-nix-shim:latest...");
{
const exitCode = await actionsExec.exec(
"docker",
["image", "load", "--input", images[arch]],
{
silent: true,
listeners: {
stdout: (data: Buffer) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
stderr: (data: Buffer) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
},
},
);
if (exitCode !== 0) {
throw new Error(
`Failed to build the shim image, exit code: \`${exitCode}\``,
);
}
executable = "sudo";
args = [daemonBin].concat(daemonCliFlags);
}
{
actionsCore.debug("Starting the Nix daemon through Docker...");
// Display the final command for debugging purposes
actionsCore.debug("Full daemon start command:");
actionsCore.debug(`${executable} ${args.join(" ")}`);
const candidateDirectories = [
{
dir: "/bin",
readOnly: true,
},
{
dir: "/etc",
readOnly: true,
},
{
dir: "/home",
readOnly: true,
},
{
dir: "/lib",
readOnly: true,
},
{
dir: "/lib64",
readOnly: true,
},
{
dir: "/tmp",
readOnly: false,
},
{
dir: "/usr",
readOnly: true,
},
{
dir: "/nix",
readOnly: false,
},
];
// Start the server, and wait for the socket to exist
const daemon = spawn(executable, args, opts);
const mountArguments = [];
const pidFile = path.join(this.daemonDir, "daemon.pid");
if (daemon.pid) {
await writeFile(pidFile, daemon.pid.toString());
}
for (const { dir, readOnly } of candidateDirectories) {
try {
await access(dir);
actionsCore.debug(`Will mount ${dir} in the docker shim.`);
mountArguments.push("--mount");
mountArguments.push(
`type=bind,src=${dir},dst=${dir}${readOnly ? ",readonly" : ""}`,
);
} catch {
actionsCore.debug(
`Not mounting ${dir} in the docker shim: it doesn't appear to exist.`,
);
try {
for (let i = 0; i <= 2400; i++) {
// Approximately 2 minutes
if (daemon.signalCode !== null || daemon.exitCode !== null) {
let msg: string;
if (daemon.signalCode) {
msg = `Daemon was killed by signal ${daemon.signalCode}`;
} else {
msg = `Daemon exited with code ${daemon.exitCode}`;
}
throw new Error(msg);
}
if (await this.doesTheSocketExistYet()) {
break;
}
await setTimeout(50);
}
const plausibleDeterminateOptions = [];
const plausibleDeterminateArguments = [];
if (this.determinate) {
plausibleDeterminateOptions.push("--entrypoint");
plausibleDeterminateOptions.push("/usr/local/bin/determinate-nixd");
plausibleDeterminateArguments.push("daemon");
if (!(await this.doesTheSocketExistYet())) {
throw new Error("Timed out waiting for the daemon socket to appear.");
}
} catch (error: unknown) {
this.recordEvent(EVENT_NO_SYSTEMD_SHIM_FAILED, {
error: stringifyError(error),
log: await readFile(outputPath, "utf-8"),
});
this.recordEvent(EVENT_START_DOCKER_SHIM);
const exitCode = await actionsExec.exec(
"docker",
[
"--log-level=debug",
"run",
"--detach",
"--privileged",
"--network=host",
"--userns=host",
"--pid=host",
"--restart",
"always",
"--init",
"--name",
`determinate-nix-shim-${this.getUniqueId()}-${randomUUID()}`,
]
.concat(plausibleDeterminateOptions)
.concat(mountArguments)
.concat(["determinate-nix-shim:latest"])
.concat(plausibleDeterminateArguments),
{
silent: true,
listeners: {
stdline: (data: string) => {
actionsCore.saveState("docker_shim_container_id", data.trimEnd());
},
stdout: (data: Buffer) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
stderr: (data: Buffer) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
},
},
);
if (exitCode !== 0) {
throw new Error(
`Failed to start the Nix daemon through Docker, exit code: \`${exitCode}\``,
);
}
throw error;
}
daemon.unref();
actionsCore.endGroup();
return;
}
async cleanupDockerShim(): Promise<void> {
const containerId = actionsCore.getState("docker_shim_container_id");
if (containerId !== "") {
actionsCore.startGroup("Cleaning up the Nix daemon's Docker shim");
let cleaned = false;
try {
await actionsExec.exec("docker", ["rm", "--force", containerId]);
cleaned = true;
} catch {
actionsCore.warning("failed to cleanup nix daemon container");
async doesTheSocketExistYet(): Promise<boolean> {
const socketPath = "/nix/var/nix/daemon-socket/socket";
try {
await stat(socketPath);
return true;
} catch (error: unknown) {
// eslint-disable-next-line no-undef
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
actionsCore.debug(`Socket '${socketPath}' does not exist yet`);
return false;
}
if (!cleaned) {
actionsCore.info("trying to pkill the container's shim process");
try {
await actionsExec.exec("pkill", [containerId]);
cleaned = true;
} catch {
actionsCore.warning(
"failed to forcibly kill the container's shim process",
);
}
}
if (cleaned) {
this.recordEvent(EVENT_CLEAN_UP_DOCKER_SHIM);
} else {
actionsCore.warning(
"Giving up on cleaning up the nix daemon container",
);
}
actionsCore.endGroup();
actionsCore.warning(
`Error waiting for the Nix Daemon socket: ${stringifyError(error)}`,
);
this.recordEvent("shim:wait-for-socket", {
exception: stringifyError(error),
});
throw error;
}
}
async summarizeExecution(): Promise<void> {
const startDate = new Date(actionsCore.getState(STATE_START_DATETIME));
const { events, hasMismatches } = await getRecentEvents(startDate);
await this.reportPassFailCount(events);
const mermaidSummary = makeMermaidReport(events);
const failureSummary = await summarizeFailures(events);
const showResults = mermaidSummary || failureSummary || hasMismatches;
if (showResults) {
actionsCore.summary.addRaw(
`## ![](https://avatars.githubusercontent.com/u/80991770?s=30) Determinate Nix build summary`,
true,
);
actionsCore.summary.addRaw("\n", true);
}
if (mermaidSummary !== undefined) {
actionsCore.summary.addRaw(mermaidSummary, true);
actionsCore.summary.addRaw("\n", true);
}
if (hasMismatches) {
actionsCore.summary.addRaw(
[
"> [!TIP]",
"> Some derivations failed to build due to the hash in the Nix expression being outdated.",
"> To find out how to automatically update your Nix expressions in GitHub Actions, see [our guide](https://docs.determinate.systems/guides/automatically-fix-hashes-in-github-actions).",
"",
].join("\n"),
true,
);
}
if (failureSummary !== undefined) {
for (const logLine of failureSummary.logLines) {
actionsCore.info(logLine);
}
actionsCore.summary.addRaw(failureSummary.markdownLines.join("\n"), true);
actionsCore.summary.addRaw("\n", true);
}
if (showResults) {
actionsCore.summary.addRaw("---", true);
actionsCore.summary.addRaw(
`_Please let us know what you think about this summary on the [Determinate Systems Discord](https://determinate.systems/discord)._`,
true,
);
actionsCore.summary.addRaw("\n", true);
await actionsCore.summary.write();
}
}
async reportPassFailCount(events: DEvent[]): Promise<void> {
let built = 0;
let failed = 0;
let unknown = 0;
for (const event of events) {
switch (event.c) {
case "BuiltPathResponseEventV1":
built++;
break;
case "BuildFailureResponseEventV1":
failed++;
break;
default:
unknown++;
}
}
this.addFact("nix_builds_succeeded", built);
this.addFact("nix_builds_failed", failed);
this.addFact("nix_builds_unknown_event", unknown);
}
async setGithubPath(): Promise<void> {
// Interim versions of the `nix-installer` crate may have already manipulated `$GITHUB_PATH`, as root even! Accessing that will be an error.
try {
@@ -885,22 +735,53 @@ class NixInstallerAction extends DetSysAction {
}
async flakehubLogin(): Promise<void> {
if (
this.recordEvent(EVENT_LOGIN_START);
const canLogin =
process.env["ACTIONS_ID_TOKEN_REQUEST_URL"] &&
process.env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"]
) {
actionsCore.startGroup("Logging in to FlakeHub");
this.recordEvent(EVENT_LOGIN_TO_FLAKEHUB);
try {
await actionsExec.exec(`determinate-nixd`, ["login", "github-action"]);
} catch (e: unknown) {
actionsCore.warning(`FlakeHub Login failure: ${stringifyError(e)}`);
this.recordEvent("flakehub-login:failure", {
exception: stringifyError(e),
});
process.env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"];
if (!canLogin) {
const pr = github.context.payload.pull_request;
const base = pr?.base?.repo?.full_name;
const head = pr?.head?.repo?.full_name;
if (pr && base !== head) {
this.recordEvent(EVENT_LOGIN_FAILURE, { reason: "fork" });
this.recordEvent(EVENT_LOGIN_END);
actionsCore.info(
`FlakeHub is disabled because this is a fork. GitHub Actions does not allow OIDC authentication from forked repositories ("${head}" is not from the same repository as "${base}").`,
);
return;
}
actionsCore.endGroup();
this.recordEvent(EVENT_LOGIN_FAILURE, { reason: "not-configured" });
this.recordEvent(EVENT_LOGIN_END);
actionsCore.info(
"FlakeHub is disabled because the workflow is misconfigured. Please make sure that `id-token: write` and `contents: read` are set for this step's (or job's) permissions so that GitHub Actions provides OIDC token endpoints.",
);
actionsCore.info(
`For more information, see https://docs.determinate.systems/guides/github-actions/#nix-installer-action`,
);
return;
}
actionsCore.startGroup("Logging in to FlakeHub");
try {
await actionsExec.exec(`determinate-nixd`, ["login", "github-action"]);
this.recordEvent(EVENT_LOGIN_SUCCESS);
} catch (e: unknown) {
actionsCore.warning(`FlakeHub Login failure: ${stringifyError(e)}`);
this.recordEvent(EVENT_LOGIN_FAILURE, {
reason: "failed",
exception: stringifyError(e),
});
}
this.recordEvent(EVENT_LOGIN_END);
actionsCore.endGroup();
}
async executeUninstall(): Promise<number> {
@@ -928,9 +809,36 @@ class NixInstallerAction extends DetSysAction {
try {
await access(receiptPath);
// There is a /nix/receipt.json
actionsCore.info(
"\u001b[32m Nix is already installed: found /nix/receipt.json \u001b[33m",
);
return true;
} catch {
// No /nix/receipt.json
}
try {
const exitCode = await actionsExec.exec("nix", ["--version"], {});
if (exitCode === 0) {
actionsCore.info(
"\u001b[32m Nix is already installed: `nix --version` exited 0 \u001b[33m",
);
// Working existing installation of `nix` available, possibly a self-hosted runner
return true;
}
} catch {
// nix --version was not successful
}
return false;
}
private async canAccessKvm(): Promise<boolean> {
try {
await access("/dev/kvm", fs.constants.R_OK | fs.constants.W_OK);
return true;
} catch {
return false;
}
}
@@ -941,6 +849,12 @@ class NixInstallerAction extends DetSysAction {
const isRoot = currentUser.uid === 0;
const maybeSudo = isRoot ? "" : "sudo";
// First check to see whether the current user can open the KVM device node
if (await this.canAccessKvm()) {
return true;
}
// The current user can't access KVM, so try adding a udev rule to allow access to all users and groups
const kvmRules = "/etc/udev/rules.d/99-determinate-nix-installer-kvm.rules";
try {
const writeFileExitCode = await actionsExec.exec(
@@ -1040,6 +954,47 @@ class NixInstallerAction extends DetSysAction {
}
}
async cleanupNoSystemd(): Promise<void> {
if (!this.forceNoSystemd) {
// Nothing to do, we didn't use the double fork
return;
}
const pidFile = path.join(this.daemonDir, "daemon.pid");
const pid = parseInt(await readFile(pidFile, { encoding: "ascii" }));
actionsCore.debug(`found daemon pid: ${pid}`);
if (!pid) {
throw new Error("the daemon did not start successfully");
}
actionsCore.debug(`killing daemon process ${pid}`);
try {
// Repeatedly signal 0 the daemon to test if it is up.
// If it exits, kill will raise an exception which breaks us out of this control flow and skips the sigterm.
// If it doesn't exit in 30s, we SIGTERM it.
for (let i = 0; i < 30 * 10; i++) {
process.kill(pid, 0);
await setTimeout(100);
}
this.addFact(FACT_SENT_SIGTERM, true);
actionsCore.info(`Sending the daemon a SIGTERM`);
process.kill(pid, "SIGTERM");
} catch {
// Perfectly normal to get an exception here, because the process shut down.
}
if (actionsCore.isDebug()) {
actionsCore.info("Entire log:");
const entireLog = await readFile(
path.join(this.daemonDir, "daemon.log"),
"utf-8",
);
actionsCore.info(entireLog);
}
}
async reportOverall(): Promise<void> {
try {
this.recordEvent(EVENT_CONCLUDE_JOB, {
@@ -1061,6 +1016,40 @@ class NixInstallerAction extends DetSysAction {
);
}
}
private async annotateMismatches(): Promise<void> {
if (!this.determinate) {
return;
}
const active = this.getFeature(FEAT_ANNOTATIONS)?.variant;
if (!active) {
actionsCore.debug("The annotations feature is disabled for this run");
return;
}
try {
actionsCore.debug("Getting hash fixes from determinate-nixd");
const since = actionsCore.getState(STATE_START_DATETIME);
const mismatches = await getFixHashes(since);
if (mismatches.version !== "v1") {
throw new Error(
`Unsupported \`determinate-nixd fix hashes\` output (got ${mismatches.version}, expected v1)`,
);
}
actionsCore.debug("Annotating mismatches");
const count = annotateMismatches(mismatches);
this.recordEvent(EVENT_FOD_ANNOTATE, { count });
} catch (error) {
// Don't hard fail the action if something exploded; this feature is only a nice-to-have
actionsCore.warning(`Could not consume hash mismatch events: ${error}`);
this.recordEvent("annotation-mismatch-execution:error", {
exception: stringifyError(error),
});
}
}
}
type ExecuteEnvironment = {
+207
View File
@@ -0,0 +1,207 @@
import { mermaidify, makeMermaidReport } from "./mermaid.js";
import { DEvent, parseEvents } from "./events.js";
import { expect, test } from "vitest";
/* eslint-disable @typescript-eslint/no-non-null-assertion */
function generateEvents(count: number): DEvent[] {
const events: DEvent[] = [];
for (let i = 0; i < count; i++) {
events.push({
v: "1",
c: "BuiltPathResponseEventV1",
drv: `/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-${i}.drv`,
timing: {
startTime: new Date(i * 1000),
durationSeconds: i,
},
});
}
return events;
}
test("Empty event list returns no report", () => {
const report = makeMermaidReport([]);
expect(report).toBeUndefined();
});
test("Create a very large report doc and make sure it is small enough", () => {
const report = makeMermaidReport(generateEvents(2500))!;
// Assert the `.drv` suffix was pruned (1 reference = the NOTE at the end)
expect(report.match(/\.drv/g)!.length).equals(1);
// Assert the `/nix/store` prefix was pruned (1 reference = the NOTE at the end)
expect(report.match(/\/nix\/store\//g)!.length).equals(1);
// Assert that some events were pruned
expect(report.match(/dep-/g)!.length).lessThan(2500);
expect(report.match(/dep-/g)!.length).greaterThan(1500);
expect(report).toContain("suffix, and builds that took less than ");
expect(report.length).lessThan(50200);
expect(report.length).greaterThan(49000);
});
test("Create a medium large report doc and make sure it is small enough", () => {
const eventCount = 675;
const report = makeMermaidReport(generateEvents(eventCount))!;
// Assert the `.drv` suffix was pruned (1 reference = the NOTE at the end)
expect(report.match(/\.drv/g)!.length).equals(1);
// Assert the `/nix/store` prefix was pruned (1 reference = the NOTE at the end)
expect(report.match(/\/nix\/store\//g)!.length).equals(1);
// Assert that no lines were pruned
expect(report.match(/dep-/g)!.length).toStrictEqual(eventCount);
expect(report).toContain(
"suffixes have been removed to make the graph small enough to render",
);
expect(report.length).lessThan(50200);
expect(report.length).greaterThan(18000);
});
test("Create a small report doc and make sure it isn't pruned", () => {
const report = makeMermaidReport(generateEvents(100))!;
// Assert 100 events have the `.drv` suffix, ie: were not pruned
expect(report.match(/\.drv/g)!.length).equals(100);
// Assert 100 events have the `.drv` suffix, ie: were not pruned
expect(report.match(/\/nix\/store\//g)!.length).equals(100);
expect(report.length).lessThan(50000);
});
test("Generate a really big report and shrink it", () => {
const events = generateEvents(1000);
const originalLength = mermaidify(events, -1)!.length;
const limitedLengthZero = mermaidify(events, 0)!.length;
const limitedLengthOne = mermaidify(events, 1)!.length;
const limitedLengthTwo = mermaidify(events, 2)!.length;
expect(originalLength).greaterThan(limitedLengthZero);
expect(limitedLengthZero).greaterThan(limitedLengthOne);
expect(limitedLengthOne).greaterThan(limitedLengthTwo);
});
test("Generate a rough report of various length", () => {
const { events } = parseEvents([
{
v: "1",
c: "BuiltPathResponseEventV1",
drv: "/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-0.drv",
outputs: ["/nix/store/qwlgz5da3pfb53gqpgdmazaj9jczrnly-dep-0"],
timing: {
startTime: "2025-04-11T14:38:02Z",
stopTime: "2025-04-11T14:38:05Z",
durationSeconds: 0,
},
},
{
v: "1",
c: "BuiltPathResponseEventV1",
drv: "/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv",
outputs: ["/nix/store/qwlgz5da3pfb53gqpgdmazaj9jczrnly-dep-1"],
timing: {
startTime: "2025-04-11T14:38:02Z",
stopTime: "2025-04-11T14:38:05Z",
durationSeconds: 1,
},
},
{
v: "1",
c: "BuiltPathResponseEventV1",
drv: "/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-2.drv",
outputs: ["/nix/store/qwlgz5da3pfb53gqpgdmazaj9jczrnly-dep-2"],
timing: {
startTime: "2025-04-11T14:38:02Z",
stopTime: "2025-04-11T14:38:05Z",
durationSeconds: 2,
},
},
{
v: "1",
c: "BuildFailureResponseEventV1",
drv: "/nix/store/ykvbksjqrza2zpj6nkbycrdfwgfdpr8g-hash-mismatch-md5-base16.drv",
timing: {
startTime: "2025-04-11T14:38:05Z",
stopTime: "2025-04-11T14:38:09Z",
durationSeconds: 4,
},
},
]);
expect(mermaidify(events, -1)).toStrictEqual(`\`\`\`mermaid
gantt
dateFormat X
axisFormat %Mm%Ss
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-0.drv (0s):d, 0, 0s
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-1.drv (1s):d, 0, 1s
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-2.drv (2s):d, 0, 2s
/nix/store/ykvbksjqrza2zpj6nkbycrdfwgfdpr8g-hash-mismatch-md5-base16.drv (4s):crit, 3, 4s
\`\`\``);
expect(mermaidify(events, 0)).toStrictEqual(`\`\`\`mermaid
gantt
dateFormat X
axisFormat %Mm%Ss
dep-0 (0s):d, 0, 0s
dep-1 (1s):d, 0, 1s
dep-2 (2s):d, 0, 2s
hash-mismatch-md5-base16 (4s):crit, 3, 4s
\`\`\``);
expect(mermaidify(events, 1)).toStrictEqual(`\`\`\`mermaid
gantt
dateFormat X
axisFormat %Mm%Ss
dep-1 (1s):d, 0, 1s
dep-2 (2s):d, 0, 2s
hash-mismatch-md5-base16 (4s):crit, 3, 4s
\`\`\``);
});
test("Generate a really big report and shrink it", () => {
const events = generateEvents(1000);
const originalLength = mermaidify(events, -1)!.length;
const limitedLengthZero = mermaidify(events, 0)!.length;
const limitedLengthOne = mermaidify(events, 1)!.length;
const limitedLengthTwo = mermaidify(events, 2)!.length;
expect(originalLength).greaterThan(limitedLengthZero);
expect(limitedLengthZero).greaterThan(limitedLengthOne);
expect(limitedLengthOne).greaterThan(limitedLengthTwo);
});
test("Really long builds get multi-unit timestamps", () => {
const { events } = parseEvents([
{
v: "1",
c: "BuiltPathResponseEventV1",
drv: "/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-0.drv",
outputs: ["/nix/store/qwlgz5da3pfb53gqpgdmazaj9jczrnly-dep-0"],
timing: {
startTime: "2025-04-11T14:38:02Z",
stopTime: "2026-05-14T13:32:01Z",
durationSeconds: 34383239,
},
},
]);
expect(mermaidify(events, -1)).toStrictEqual(`\`\`\`mermaid
gantt
dateFormat X
axisFormat %Mm%Ss
/nix/store/rz9hrpay90sjrid5hx3x8v606ji679xa-dep-0.drv (573053m59s):d, 0, 34383239s
\`\`\``);
});
+96
View File
@@ -0,0 +1,96 @@
import { DEvent } from "./events.js";
import { truncateDerivation } from "./util.js";
export function makeMermaidReport(events: DEvent[]): string | undefined {
// # 50k is the max: https://github.com/mermaid-js/mermaid/blob/c269dc822c528e1afbde34e18a1cad03d972d4fe/src/defaultConfig.js#L55
const maxLength = 49900;
let mermaid = "";
let pruneLevel = -2;
do {
pruneLevel += 1;
mermaid = mermaidify(events, pruneLevel) ?? "";
} while (mermaid.length > maxLength);
if (!mermaid) {
return undefined;
}
const lines = [
"<details open><summary><strong>Build timeline</strong> :hourglass_flowing_sand:</summary>",
"", // load bearing whitespace, deleting it breaks the details expander / markdown
mermaid,
"", // load bearing whitespace, deleting it breaks the details expander / markdown
];
if (pruneLevel === 0) {
lines.push("> [!NOTE]");
lines.push(
"> `/nix/store/[hash]` and the `.drv` suffixes have been removed to make the graph small enough to render.",
);
} else if (pruneLevel > 0) {
lines.push("> [!NOTE]");
lines.push(
`> \`/nix/store/[hash]\`, the \`.drv\` suffix, and builds that took less than ${formatDuration(pruneLevel)} have been removed to make the graph small enough to render.`,
);
}
lines.push(""); // load bearing whitespace, deleting it breaks the details expander / markdown
lines.push("</details>");
return lines.join("\n");
}
export function mermaidify(
allEvents: DEvent[],
pruneLevel: number,
): string | undefined {
const events = allEvents
.filter(
(event) =>
event.c === "BuiltPathResponseEventV1" ||
event.c === "BuildFailureResponseEventV1",
)
.sort(
(a, b) => a.timing.startTime.getTime() - b.timing.startTime.getTime(),
);
const firstEvent = events.at(0);
if (firstEvent === undefined) {
return undefined;
}
const zeroMoment = firstEvent.timing.startTime.getTime();
const lines = [
"```mermaid",
"gantt",
" dateFormat X",
" axisFormat %Mm%Ss",
];
for (const event of events) {
const duration = event.timing.durationSeconds;
if (duration < pruneLevel) {
continue;
}
const label = pruneLevel >= 0 ? truncateDerivation(event.drv) : event.drv;
const tag = event.c === "BuildFailureResponseEventV1" ? "crit" : "d";
const relativeStartTime =
(event.timing.startTime.getTime() - zeroMoment) / 1000;
lines.push(
`${label} (${formatDuration(duration)}):${tag}, ${relativeStartTime}, ${duration}s`,
);
}
lines.push("```");
return lines.join("\n");
}
function formatDuration(duration: number): string {
const durSeconds = duration % 60;
const durMinutes = (duration - durSeconds) / 60;
return `${durMinutes > 0 ? `${durMinutes}m` : ""}${durSeconds}s`;
}
+3
View File
@@ -0,0 +1,3 @@
export function truncateDerivation(drv: string): string {
return drv.replace(/^\/nix\/store\/[a-z0-9]+-/, "").replace(/\.drv$/, "");
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"target": "ES2022" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"module": "Node16",
"moduleResolution": "NodeNext",
"outDir": "./dist",
@@ -11,5 +11,5 @@
"resolveJsonModule": true,
"declaration": true
},
"exclude": ["node_modules", "**/*.test.ts", "dist"]
"exclude": ["node_modules", "dist"]
}