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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
with:
node-version: ${{ matrix.node }}
package-manager-cache: false
- name: Use the locked npm CLI
run: npm install --global npm@11.19.1
- name: Install locked dependencies
run: npm ci --no-audit --no-fund
- name: Build
Expand All @@ -55,6 +57,8 @@ jobs:
with:
node-version: 24.20.0
package-manager-cache: false
- name: Use the locked npm CLI
run: npm install --global npm@11.19.1
- name: Install locked dependencies
run: npm ci --no-audit --no-fund
- name: Lint
Expand Down
70 changes: 68 additions & 2 deletions .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,47 @@ jobs:
version="${tag#v}"
asset_name="testfold-${version}.tgz"
release_json="${RUNNER_TEMP}/release.json"
ref_json="${RUNNER_TEMP}/tag-ref.json"
tag_json="${RUNNER_TEMP}/annotated-tag.json"
compare_json="${RUNNER_TEMP}/compare.json"
tarball="${RUNNER_TEMP}/${asset_name}"
manifest="${RUNNER_TEMP}/package.json"

parse_registry_url() {
node -e '
const parsed = JSON.parse(process.argv[1]);
const values = Array.isArray(parsed) ? parsed : [parsed];
if (values.length !== 1 || typeof values[0] !== "string" || !values[0].startsWith("https://")) throw new Error("registry tarball URL is invalid");
process.stdout.write(values[0]);
' "$1"
}

gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${tag}" > "${ref_json}"
# shellcheck disable=SC2016
annotated_tag_sha="$(node -e '
const fs = require("node:fs");
const ref = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
if (ref.object?.type !== "tag" || !/^[0-9a-f]{40}$/.test(ref.object?.sha)) throw new Error("release tag must be annotated");
process.stdout.write(ref.object.sha);
' "${ref_json}")"
gh api "repos/${GITHUB_REPOSITORY}/git/tags/${annotated_tag_sha}" > "${tag_json}"
# shellcheck disable=SC2016
tag_commit="$(node -e '
const fs = require("node:fs");
const [file, expectedTag] = process.argv.slice(1);
const tagObject = JSON.parse(fs.readFileSync(file, "utf8"));
if (tagObject.tag !== expectedTag || tagObject.object?.type !== "commit" || !/^[0-9a-f]{40}$/.test(tagObject.object?.sha)) throw new Error("annotated tag identity is invalid");
process.stdout.write(tagObject.object.sha);
' "${tag_json}" "${tag}")"
gh api "repos/${GITHUB_REPOSITORY}/compare/${tag_commit}...${GITHUB_SHA}" > "${compare_json}"
# shellcheck disable=SC2016
node -e '
const fs = require("node:fs");
const [file, expectedBase] = process.argv.slice(1);
const comparison = JSON.parse(fs.readFileSync(file, "utf8"));
if (!["ahead", "identical"].includes(comparison.status) || comparison.merge_base_commit?.sha !== expectedBase) throw new Error("tag commit is not contained in the dispatched revision");
' "${compare_json}" "${tag_commit}"

gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${tag}" > "${release_json}"
asset_url="$(
# The JavaScript template literal is intentionally protected from shell expansion.
Expand Down Expand Up @@ -107,10 +145,38 @@ jobs:
if (
manifest.name !== "testfold" ||
manifest.version !== expectedVersion ||
manifest.repository?.url !== expectedRepository
manifest.repository?.url !== expectedRepository ||
manifest.engines?.node !== ">=22.18.0" ||
manifest.publishConfig?.access !== "public"
) {
throw new Error("packed package identity is invalid");
}
' "${manifest}" "${version}" "git+https://github.com/${GITHUB_REPOSITORY}.git"

npm publish --access public "${asset_url}"
if existing_response="$(npm view "testfold@${version}" dist.tarball --json 2>/dev/null)"; then
registry_url="$(parse_registry_url "${existing_response}")"
registry_tarball="${RUNNER_TEMP}/registry-preflight.tgz"
curl --fail --location --proto '=https' --tlsv1.2 --output "${registry_tarball}" "${registry_url}"
registry_sha256="$(shasum -a 256 "${registry_tarball}" | cut -d ' ' -f 1)"
test "${registry_sha256}" = "${expected_sha256}"
echo "testfold@${version} already contains the accepted bytes; skipping"
else
npm publish --access public "${tarball}"
fi

registry_response=""
for attempt in {1..24}; do
if registry_response="$(npm view "testfold@${version}" dist.tarball --json 2>/dev/null)"; then
break
fi
if [[ "${attempt}" = 24 ]]; then
echo "testfold@${version} did not become visible in the registry" >&2
exit 1
fi
sleep 5
done
registry_url="$(parse_registry_url "${registry_response}")"
registry_tarball="${RUNNER_TEMP}/registry-final.tgz"
curl --fail --location --proto '=https' --tlsv1.2 --output "${registry_tarball}" "${registry_url}"
registry_sha256="$(shasum -a 256 "${registry_tarball}" | cut -d ' ' -f 1)"
test "${registry_sha256}" = "${expected_sha256}"
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.20.0
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.20.0
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ dist/
node_modules/
test-results/
claude-temp-files/
agent_temp_files_local/
moira-ws/
summary.json
tests/fixtures/summary.json
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Notable changes to Testfold are documented here. The project follows Semantic Ve

## Unreleased

## 0.4.2 — 2026-09-06

- Update the full dependency tree, including ESLint 10, Jest 30, TypeScript 6, and Zod 4, while preserving the Node.js 22.18.0 runtime contract.
- Remove all reported npm audit vulnerabilities and replace the deprecated transitive `glob` release used by the coverage toolchain.
- Pin local development and continuous integration to npm 11.19.1, with Node.js 24.20.0 as the repository default and Node.js 22/24 compatibility coverage.
- Publish only the downloaded, SHA-256-verified GitHub Release asset and verify the exact registry bytes before and after retry-safe trusted publication.

## 0.4.1 — 2026-09-04

- Make the CLI `--no-parallel`, `--parallel`, and `--fail-fast` flags override execution policy for the current run while preserving config defaults when no override is supplied.
Expand Down
10 changes: 5 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ npm-модуль для унифицированного запуска тест

## Tech Stack

- **Runtime:** Node.js 22.18.0+
- **Runtime:** Node.js 22.18.0+; repository default 24.20.0
- **Language:** TypeScript (strict mode)
- **Package Manager:** npm
- **Testing:** Jest
- **Validation:** Zod
- **Package Manager:** npm 11.19.1
- **Testing:** Jest 30
- **Validation:** Zod 4

## Источники паттернов

Expand All @@ -35,7 +35,7 @@ npm-модуль для унифицированного запуска тест
### Quick Start

```bash
npm install
npm ci
npm run dev
```

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Testfold accepts focused bug fixes, tests, documentation improvements, and compa

## Development setup

Use Node.js 22.18.0 or newer and npm 10 or newer:
Node.js 22.18.0 and newer are supported. Use the repository default Node.js 24.20.0 from `.nvmrc` or `.node-version` and the npm 11.19.1 version declared by `packageManager`:

```sh
git clone https://github.com/witqq/testfold.git
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ Use `--dry-run` to preview resolved commands without execution.

## Development and releases

Use Node.js 22.18.0 or newer. Install the locked dependencies and run the complete local gate:
Node.js 22.18.0 and newer are supported. Local development is pinned to Node.js 24.20.0 through `.nvmrc` and `.node-version`, while `packageManager` pins npm 11.19.1. Install the locked dependencies and run the complete local gate:

```bash
npm ci --no-audit --no-fund
Expand Down
6 changes: 3 additions & 3 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Protect `master` after the CI workflow exists. Require the Node.js matrix and re

## Prepare the candidate once

Work on a clean feature branch with the release version already set in `package.json` and `package-lock.json`. Update [CHANGELOG.md](../CHANGELOG.md), then run:
Work on a clean feature branch with the release version already set in `package.json` and `package-lock.json`. Use Node.js 24.20.0 and npm 11.19.1 as pinned by the repository. Update [CHANGELOG.md](../CHANGELOG.md), then run:

```sh
npm ci --no-audit --no-fund
Expand Down Expand Up @@ -60,9 +60,9 @@ gh run watch "<databaseId>" --exit-status
npm view testfold dist-tags version --json
```

The workflow requires exactly one uploaded release asset named `testfold-VERSION.tgz`, matches GitHub's asset digest to the accepted SHA-256, downloads over verified HTTPS, recomputes SHA-256, verifies package name/version/repository, and publishes the asset URL with npm OIDC. It performs no source checkout or build.
The workflow requires an annotated tag contained in the dispatched revision and exactly one uploaded release asset named `testfold-VERSION.tgz`. It matches GitHub's asset digest to the accepted SHA-256, downloads over verified HTTPS, recomputes SHA-256, verifies package identity, and publishes that local verified tarball with npm OIDC. It performs no source checkout or build. A retry first accepts an existing npm version only when its registry tarball has the same SHA-256; every successful run polls the registry and verifies the final bytes again.

The release is complete only when the workflow succeeds and npm reports both `latest` and `version` as the released version. Do not publish locally or add an npm token as a fallback.
The release is complete only when the workflow succeeds, npm reports both `latest` and `version` as the released version, and the registry tarball digest matches the GitHub Release asset. Do not publish locally or add an npm token as a fallback.

## Failure handling

Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default {
],
},
testMatch: ['**/tests/**/*.test.ts'],
modulePathIgnorePatterns: ['<rootDir>/agent_temp_files_local/', '<rootDir>/test-results/'],
testTimeout: 30000,
verbose: true,
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts'],
Expand Down
Loading