diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 000000000..8c1f1269a --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,16 @@ +--- +# Runner labels actionlint has no way to know about. +# +# actionlint checks every `runs-on` against the list of GitHub-hosted labels it +# ships with, so the Blacksmith machines pr.yml and release.yml ask for read as +# typos and any change to a workflow comes back with an error against each one. +# +# Nothing in this repository runs actionlint as part of a check. This file is +# here for the editors and review tooling that do run it, and because a label +# that has to be spelled exactly is worth writing down in one place. +# +# Keep in step with the `runs-on` lines in .github/workflows/. +self-hosted-runner: + labels: + - blacksmith-4vcpu-ubuntu-2404 + - blacksmith-8vcpu-ubuntu-2404 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 084fcb613..5db16a843 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,16 +53,138 @@ concurrency: group: release cancel-in-progress: false -# Nothing is granted by default; the job below asks for the two scopes it -# needs and no more. +# Nothing is granted by default; each job below asks for the scopes it needs +# and no more. permissions: {} +# The checks pr.yml already ran, run again against main, and then publish. +# +# Re-running them is not belt and braces. The main ruleset does not require a +# pull request to be up to date with main before it merges, so two branches +# that were each green on their own can still break main together, and a +# release is the first thing after that which anyone would notice. +# +# They are separate jobs from the publish for two reasons. One is the runner: +# the checks want the Blacksmith machines pr.yml uses, and the publish wants a +# GitHub-hosted one (see the note on the release job). The other is that four +# jobs cost the slowest of them rather than the sum, and as one job the release +# was spending most of its wall clock running checks end to end. +# +# The check names are deliberately not the bare "Lint"/"Test"/"Build"/"Pack" +# pr.yml uses. Those four are the required status checks in the main ruleset, +# and a ruleset matches a check by name whatever workflow posted it, so a +# release dispatched from a branch with an open pull request would otherwise +# report into that pull request's gate. +# +# Spelled out rather than calling `pnpm check`, because that starts with +# `pnpm fmt`, which rewrites files instead of reporting on them. Keep these +# jobs and the ones in pr.yml in step with each other. jobs: + lint: + name: Release lint + runs-on: blacksmith-4vcpu-ubuntu-2404 + permissions: + contents: read + steps: + # Actions are pinned to commit SHAs rather than tags throughout this + # workflow. A tag can be moved to point at new code, which is how the + # tj-actions/changed-files compromise reached the repositories using it; + # a SHA cannot. The trailing comment is the tag each SHA was resolved + # from, so a bump is still readable in review. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: .nvmrc + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + with: + version: 11.17.0 + cache: true + - run: pnpm install --frozen-lockfile + - run: pnpm lint + - run: pnpm fta + + test: + name: Release test + # The same runner pr.yml gives the suite, kept in step with it rather than + # sized on its own. Since vitest.config.ts stopped isolating every test + # file the suite is no longer the long pole here, so if these two ever want + # tuning, measure before paying for the larger machine in either file. + runs-on: blacksmith-8vcpu-ubuntu-2404 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: .nvmrc + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + with: + version: 11.17.0 + cache: true + - run: pnpm install --frozen-lockfile + - run: pnpm test:coverage + + build: + name: Release build + runs-on: blacksmith-4vcpu-ubuntu-2404 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: .nvmrc + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + with: + version: 11.17.0 + cache: true + - run: pnpm install --frozen-lockfile + - run: pnpm build:check + # Extracts the examples out of docs/, type checks and lints them. Not one + # of pr.yml's jobs; a release is where a documented example that no + # longer compiles would otherwise first be noticed. + - run: pnpm examples:check + + pack: + name: Release pack + runs-on: blacksmith-4vcpu-ubuntu-2404 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: .nvmrc + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + with: + version: 11.17.0 + cache: true + - run: pnpm install --frozen-lockfile + # The same check pr.yml runs as Pack: builds the tarball, installs it + # into a throwaway project and imports every export subpath. The last + # gate before a broken dist/ reaches the registry. + - run: pnpm verify:pack + release: name: Release - # A GitHub-hosted runner rather than the Blacksmith ones pr.yml uses. The - # release path is the one place worth keeping third parties out of, and - # this job runs a few times a month, so the speed is not worth the reach. + # Nothing runs here until every check above is green. They check out the + # same commit this one publishes, so a green check is a statement about the + # tarball semantic-release is about to build. + needs: [lint, test, build, pack] + + # A GitHub-hosted runner rather than the Blacksmith ones the checks use. + # This is the job that mints the npm publish credential, and the publish + # path is the one place worth keeping third parties out of. The checks hold + # no credential and produce nothing this job consumes, so they can run + # wherever is fastest without that reach following them here. runs-on: ubuntu-latest # What the npm trusted publisher is scoped to, and where any approval @@ -78,11 +200,6 @@ jobs: id-token: write steps: - # Actions are pinned to commit SHAs rather than tags throughout this - # workflow. A tag can be moved to point at new code, which is how the - # tj-actions/changed-files compromise reached the repositories using it; - # a SHA cannot. The trailing comment is the tag each SHA was resolved - # from, so a bump is still readable in review. - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: # semantic-release works out the next version by reading the @@ -104,30 +221,11 @@ jobs: - run: pnpm install --frozen-lockfile - # The checks pr.yml already ran, run again against main. - # - # This is not belt and braces. The main ruleset does not require a pull - # request to be up to date with main before it merges, so two branches - # that were each green on their own can still break main together, and - # a release is the first thing after that which anyone would notice. - # - # Spelled out rather than calling `pnpm check`, because that starts with - # `pnpm fmt`, which rewrites files instead of reporting on them. Keep - # this list and the jobs in pr.yml in step with each other. - - run: pnpm lint - - run: pnpm fta - - run: pnpm build:check - - run: pnpm examples:check - - run: pnpm test:coverage - - # The same check pr.yml runs as Pack: builds the tarball, installs it - # into a throwaway project and imports every export subpath. The last - # gate before a broken dist/ reaches the registry. - - run: pnpm verify:pack - # Decides the version, publishes to npm, tags and writes the GitHub # Release. Does nothing at all if the commits since the last tag are - # only chore/test/docs. See .releaserc.yaml. + # only chore/test/docs. See .releaserc.yaml. The tarball is built here by + # prepack, from this checkout, rather than carried over from the pack + # check — that check proves the build works, it does not hand anything on. - run: pnpm exec semantic-release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/vitest.config.ts b/vitest.config.ts index e32f50a36..c58079b29 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,6 +9,23 @@ export default defineConfig({ }, test: { environment: "node", + // Test files share a worker rather than getting a fresh one each. The suite + // is dominated by importing modules, not by running tests: with a worker + // per file the same @aws-sdk clients are parsed hundreds of times over, and + // that import cost was roughly five times the time the tests themselves + // took. Sharing the worker imports them once and the cost all but vanishes. + // + // What makes it safe is what the simulator is for. State lives on a SimAws + // instance a test makes and drops, not in module scope, so a file leaves + // nothing behind for the next one to find. Nothing in the suite calls + // vi.mock either, which is the usual reason a shared module registry goes + // wrong. The whole suite passes in a single process, in file order and + // shuffled, which is the strongest form of that claim available. + // + // Turn this back on if a test ever has to reach for module-level state — + // and prefer changing the test, because a simulator that cannot survive a + // shared registry is one its users cannot either. + isolate: false, typecheck: { tsconfig: "./tsconfig.json", },