machine-bridge-mcp 1.2.7 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/CONTRIBUTING.md +26 -19
- package/README.md +13 -2
- package/browser-extension/manifest.json +2 -2
- package/docs/ARCHITECTURE.md +4 -2
- package/docs/AUDIT.md +12 -0
- package/docs/ENGINEERING.md +2 -2
- package/docs/PROJECT_STANDARDS.md +9 -5
- package/docs/RELEASING.md +64 -32
- package/docs/TESTING.md +4 -1
- package/package.json +11 -6
- package/scripts/github-push.mjs +49 -0
- package/scripts/github-release.mjs +15 -8
- package/scripts/local-release-acceptance.mjs +136 -0
- package/scripts/release-acceptance.mjs +169 -0
- package/scripts/release-impact-check.mjs +19 -3
- package/src/worker/index.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.8 - 2026-07-17
|
|
4
|
+
|
|
5
|
+
### Owner-tested release gate and dependency workflow repair
|
|
6
|
+
|
|
7
|
+
- Replace the previous automation-only release assumption with an explicit repository-owner local acceptance boundary. `npm run release:candidate` runs the complete suite and creates the exact npm tarball under ignored local state; `npm run release:accept` records the owner decision only when a second pack is byte-identical. The tracked acceptance record contains package identity, SHA-1, SHA-512 integrity, timestamp, and a fixed confirmation marker, while excluding personal identity, machine paths, logs, credentials, and user content.
|
|
8
|
+
- Add `npm run github:push`, which rejects dirty trees, detached HEAD, direct `main` pushes, untracked acceptance records, or package-hash drift before pushing a release-relevant branch. Pull-request CI and `release:publish` independently rebuild and verify the accepted package. `release:publish` no longer pushes `main`; it requires the accepted branch to have been reviewed and merged so local `HEAD` already equals `origin/main`.
|
|
9
|
+
- Distinguish npm-package changes from GitHub-only repository infrastructure. The release-impact gate now derives package relevance from `package.json.files`, so Action-only Dependabot PRs no longer deadlock on an unrelated npm version bump while source, scripts, browser extension, package metadata, and shipped documentation remain versioned.
|
|
10
|
+
- Consolidate the five pending GitHub Action updates atomically: CodeQL `init`, `analyze`, and `upload-sarif` now use the same 4.37.1 commit; `actions/setup-node` advances to 7.0.0; and `actions/upload-artifact` advances to 7.0.1. Dependabot now groups all GitHub Action updates so coupled action families cannot be split into incompatible PRs.
|
|
11
|
+
- Update Wrangler from 4.111.0 to 4.112.0 and advance the exact reviewed `workerd` install-script allowlist to 1.20260714.1. Complete and production dependency audits remain at zero known vulnerabilities.
|
|
12
|
+
- Rewrite the release, contribution, automation, engineering, architecture, testing, and audit contracts around the owner-tested artifact boundary. Add executable regression coverage for package-impact classification, package-hash acceptance, workflow grouping, CI verification, no automatic `main` push, and package-manifest inclusion of every new helper.
|
|
13
|
+
|
|
3
14
|
## 1.2.7 - 2026-07-17
|
|
4
15
|
|
|
5
16
|
### Process supervision, lifecycle, and isolation audit
|
package/CONTRIBUTING.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# Contributing and release discipline
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Read [docs/PROJECT_STANDARDS.md](docs/PROJECT_STANDARDS.md), [docs/ENGINEERING.md](docs/ENGINEERING.md), [GOVERNANCE.md](GOVERNANCE.md), and the relevant domain documentation before changing behavior.
|
|
4
4
|
|
|
5
5
|
## Development workflow
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The repository uses GitHub Flow: branch from current `main`, keep one coherent change, validate it locally, open a pull request, satisfy required checks, squash-merge, and delete the branch. Permanent `develop` or generic release integration branches are not used unless an independently maintained release line creates a concrete need.
|
|
8
|
+
|
|
9
|
+
Repository automation performs GitHub operations only through local `git`, `gh`, and `gh api` commands executed by Machine Bridge. Hosted GitHub connectors and ChatGPT GitHub plugins are prohibited for this repository. Direct pushes to `main` and force pushes are prohibited.
|
|
8
10
|
|
|
9
11
|
Branch names use a category and purpose such as `feat/browser-downloads`, `fix/relay-timeout`, or `chore/dependency-policy`.
|
|
10
12
|
|
|
@@ -14,21 +16,32 @@ Pull-request titles and final commit subjects use Conventional Commits:
|
|
|
14
16
|
<type>[optional scope][optional !]: <imperative description>
|
|
15
17
|
```
|
|
16
18
|
|
|
17
|
-
Accepted types are `feat`, `fix`, `docs`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `security`, `release`, and `revert`. Explain the causal problem, why the solution is correct, compatibility/security/privacy risk, verification, and release impact
|
|
19
|
+
Accepted types are `feat`, `fix`, `docs`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `security`, `release`, and `revert`. Explain the causal problem, why the solution is correct, compatibility/security/privacy risk, verification, and release impact. Bug fixes require a regression test for the original failure mechanism.
|
|
20
|
+
|
|
21
|
+
## Package impact
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
An npm version is required when a change affects `package.json`, `package-lock.json`, or any path included by the package `files` manifest. This includes runtime source, executable scripts, browser-extension files, shared contracts, and shipped documentation. `npm run release-impact:check` derives the decision from the package manifest.
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
Repository-only infrastructure changes, such as a `.github/` workflow update, do not require a synthetic npm version when package bytes are unchanged. They still require review and all applicable CI, dependency-review, CodeQL, governance, and Scorecard checks.
|
|
26
|
+
|
|
27
|
+
## Required before pushing an npm-package change
|
|
22
28
|
|
|
23
29
|
1. bump `package.json` to a version newer than the latest reachable `v*` tag;
|
|
24
|
-
2. add the matching dated section to `CHANGELOG.md
|
|
25
|
-
3. run `npm run
|
|
26
|
-
4. inspect the complete diff
|
|
27
|
-
5.
|
|
30
|
+
2. add the matching dated section to `CHANGELOG.md` and update audit/documentation records;
|
|
31
|
+
3. run targeted tests, both dependency audits, `npm run worker:dry-run`, privacy/history review, signature verification, SBOM generation, and package inspection as applicable;
|
|
32
|
+
4. inspect the complete diff;
|
|
33
|
+
5. run `npm run release:candidate`, which executes the complete suite and creates the exact candidate tarball under ignored `.release-candidate/`;
|
|
34
|
+
6. have the repository owner test that exact tarball on the maintainer machine through the normal installation/startup path;
|
|
35
|
+
7. have the owner record the explicit decision with the exact command printed by the candidate tool, creating `release-acceptance/v<version>.json`;
|
|
36
|
+
8. commit the acceptance record and push the clean non-`main` branch only with `npm run github:push`.
|
|
37
|
+
|
|
38
|
+
Automated checks do not authorize step 7. A coding agent must not record owner acceptance or push the release-relevant branch before the owner has tested it. Any packaged-file change after acceptance changes the npm tarball hash and requires a regenerated candidate and a new owner test.
|
|
28
39
|
|
|
29
|
-
After all required pull-request checks pass, repository automation
|
|
40
|
+
After all required pull-request checks pass, repository automation may complete the source release: squash-merge, verify the exact `main` push CI, CodeQL, Governance, and Scorecard runs, and run `npm run release:publish`. The publication helper requires `HEAD === origin/main`; it does not push `main`. It creates or verifies the annotated version tag and final GitHub Release only after the accepted package hash and exact-commit checks match.
|
|
30
41
|
|
|
31
|
-
|
|
42
|
+
The release operator separately authorizes npm publication and any live machine update. Automation must not publish, deprecate, or unpublish npm packages; install the CLI globally; deploy the Worker; rotate credentials; mutate live deployment state; or start, stop, install, remove, or replace the daemon or autostart service without explicit user authorization.
|
|
43
|
+
|
|
44
|
+
Supported upgrade and rollback behavior is defined in [docs/UPGRADING.md](docs/UPGRADING.md). Support requests follow [SUPPORT.md](SUPPORT.md), and repository participation follows [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
|
|
32
45
|
|
|
33
46
|
After npm publication, the standard machine update is:
|
|
34
47
|
|
|
@@ -36,15 +49,9 @@ After npm publication, the standard machine update is:
|
|
|
36
49
|
npm install -g --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest && machine-mcp
|
|
37
50
|
```
|
|
38
51
|
|
|
39
|
-
The npm command updates the global CLI. Normal `machine-mcp` startup checks the Worker deployment hash, expected version, and health, redeploys when necessary, and reconciles the daemon/autostart flow.
|
|
40
|
-
|
|
41
|
-
`npm run release-impact:check` enforces the version and changelog parts. It fails when release-relevant files changed after the latest version tag but the package version was not advanced.
|
|
42
|
-
|
|
43
|
-
A privacy or security correction is always release-relevant. Removing a private identifier only from the current branch is insufficient: publish a replacement npm version, update GitHub, and deprecate or unpublish the affected npm version when policy and authentication permit.
|
|
44
|
-
|
|
45
52
|
## Privacy
|
|
46
53
|
|
|
47
|
-
Use only synthetic names, reserved example domains, and generic paths. Maintain private local identifiers in the ignored `.privacy-denylist` and run `npm run privacy:check` before committing.
|
|
54
|
+
Use only synthetic names, reserved example domains, and generic paths. Maintain private local identifiers in the ignored `.privacy-denylist` and run `npm run privacy:check` before committing. Local acceptance records contain hashes and a fixed marker only; do not add machine paths, personal names, logs, credentials, endpoint URLs, or user content.
|
|
48
55
|
|
|
49
56
|
## Engineering standards
|
|
50
57
|
|
|
@@ -52,4 +59,4 @@ Read [docs/ENGINEERING.md](docs/ENGINEERING.md) before changing architecture, po
|
|
|
52
59
|
|
|
53
60
|
A log change is behavior: test its level, repetition policy, privacy fields, and recovery message. A transport change must distinguish low-level connectivity from authenticated readiness and test timeout/reconnect branches deterministically. Lock, state deletion, service lifecycle, detached process, and credential changes require behavior-level concurrency or fault-injection tests. Review [docs/AUDIT.md](docs/AUDIT.md) before changing those surfaces.
|
|
54
61
|
|
|
55
|
-
Reusable decisions belong in tracked documentation. Keep only machine-specific observations in
|
|
62
|
+
Reusable decisions belong in tracked documentation. Keep only machine-specific observations in ignored `.project-local/`, and never store credentials there.
|
package/README.md
CHANGED
|
@@ -541,7 +541,18 @@ npm audit --omit=dev --audit-level=high
|
|
|
541
541
|
npm pack --dry-run
|
|
542
542
|
```
|
|
543
543
|
|
|
544
|
-
`npm run check` covers privacy and
|
|
544
|
+
`npm run check` covers privacy and package-impact gates, local-release-acceptance hash logic, architecture/link invariants, generated Worker types, TypeScript, recursively discovered JavaScript syntax, semantic undefined-identifier checks, catalog-to-runtime handler parity, deterministic relay lifecycle and secure-file tests, concurrent lock/atomic-replacement/PID-reuse fixtures, fail-closed service lifecycle tests, descendant process-tree termination, local path/write/state/log/service invariants, Ed25519/RSA generation and key-pair validation, real-machine `full` sandbox acceptance, a clean npm package-manifest/mode/sensitive-artifact check, an isolated installation of the actual packed tarball whose zero-argument CLI startup must reach a controlled Worker-deployment boundary, managed-job integrity/redaction/finally/cancellation/recovery, a live stdio MCP flow, a live local OAuth/Worker/WebSocket/MCP flow covering discovery, Claude callback routing, rotating refresh tokens, replay rejection, and account-targeted refresh revocation, plus a real Chromium negative/positive test for CSP-governed OAuth callback navigation. GitHub Actions runs the complete suite on Linux, macOS, and Windows with the pinned Node 26/npm 12 baseline.
|
|
545
|
+
|
|
546
|
+
For an npm-package release, automated checks are followed by a separate repository-owner acceptance boundary:
|
|
547
|
+
|
|
548
|
+
```sh
|
|
549
|
+
npm run release:candidate
|
|
550
|
+
# Test the exact .release-candidate/*.tgz artifact locally.
|
|
551
|
+
npm run release:accept -- --confirm "I TESTED machine-bridge-mcp <version> LOCALLY AND IT WORKS"
|
|
552
|
+
npm run github:push
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
The coding agent must stop before the first GitHub push until the owner tests the exact tarball. The acceptance record stores package hashes, not machine paths or logs, and any packaged-file change invalidates it. See [docs/RELEASING.md](docs/RELEASING.md).
|
|
545
556
|
|
|
546
557
|
The cross-cutting 0.12.0 review, corrected failure modes, and residual OS-level limits are recorded in [docs/AUDIT.md](docs/AUDIT.md). See also [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md), [docs/MULTI_ACCOUNT.md](docs/MULTI_ACCOUNT.md), [docs/AGENT_CONTEXT.md](docs/AGENT_CONTEXT.md), [docs/LOCAL_AUTOMATION.md](docs/LOCAL_AUTOMATION.md), [docs/MANAGED_JOBS.md](docs/MANAGED_JOBS.md), [docs/TESTING.md](docs/TESTING.md), [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), [docs/ENGINEERING.md](docs/ENGINEERING.md), [docs/PROJECT_STANDARDS.md](docs/PROJECT_STANDARDS.md), the generated [MCP tool reference](docs/TOOL_REFERENCE.md), and [SECURITY.md](SECURITY.md).
|
|
547
558
|
|
|
@@ -558,4 +569,4 @@ Use `--keep-worker` to retain deployed Workers while removing local state and au
|
|
|
558
569
|
|
|
559
570
|
MIT
|
|
560
571
|
|
|
561
|
-
See [repository privacy hygiene](docs/PRIVACY.md) and [contribution/release discipline](CONTRIBUTING.md) before committing. Every
|
|
572
|
+
See [repository privacy hygiene](docs/PRIVACY.md) and [contribution/release discipline](CONTRIBUTING.md) before committing. Every npm-package change requires a new version, an owner-tested exact tarball, a matching acceptance record, and a matching npm release. GitHub-only infrastructure changes may remain source-only when package bytes are unchanged.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Machine Bridge Browser",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.8",
|
|
5
5
|
"description": "Connects the current Chromium browser profile to the local Machine Bridge runtime for user-authorized automation.",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"tabs",
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"action": {
|
|
31
31
|
"default_title": "Machine Bridge Browser"
|
|
32
32
|
},
|
|
33
|
-
"version_name": "1.2.
|
|
33
|
+
"version_name": "1.2.8"
|
|
34
34
|
}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -255,9 +255,11 @@ Cloudflare sampling is size control rather than an audit log. The project intent
|
|
|
255
255
|
|
|
256
256
|
## Release integrity
|
|
257
257
|
|
|
258
|
-
Repository-local checks are necessary but cannot prove
|
|
258
|
+
Repository-local automated checks are necessary but cannot prove that the maintainer's ordinary installation path works. `scripts/local-release-acceptance.mjs` builds the exact npm tarball, requires an explicit repository-owner confirmation after local testing, and records both npm hashes outside the package. `scripts/github-push.mjs`, pull-request CI, and `scripts/github-release.mjs` rebuild the package and reject any mismatch. The release helper also requires `HEAD === origin/main`; it cannot silently push `main`.
|
|
259
259
|
|
|
260
|
-
|
|
260
|
+
Cross-platform evidence remains independent. `scripts/github-release.mjs` queries CI, CodeQL, Governance, and Scorecard for the exact `origin/main` commit and requires the newest push-triggered run for each workflow to be completed with `success` before it creates or verifies a version tag, GitHub Release, or package asset. Pull-request runs, older successful runs, pending runs, and successful runs for another SHA do not satisfy the gate. The workflow selection policy is isolated in `scripts/release-ci.mjs` and tested independently.
|
|
261
|
+
|
|
262
|
+
Third-party workflow actions are pinned to immutable commit SHAs. Dependabot groups GitHub Action updates into one reviewed PR so coupled action families cannot drift across versions. `architecture:test` rejects movable action tags, split update policy, loss of local acceptance verification, or removal of the reachable-history package-audit step.
|
|
261
263
|
|
|
262
264
|
## Explicit non-goals
|
|
263
265
|
|
package/docs/AUDIT.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Security and privacy audit notes
|
|
2
2
|
|
|
3
|
+
## 2026-07-17 version 1.2.8 release-integrity and dependency-automation audit
|
|
4
|
+
|
|
5
|
+
The core failure was procedural rather than a missing unit test: the repository treated a green automated suite as sufficient evidence for source publication, and `release:publish` could push a locally tested commit directly to `main`. That model cannot satisfy the reported history of formally released builds failing on the maintainer's real installation. It also assigned the coding agent authority to infer release acceptance, even though only the repository owner can observe the ordinary local environment and decide whether the product is usable.
|
|
6
|
+
|
|
7
|
+
Version 1.2.8 separates automated verification from owner acceptance. The candidate command runs the full suite and packs the exact artifact. The owner tests that tarball and records a fixed confirmation; a second pack must have identical npm SHA-1 and SHA-512 integrity before the tracked record is written. The record is outside the package manifest, so adding it and squash-merging do not alter the accepted bytes. Any packaged source, script, metadata, browser-extension, or shipped-documentation change invalidates the record. The guarded push command checks cleanliness, branch safety, tracking, and package hashes. CI repeats the hash check. Final release creation requires the same acceptance plus exact-commit CI, CodeQL, Governance, and Scorecard success, and it no longer contains a path that pushes `main`. The acceptance record is a reviewed process assertion, not a cryptographic identity signature; automation is explicitly prohibited from creating it on the owner's behalf.
|
|
8
|
+
|
|
9
|
+
The five open Dependabot PRs exposed a second design defect. The three CodeQL sub-actions were proposed independently even though CodeQL rejects mixed action versions, while every Action-only PR was forced through an npm release-impact gate that considered all repository files package changes. The combined update now keeps all CodeQL components on 4.37.1, updates setup-node to 7.0.0 and upload-artifact to 7.0.1, groups future GitHub Action updates, and derives npm release relevance from the package manifest. GitHub-only workflow maintenance can therefore be reviewed without inventing an npm version, while package content remains fail-closed. Official tag objects were resolved through GitHub and match every pinned SHA. The setup-node and upload-artifact majors move their action runtimes to Node 24/ESM without changing the inputs used here; CodeQL 4.37.1 advances the default bundle to 2.26.1. Architecture tests now require all three CodeQL action roles to share one immutable commit even if dependency automation regresses.
|
|
10
|
+
|
|
11
|
+
The broader pass rechecked dependency health, install-script policy, package contents, release scripts, workflow permissions, immutable Action pinning, source and test size guardrails, process/network boundary inventory, privacy scanning, documentation ownership, and existing architecture limits. Both complete and production npm audits report zero vulnerabilities, the dependency tree reports no structural problems, and Wrangler's only available patch was advanced to 4.112.0 with the exact new workerd install script reviewed and allowlisted. No credential-like tracked artifact beyond the deliberate non-secret repository `.npmrc` was found. Existing execution, relay, browser, state, ACL, logging, and lifecycle behavior remains covered by the prior risk-directed suites; this change does not claim OS CPU/memory/network isolation that the runtime does not implement.
|
|
12
|
+
|
|
13
|
+
No branch, tag, GitHub Release, npm package, Worker deployment, global installation, daemon/service replacement, credential change, or live state mutation is performed before the repository owner tests and accepts the 1.2.8 candidate.
|
|
14
|
+
|
|
3
15
|
## 2026-07-17 version 1.2.7 process-supervision and lifecycle audit
|
|
4
16
|
|
|
5
17
|
The core question in this review was not whether the existing green suite covered many paths; it was whether failure and ownership boundaries remained truthful when processes resist termination, startup is interrupted, proxy state is hostile, or the operating system refuses a spawn. The audit found that generic argv validation and process-tree termination had drifted into the interactive-session module, while shell and managed-job paths maintained adjacent termination branches. This created a dependency inversion: one-shot execution and process accounting depended on an adapter whose responsibility should have been only interactive session state.
|
package/docs/ENGINEERING.md
CHANGED
|
@@ -9,7 +9,7 @@ This document records project-wide decisions that must survive individual fixes,
|
|
|
9
9
|
3. **Machine Bridge authority and host authority are separate.** `full` removes Machine Bridge's own policy, path, shell, and environment restrictions. It cannot override an MCP host, connector gateway, operating system, endpoint-security product, cloud IAM, remote authentication, or `sudo`.
|
|
10
10
|
4. **Publication surfaces contain no real environment metadata.** Source, tests, fixtures, examples, release notes, filenames, package contents, tags, and release assets use synthetic identifiers and reserved example domains.
|
|
11
11
|
5. **Secrets are never operational log data.** Tool arguments, command text, stdin, stdout, stderr, file content, OAuth bodies, credentials, and local resource values are not logged.
|
|
12
|
-
6. **A release is one
|
|
12
|
+
6. **A release is one owner-tested package with successful cross-platform evidence.** The repository owner must test the exact npm tarball and record its hashes before the first GitHub push of an npm-package change. Package metadata, Worker version, browser-extension version/name, acceptance record, Git tag, GitHub Release, npm version, documentation, and deployed health version must agree, and the exact `origin/main` commit must have completed successful push-triggered CI, CodeQL, Governance, and OpenSSF Scorecard runs before a tag or release is created.
|
|
13
13
|
7. **Generic local automation is structured, not arbitrary evaluation.** Browser/application features may expose broad user authority under canonical `full`, but must not accept caller-provided JavaScript, AppleScript, JXA, or extension code.
|
|
14
14
|
8. **Daily-browser integration uses the existing profile.** The supported primary browser path is the packaged authenticated extension and machine-level loopback broker, preserving current tabs/login state; a separate automation profile is not an equivalent replacement.
|
|
15
15
|
9. **Pairing and resource secrets are not conversation or log data.** Tokens and injected local-resource values must not be returned, embedded in URLs, or written to operational logs.
|
|
@@ -24,7 +24,7 @@ A proposed change that conflicts with an invariant requires an explicit owner de
|
|
|
24
24
|
|
|
25
25
|
## Change and release-operation ownership
|
|
26
26
|
|
|
27
|
-
Repository source release completion and live release operations are separate responsibilities. Under
|
|
27
|
+
Repository implementation, owner acceptance, source release completion, and live release operations are separate responsibilities. Under `AGENTS.md`, coding automation may edit, test, and commit locally, then generate an exact candidate tarball. It must stop before the first GitHub push until the repository owner personally tests that artifact and records `release-acceptance/v<version>.json`. The agent may then push only through `npm run github:push`, complete the pull request through local `git`/`gh`, and create the annotated version tag plus final GitHub Release only after the accepted package hash and exact `main` checks pass. `release:publish` never pushes `main`. Automation must not assert owner acceptance, publish or alter npm packages, install globally, deploy or reconfigure a Worker, rotate credentials, mutate live deployment state, or replace daemon/service state without explicit user authorization.
|
|
28
28
|
|
|
29
29
|
The normal handoff is: the repository owner publishes the reviewed npm version, then runs `npm install -g --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest && machine-mcp`. The npm command updates the global CLI but cannot hot-reload an existing Node process. The subsequent normal foreground startup validates the Worker deployment hash, expected version, and health, requests shutdown of an active autostart daemon, waits a bounded interval for its lock, redeploys when necessary, and then takes over with the installed version. Live operations require explicit authorization even when they appear to be the obvious next release step.
|
|
30
30
|
|
|
@@ -18,11 +18,15 @@ Branch names use a short category and purpose, for example `feat/browser-downloa
|
|
|
18
18
|
|
|
19
19
|
Direct pushes to `main`, force pushes, and branch deletion are blocked by repository protection. An exception requires an incident record and an explicit owner decision.
|
|
20
20
|
|
|
21
|
-
### Completion ownership
|
|
21
|
+
### Completion ownership and local acceptance
|
|
22
22
|
|
|
23
|
-
Repository automation owns
|
|
23
|
+
Repository automation owns implementation, local validation, and pull-request completion, but it does not own the maintainer's release acceptance decision. For every npm-package change, automation must generate the exact candidate tarball with `npm run release:candidate` and stop before the first GitHub push. The repository owner must install or otherwise exercise that exact tarball on the maintainer machine through the normal user path and record a passing decision with `npm run release:accept -- --confirm "I TESTED machine-bridge-mcp <version> LOCALLY AND IT WORKS"`. Automation must not create that assertion on the owner's behalf.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The tracked `release-acceptance/v<version>.json` record binds the decision to the npm tarball SHA-1 and SHA-512 integrity value. The record is excluded from the package, so a content-preserving squash merge does not invalidate it; any source, executable script, package metadata, or packaged-documentation change produces a different package hash and requires a new local test. `npm run github:push`, pull-request CI, and `release:publish` verify the record. Raw direct pushes of release-relevant branches are prohibited.
|
|
26
|
+
|
|
27
|
+
After owner acceptance, repository automation may push only through `npm run github:push`, open and complete the pull request, verify the resulting `main` commit, and remove the merged branch. Every npm-package change advances the package version and is completed by an annotated `v<version>` tag plus a final GitHub Release for the exact successful `main` commit. GitHub-only repository infrastructure changes that do not alter npm package contents do not require a synthetic package version or local runtime acceptance, but they still require review and their applicable CI/security checks.
|
|
28
|
+
|
|
29
|
+
The coding agent may perform the tag and GitHub Release work only after the recorded owner acceptance and exact-commit checks succeed. npm publication, Worker deployment, credential mutation, global installation, and daemon/service replacement remain separate live operations requiring explicit authorization.
|
|
26
30
|
|
|
27
31
|
### Local GitHub control plane
|
|
28
32
|
|
|
@@ -115,10 +119,10 @@ Flaky tests are defects. A retry may diagnose environmental instability but may
|
|
|
115
119
|
## 7. Security and software supply chain
|
|
116
120
|
|
|
117
121
|
- GitHub workflow permissions default to read-only and are expanded per job only when required.
|
|
118
|
-
- Third-party Actions are pinned to immutable commit SHAs and reviewed when Dependabot updates them.
|
|
122
|
+
- Third-party Actions are pinned to immutable commit SHAs and reviewed when Dependabot updates them. GitHub Action updates are grouped into one atomic pull request so coupled suites such as CodeQL cannot be split across incompatible versions.
|
|
119
123
|
- npm dependencies use exact versions and a committed lockfile. Source bootstrap uses `npm ci`; the CI npm baseline itself is downloaded from an exact URL and verified against a pinned SHA-512 SRI before use. Registry signatures and attestations are verified in CI.
|
|
120
124
|
- Dependency review blocks newly introduced vulnerable dependencies. CodeQL performs JavaScript/TypeScript and workflow analysis. OpenSSF Scorecard audits supply-chain posture, and both SARIF streams are failing gates with exact, expiring exceptions rather than advisory-only uploads.
|
|
121
|
-
- CI generates and validates a CycloneDX SBOM. Release artifacts must be reproducible from a reviewed commit and tied to successful exact-commit CI, CodeQL, Governance, and Scorecard evidence.
|
|
125
|
+
- CI generates and validates a CycloneDX SBOM. Release artifacts must match the repository-owner-tested tarball hash, be reproducible from a reviewed commit, and be tied to successful exact-commit CI, CodeQL, Governance, and Scorecard evidence.
|
|
122
126
|
- Secret scanning and push protection are enabled. Repository examples use synthetic identities and reserved domains; reachable history is scanned before release.
|
|
123
127
|
- Long-lived publication tokens should be replaced by npm trusted publishing with GitHub OIDC. Until that external registry configuration is completed, release credentials remain an explicit operator responsibility and must never be stored in the repository.
|
|
124
128
|
- Security reports follow [SECURITY.md](../SECURITY.md), not public issue templates.
|
package/docs/RELEASING.md
CHANGED
|
@@ -2,58 +2,92 @@
|
|
|
2
2
|
|
|
3
3
|
The release invariant is:
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
5
|
+
- the repository owner tested the exact npm tarball represented by `release-acceptance/v<version>.json` on the maintainer machine and explicitly recorded a passing result;
|
|
6
|
+
- the current package produces the same SHA-1 and SHA-512 integrity values as that accepted tarball;
|
|
7
|
+
- `main` points to the release commit, and that exact commit has completed successful push-triggered CI, CodeQL, Governance, and OpenSSF Scorecard runs;
|
|
8
|
+
- `v<package version>` points to that same commit locally and on GitHub;
|
|
9
|
+
- a final GitHub Release exists for the tag and contains the accepted npm tarball;
|
|
10
|
+
- `package.json`, `package-lock.json`, the Worker-reported version, and `browser-extension/manifest.json` (`version`/`version_name`) agree;
|
|
11
|
+
- every npm-package change since the prior version tag has a higher package version and matching CHANGELOG section;
|
|
12
|
+
- the same accepted package content is present on GitHub and in a new npm version.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
A green automated suite is necessary but is not evidence that the maintainer's ordinary installation path works. The repository therefore separates automated validation from owner acceptance and binds both to the final package bytes.
|
|
14
15
|
|
|
15
|
-
##
|
|
16
|
+
## Change classification
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
A change is **npm-package relevant** when it alters `package.json`, `package-lock.json`, or a path included by the package `files` manifest. Source, runtime scripts, browser extension files, shared contracts, shipped documentation, and package metadata therefore require a new version and local acceptance.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
A repository-only change under paths such as `.github/` may be merged without a synthetic npm version when the package bytes are unchanged. It still requires review and all applicable GitHub checks. `scripts/release-impact-check.mjs` implements this distinction from the package manifest rather than from a duplicated path list.
|
|
20
21
|
|
|
21
|
-
## Prepare
|
|
22
|
+
## Prepare the candidate locally
|
|
22
23
|
|
|
23
|
-
1. Set the new version without creating
|
|
24
|
+
1. Set the new version without creating a tag. The npm version hook synchronizes the Worker and browser-extension versions:
|
|
24
25
|
|
|
25
26
|
```sh
|
|
26
27
|
npm version <version> --no-git-tag-version
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
2. Add the matching dated `CHANGELOG.md` section.
|
|
30
|
-
3. Run
|
|
31
|
-
4. Inspect the complete diff
|
|
30
|
+
2. Add the matching dated `CHANGELOG.md` section and update affected documentation and audit notes.
|
|
31
|
+
3. Run the required dependency, privacy, Worker, and package checks while iterating.
|
|
32
|
+
4. Inspect the complete diff.
|
|
33
|
+
5. Generate the final candidate:
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
```sh
|
|
36
|
+
npm run release:candidate
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`release:candidate` runs the complete repository suite and then writes the exact npm tarball plus a bounded pending manifest under ignored `.release-candidate/`. Do not modify a packaged file after generating the candidate without regenerating and retesting it.
|
|
40
|
+
|
|
41
|
+
## Repository-owner local acceptance
|
|
42
|
+
|
|
43
|
+
The repository owner—not the coding agent—must test the exact `.release-candidate/*.tgz` artifact on the maintainer machine through the normal installation and startup path relevant to the change. For a general release, this includes installation from that tarball, zero-argument startup, ordinary connection/readiness, and at least one representative user operation. Changes to browser, application automation, service startup, proxying, credentials, or platform-specific behavior require the corresponding live or platform-specific test described in `docs/TESTING.md` and `docs/OPERATIONS.md`.
|
|
44
|
+
|
|
45
|
+
After the owner confirms the candidate works, record the decision using the exact phrase printed by `release:candidate`, for example:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
npm run release:accept -- --confirm "I TESTED machine-bridge-mcp 1.2.8 LOCALLY AND IT WORKS"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This creates `release-acceptance/v<version>.json` containing only package identity, hashes, timestamp, result, and a fixed owner-confirmation marker. It does not store a personal name, machine path, command output, credential, or user content. The acceptance record is intentionally excluded from the npm package, so adding it does not change the tested tarball.
|
|
34
52
|
|
|
35
|
-
|
|
53
|
+
The command repacks the current tree and refuses to record acceptance if any packaged byte changed after candidate preparation. The assertion is a process gate backed by branch ownership and review, not a cryptographic identity signature; it must never be generated by automation merely because automated tests passed.
|
|
36
54
|
|
|
37
|
-
|
|
55
|
+
## Push and review
|
|
56
|
+
|
|
57
|
+
Commit the candidate changes and acceptance record, then push the branch only through:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
npm run github:push
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The command requires a clean non-`main` branch, verifies that the acceptance record is tracked, rebuilds the npm package, compares both hashes, and only then executes a non-force push of the current branch. Direct pushes to `main` remain prohibited. Any packaged-file change after acceptance invalidates the hash and blocks the next push until the owner retests a regenerated candidate.
|
|
64
|
+
|
|
65
|
+
Open a pull request, satisfy the required checks, and squash-merge. Pull-request CI repeats the acceptance verification. A content-preserving squash changes the Git commit but not the package bytes, so the accepted hash remains valid.
|
|
66
|
+
|
|
67
|
+
## Publish the GitHub source release
|
|
68
|
+
|
|
69
|
+
From a clean, fast-forwarded `main` worktree whose `HEAD` exactly equals `origin/main`:
|
|
38
70
|
|
|
39
71
|
```sh
|
|
40
72
|
npm run release:publish
|
|
41
73
|
```
|
|
42
74
|
|
|
43
|
-
|
|
75
|
+
`release:publish` never pushes `main`. It verifies the owner acceptance, runs the complete project and version checks, requires successful exact-commit push runs for CI, CodeQL, Governance, and Scorecard, creates or verifies the annotated version tag, pushes only the version tag when absent, builds the npm tarball, creates or updates the final GitHub Release, uploads the tarball, and verifies the resulting state.
|
|
44
76
|
|
|
45
|
-
To verify without changing anything:
|
|
77
|
+
To verify an already completed source release without changing anything:
|
|
46
78
|
|
|
47
79
|
```sh
|
|
48
80
|
npm run release:check
|
|
49
81
|
```
|
|
50
82
|
|
|
51
|
-
To create GitHub Release records for
|
|
83
|
+
To create GitHub Release records for historical remote tags that lack releases:
|
|
52
84
|
|
|
53
85
|
```sh
|
|
54
86
|
npm run release:backfill
|
|
55
87
|
```
|
|
56
88
|
|
|
89
|
+
Backfill is historical metadata repair. It does not retroactively claim that releases predating the 1.2.8 acceptance policy had a recorded local acceptance.
|
|
90
|
+
|
|
57
91
|
## Publish npm
|
|
58
92
|
|
|
59
93
|
Only after `release:check` succeeds:
|
|
@@ -62,18 +96,16 @@ Only after `release:check` succeeds:
|
|
|
62
96
|
npm publish --access public
|
|
63
97
|
```
|
|
64
98
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
## Authentication requirements
|
|
99
|
+
`prepublishOnly` repeats the complete checks and GitHub synchronization check. npm publication remains a deliberate release-operator action and is not authorized by an ordinary source change.
|
|
68
100
|
|
|
69
|
-
|
|
70
|
-
- An authenticated GitHub CLI session with repository release permission.
|
|
71
|
-
- An npm account that owns the package or has maintainer permission.
|
|
101
|
+
## Authentication and external controls
|
|
72
102
|
|
|
73
|
-
|
|
103
|
+
Required local access:
|
|
74
104
|
|
|
75
|
-
|
|
105
|
+
- Git push access to `origin` for the accepted branch and version tag;
|
|
106
|
+
- an authenticated GitHub CLI session with pull-request and release permission;
|
|
107
|
+
- an npm account that owns the package or has maintainer permission for the separate registry publication step.
|
|
76
108
|
|
|
77
|
-
|
|
109
|
+
GitHub Actions remains a required cross-platform boundary. Missing, pending, cancelled, skipped, failed, pull-request-only, stale, or wrong-commit runs do not satisfy release publication. Local owner acceptance and successful GitHub checks are complementary evidence; neither substitutes for the other.
|
|
78
110
|
|
|
79
|
-
|
|
111
|
+
The preferred registry target remains npm trusted publishing from a narrowly scoped GitHub Actions workflow using OIDC and a protected GitHub environment. Enabling it requires package-owner configuration in npm and a reviewed workflow change. Until then, never place an npm token in repository files, workflow YAML, logs, local acceptance records, or project notes.
|
package/docs/TESTING.md
CHANGED
|
@@ -12,6 +12,8 @@ The repository requires Node.js 26 and npm 12. `.node-version`, `.nvmrc`, `packa
|
|
|
12
12
|
|
|
13
13
|
The suite includes:
|
|
14
14
|
|
|
15
|
+
- package-impact classification derived from the npm package manifest, allowing repository-only GitHub workflow maintenance without weakening version requirements for shipped content;
|
|
16
|
+
- repository-owner local acceptance hashing, including exact tarball SHA-1/SHA-512 matching, acceptance-record exclusion from the package, and invalidation after any packaged-byte change;
|
|
15
17
|
- release-impact enforcement requiring a new package version and CHANGELOG section for release-relevant changes;
|
|
16
18
|
- release-state diagnostics distinguishing missing local/remote version tags from tags that point to the wrong commit, plus a release-CI gate that rejects missing, pending, failed, pull-request-only, stale, or wrong-commit runs;
|
|
17
19
|
- generated Cloudflare Worker types under ignored `.wrangler/` state and strict TypeScript checking, including unused-local and unused-parameter rejection; packaging rejects generated declarations;
|
|
@@ -97,9 +99,10 @@ npm sbom --sbom-format cyclonedx
|
|
|
97
99
|
npm pack --dry-run
|
|
98
100
|
npm run version:check
|
|
99
101
|
npm run release-impact:check
|
|
102
|
+
npm run release:acceptance:verify
|
|
100
103
|
```
|
|
101
104
|
|
|
102
|
-
GitHub Actions executes the main suite on Linux, macOS, and Windows using the pinned Node 26/npm 12 baseline. Because Node 26 currently bundles npm 11, CI downloads the exact npm 12.0.1 tarball from the canonical registry, rejects redirects, verifies its recorded SHA-512 SRI, and exposes that verified CLI through `GITHUB_PATH` before any project-local npm command can trigger strict `devEngines`. Checkout fetches version tags so the release-impact gate can compare the branch with the latest release. A separate package-audit job scans reachable Git history, audits both the complete dependency graph and the production-only graph, verifies registry signatures and attestations, validates a CycloneDX SBOM written under the runner temporary directory, exercises the documented isolated global installation, then performs a dry-run package build. Separate pinned workflows validate governance titles, review dependency changes, run CodeQL over JavaScript/TypeScript and GitHub Actions, and run OpenSSF Scorecard. The SARIF gate fails closed when rule metadata is absent, CodeQL permits only one exact, expiring non-shell process-boundary result, and Scorecard permits only exact reviewed governance/time-dependent findings; new dependency-pinning, fuzzing, or other supply-chain results fail before upload. Scorecard's signed analysis job contains only pinned `uses` steps, while a dependent gate job downloads the SARIF, enforces the accepted inventory, and uploads it to code scanning. Security property tests use a `.js` `fast-check` suite with deterministic seeds because the pinned Scorecard scanner recognizes JavaScript property testing only in `*.js`/`*.jsx`. The installed zero-argument startup smoke test also verifies that Windows creates and remembers `%USERPROFILE%\MachineBridge` while other platforms retain the package-free current directory. The macOS matrix job also runs the installation smoke test because Wrangler's optional `fsevents` resolution is platform-specific. Third-party Actions are pinned to immutable 40-character commit SHAs; architecture tests reject movable tags, and Dependabot
|
|
105
|
+
GitHub Actions executes the main suite on Linux, macOS, and Windows using the pinned Node 26/npm 12 baseline. Because Node 26 currently bundles npm 11, CI downloads the exact npm 12.0.1 tarball from the canonical registry, rejects redirects, verifies its recorded SHA-512 SRI, and exposes that verified CLI through `GITHUB_PATH` before any project-local npm command can trigger strict `devEngines`. Checkout fetches version tags so the release-impact gate can compare the branch with the latest release. A separate package-audit job scans reachable Git history, audits both the complete dependency graph and the production-only graph, verifies registry signatures and attestations, validates a CycloneDX SBOM written under the runner temporary directory, exercises the documented isolated global installation, then performs a dry-run package build. Separate pinned workflows validate governance titles, review dependency changes, run CodeQL over JavaScript/TypeScript and GitHub Actions, and run OpenSSF Scorecard. The SARIF gate fails closed when rule metadata is absent, CodeQL permits only one exact, expiring non-shell process-boundary result, and Scorecard permits only exact reviewed governance/time-dependent findings; new dependency-pinning, fuzzing, or other supply-chain results fail before upload. Scorecard's signed analysis job contains only pinned `uses` steps, while a dependent gate job downloads the SARIF, enforces the accepted inventory, and uploads it to code scanning. Security property tests use a `.js` `fast-check` suite with deterministic seeds because the pinned Scorecard scanner recognizes JavaScript property testing only in `*.js`/`*.jsx`. The installed zero-argument startup smoke test also verifies that Windows creates and remembers `%USERPROFILE%\MachineBridge` while other platforms retain the package-free current directory. The macOS matrix job also runs the installation smoke test because Wrangler's optional `fsevents` resolution is platform-specific. Third-party Actions are pinned to immutable 40-character commit SHAs; architecture tests reject movable tags, and Dependabot groups GitHub Action updates into one atomic PR so coupled suites such as CodeQL cannot drift across versions. Pull-request CI also rebuilds the package and verifies the tracked repository-owner acceptance record before running install/package audit stages. Source release creation additionally requires successful push-triggered CI, CodeQL, Governance, and Scorecard runs for the exact `main` commit.
|
|
103
106
|
|
|
104
107
|
## Test design rules
|
|
105
108
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "machine-bridge-mcp",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"description": "Cross-client MCP bridge for local agent context, structured browser and application automation, files, Git, processes, resources, and durable jobs over stdio or OAuth relay.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"worker:types": "node scripts/generate-worker-types.mjs",
|
|
48
48
|
"typecheck": "npm run worker:types && tsc -p tsconfig.json --noEmit && npm run typecheck:local",
|
|
49
49
|
"syntax": "node scripts/syntax-check.mjs",
|
|
50
|
-
"check": "npm run privacy:check && npm run privacy:test && npm run release-impact:check && npm run release-impact:test && npm run release-state:test && npm run release-ci:test && npm run network-retry:test && npm run worker-deployment:test && npm run relay:test && npm run secure-file:test && npm run worker-secret-file:test && npm run sarif-security:test && npm run security-properties:test && npm run shell:test && npm run architecture:test && npm run markdown:test && npm run project-metadata:test && npm run numbers:test && npm run records:test && npm run state-inventory:test && npm run commit-message:test && npm run policy:test && npm run account:test && npm run worker-oauth-controller:test && npm run policy-docs:check && npm run tool-docs:check && npm run runtime-infrastructure:test && npm run runtime-boundaries:test && npm run runtime-handlers:test && npm run cli-entrypoint:test && npm run cli-service:test && npm run lifecycle:test && npm run logging-structure:test && npm run coverage:test && npm run worker-runtime-infrastructure:test && npm run lint:test && npm run lint && npm run typecheck && npm run syntax && npm run deadline:test && npm run process-lock:test && npm run service-lifecycle:test && npm run service-environment:test && npm run service-platform:test && npm run catalog:test && npm run agent-context:test && npm run capability-ranking:test && npm run browser-command:test && npm run browser-devtools-input:test && npm run browser-service-worker:test && npm run browser-page-automation:test && npm run browser-bridge:test && npm run app-automation:test && npm run self-test && npm run atomic-fs:test && npm run package:test && npm run install:test && npm run ssh-key:test && npm run full-access:test && npm run managed-jobs:test && npm run stdio:integration-test && npm run worker:integration-test && npm run oauth-browser:test",
|
|
50
|
+
"check": "npm run privacy:check && npm run privacy:test && npm run release-impact:check && npm run release-impact:test && npm run release:acceptance:test && npm run release-state:test && npm run release-ci:test && npm run network-retry:test && npm run worker-deployment:test && npm run relay:test && npm run secure-file:test && npm run worker-secret-file:test && npm run sarif-security:test && npm run security-properties:test && npm run shell:test && npm run architecture:test && npm run markdown:test && npm run project-metadata:test && npm run numbers:test && npm run records:test && npm run state-inventory:test && npm run commit-message:test && npm run policy:test && npm run account:test && npm run worker-oauth-controller:test && npm run policy-docs:check && npm run tool-docs:check && npm run runtime-infrastructure:test && npm run runtime-boundaries:test && npm run runtime-handlers:test && npm run cli-entrypoint:test && npm run cli-service:test && npm run lifecycle:test && npm run logging-structure:test && npm run coverage:test && npm run worker-runtime-infrastructure:test && npm run lint:test && npm run lint && npm run typecheck && npm run syntax && npm run deadline:test && npm run process-lock:test && npm run service-lifecycle:test && npm run service-environment:test && npm run service-platform:test && npm run catalog:test && npm run agent-context:test && npm run capability-ranking:test && npm run browser-command:test && npm run browser-devtools-input:test && npm run browser-service-worker:test && npm run browser-page-automation:test && npm run browser-bridge:test && npm run app-automation:test && npm run self-test && npm run atomic-fs:test && npm run package:test && npm run install:test && npm run ssh-key:test && npm run full-access:test && npm run managed-jobs:test && npm run stdio:integration-test && npm run worker:integration-test && npm run oauth-browser:test",
|
|
51
51
|
"worker:dry-run": "wrangler deploy --dry-run",
|
|
52
52
|
"worker:integration-test": "node tests/worker-integration-test.mjs",
|
|
53
53
|
"release:check": "node scripts/github-release.mjs --check",
|
|
@@ -117,12 +117,17 @@
|
|
|
117
117
|
"typecheck:local": "tsc -p tsconfig.local.json --noEmit",
|
|
118
118
|
"runtime-boundaries:test": "node tests/runtime-boundaries-test.mjs",
|
|
119
119
|
"worker-oauth-controller:test": "node tests/worker-oauth-controller-test.mjs",
|
|
120
|
-
"cli-service:test": "node tests/cli-service-test.mjs"
|
|
120
|
+
"cli-service:test": "node tests/cli-service-test.mjs",
|
|
121
|
+
"release:acceptance:test": "node tests/release-acceptance-test.mjs",
|
|
122
|
+
"release:candidate": "npm run check && node scripts/local-release-acceptance.mjs --prepare",
|
|
123
|
+
"release:accept": "node scripts/local-release-acceptance.mjs --record",
|
|
124
|
+
"release:acceptance:verify": "node scripts/local-release-acceptance.mjs --verify",
|
|
125
|
+
"github:push": "node scripts/github-push.mjs"
|
|
121
126
|
},
|
|
122
127
|
"dependencies": {
|
|
123
128
|
"https-proxy-agent": "9.1.0",
|
|
124
129
|
"proxy-from-env": "2.1.0",
|
|
125
|
-
"wrangler": "4.
|
|
130
|
+
"wrangler": "4.112.0",
|
|
126
131
|
"ws": "8.21.1"
|
|
127
132
|
},
|
|
128
133
|
"devDependencies": {
|
|
@@ -161,8 +166,8 @@
|
|
|
161
166
|
"allowScripts": {
|
|
162
167
|
"esbuild@0.28.1": true,
|
|
163
168
|
"sharp@0.34.5": true,
|
|
164
|
-
"
|
|
165
|
-
"
|
|
169
|
+
"fsevents": false,
|
|
170
|
+
"workerd@1.20260714.1": true
|
|
166
171
|
},
|
|
167
172
|
"packageManager": "npm@12.0.1",
|
|
168
173
|
"devEngines": {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { verifyCurrentReleaseAcceptance } from "./release-acceptance.mjs";
|
|
7
|
+
|
|
8
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const status = output("git", ["status", "--porcelain"]);
|
|
12
|
+
if (status) throw new Error(`working tree is not clean:\n${status}`);
|
|
13
|
+
const branch = output("git", ["branch", "--show-current"]);
|
|
14
|
+
if (!branch) throw new Error("cannot push from a detached HEAD");
|
|
15
|
+
if (branch === "main") throw new Error("direct pushes to main are prohibited; push a reviewed branch and merge through a pull request");
|
|
16
|
+
|
|
17
|
+
const acceptance = verifyCurrentReleaseAcceptance(root);
|
|
18
|
+
if (acceptance.required) {
|
|
19
|
+
const path = `release-acceptance/v${acceptance.metadata.package_version}.json`;
|
|
20
|
+
run("git", ["ls-files", "--error-unmatch", path], { capture: true });
|
|
21
|
+
console.log(`Verified repository-owner local acceptance for ${acceptance.metadata.filename}.`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
run("git", ["push", "--set-upstream", "origin", "HEAD"]);
|
|
25
|
+
console.log(`Pushed accepted branch ${branch} to origin.`);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(`GitHub push blocked: ${error?.message || error}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function output(command, args) {
|
|
32
|
+
return String(run(command, args, { capture: true }).stdout || "").trim();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function run(command, args, options = {}) {
|
|
36
|
+
const result = spawnSync(command, args, {
|
|
37
|
+
cwd: root,
|
|
38
|
+
encoding: "utf8",
|
|
39
|
+
env: process.env,
|
|
40
|
+
stdio: options.capture ? "pipe" : "inherit",
|
|
41
|
+
windowsHide: true,
|
|
42
|
+
});
|
|
43
|
+
if (result.error) throw result.error;
|
|
44
|
+
if (result.status !== 0) {
|
|
45
|
+
const detail = options.capture ? String(result.stderr || result.stdout).trim() : "";
|
|
46
|
+
throw new Error(`${command} ${args.join(" ")} failed${detail ? `: ${detail}` : ""}`);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
@@ -12,6 +12,7 @@ import { spawnSync } from "node:child_process";
|
|
|
12
12
|
import { runNetworkCommand } from "./network-retry.mjs";
|
|
13
13
|
import { requireSuccessfulWorkflowRun } from "./release-ci.mjs";
|
|
14
14
|
import { tagSyncError } from "./release-state.mjs";
|
|
15
|
+
import { verifyCurrentReleaseAcceptance } from "./release-acceptance.mjs";
|
|
15
16
|
import { fileURLToPath } from "node:url";
|
|
16
17
|
|
|
17
18
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -178,6 +179,7 @@ function releaseInfo(tag) {
|
|
|
178
179
|
|
|
179
180
|
function assertCoreSync({ requireReleaseAsset }) {
|
|
180
181
|
const pkg = packageMetadata();
|
|
182
|
+
assertLocalAcceptance();
|
|
181
183
|
const tag = `v${pkg.version}`;
|
|
182
184
|
const head = output("git", ["rev-parse", "HEAD"]);
|
|
183
185
|
const originMain = output("git", ["rev-parse", "origin/main"]);
|
|
@@ -299,18 +301,12 @@ function publishCurrent() {
|
|
|
299
301
|
run("npm", ["run", "check"]);
|
|
300
302
|
run("npm", ["run", "version:check"]);
|
|
301
303
|
ensureClean();
|
|
304
|
+
assertLocalAcceptance();
|
|
302
305
|
|
|
303
306
|
const head = output("git", ["rev-parse", "HEAD"]);
|
|
304
307
|
const originMain = output("git", ["rev-parse", "origin/main"]);
|
|
305
308
|
if (head !== originMain) {
|
|
306
|
-
|
|
307
|
-
capture: true,
|
|
308
|
-
allowFailure: true,
|
|
309
|
-
});
|
|
310
|
-
if (ancestor.status !== 0) {
|
|
311
|
-
fail("origin/main is not an ancestor of HEAD; refusing a non-fast-forward release");
|
|
312
|
-
}
|
|
313
|
-
runNetwork("git", ["push", "origin", "HEAD:main"]);
|
|
309
|
+
fail("HEAD does not match origin/main; local acceptance must be committed, pushed through npm run github:push, reviewed, and merged before release publication");
|
|
314
310
|
}
|
|
315
311
|
assertSuccessfulCi(head);
|
|
316
312
|
|
|
@@ -342,6 +338,17 @@ function publishCurrent() {
|
|
|
342
338
|
assertCoreSync({ requireReleaseAsset: true });
|
|
343
339
|
}
|
|
344
340
|
|
|
341
|
+
function assertLocalAcceptance() {
|
|
342
|
+
try {
|
|
343
|
+
const result = verifyCurrentReleaseAcceptance(root);
|
|
344
|
+
if (result.required) {
|
|
345
|
+
console.log(`Repository-owner local acceptance matches ${result.metadata.filename} (${result.metadata.shasum}).`);
|
|
346
|
+
}
|
|
347
|
+
} catch (error) {
|
|
348
|
+
fail(String(error?.message || error));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
345
352
|
function backfillMissingReleases() {
|
|
346
353
|
ensureClean();
|
|
347
354
|
fetchRemote();
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { dirname, join, resolve } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import {
|
|
12
|
+
ACCEPTANCE_CONFIRMATION,
|
|
13
|
+
ACCEPTANCE_SCHEMA_VERSION,
|
|
14
|
+
acceptancePath,
|
|
15
|
+
packProject,
|
|
16
|
+
requiresLocalAcceptance,
|
|
17
|
+
verifyAcceptanceRecord,
|
|
18
|
+
verifyCurrentReleaseAcceptance,
|
|
19
|
+
verifyTarball,
|
|
20
|
+
} from "./release-acceptance.mjs";
|
|
21
|
+
|
|
22
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
23
|
+
const candidateDirectory = join(root, ".release-candidate");
|
|
24
|
+
const candidateManifestPath = join(candidateDirectory, "manifest.json");
|
|
25
|
+
const mode = process.argv[2] || "--verify";
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (mode === "--prepare") prepareCandidate();
|
|
29
|
+
else if (mode === "--record") recordAcceptance();
|
|
30
|
+
else if (mode === "--verify") verifyAcceptance();
|
|
31
|
+
else fail("usage: node scripts/local-release-acceptance.mjs [--prepare|--record|--verify]");
|
|
32
|
+
} catch (error) {
|
|
33
|
+
fail(error?.message || error);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function prepareCandidate() {
|
|
37
|
+
const pkg = readPackage();
|
|
38
|
+
if (!requiresLocalAcceptance(pkg.version)) {
|
|
39
|
+
throw new Error(`local acceptance policy begins at ${pkg.name} 1.2.8; current version is ${pkg.version}`);
|
|
40
|
+
}
|
|
41
|
+
rmSync(candidateDirectory, { recursive: true, force: true });
|
|
42
|
+
mkdirSync(candidateDirectory, { recursive: true });
|
|
43
|
+
const metadata = packProject(root, candidateDirectory);
|
|
44
|
+
const manifest = {
|
|
45
|
+
schema_version: ACCEPTANCE_SCHEMA_VERSION,
|
|
46
|
+
result: "pending",
|
|
47
|
+
...metadata,
|
|
48
|
+
prepared_at: new Date().toISOString(),
|
|
49
|
+
};
|
|
50
|
+
writeFileSync(candidateManifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
|
|
51
|
+
const phrase = confirmationPhrase(pkg.name, pkg.version);
|
|
52
|
+
console.log(`Release candidate created: ${join(candidateDirectory, metadata.filename)}`);
|
|
53
|
+
console.log("Test this exact tarball on the maintainer machine using the documented normal startup path.");
|
|
54
|
+
console.log("After the repository owner confirms it works, record that confirmation with:");
|
|
55
|
+
console.log(`npm run release:accept -- --confirm \"${phrase}\"`);
|
|
56
|
+
console.log("Do not push this release-relevant branch before that command succeeds and its acceptance record is committed.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function recordAcceptance() {
|
|
60
|
+
const pkg = readPackage();
|
|
61
|
+
const supplied = argumentValue("--confirm");
|
|
62
|
+
const expected = confirmationPhrase(pkg.name, pkg.version);
|
|
63
|
+
if (supplied !== expected) {
|
|
64
|
+
throw new Error(`owner confirmation must exactly match: ${expected}`);
|
|
65
|
+
}
|
|
66
|
+
const pending = readJson(candidateManifestPath, "release candidate manifest");
|
|
67
|
+
if (pending.result !== "pending" || pending.package_name !== pkg.name || pending.package_version !== pkg.version) {
|
|
68
|
+
throw new Error("release candidate manifest does not match the current package");
|
|
69
|
+
}
|
|
70
|
+
verifyTarball(join(candidateDirectory, pending.filename), pending);
|
|
71
|
+
|
|
72
|
+
const verificationDirectory = join(candidateDirectory, "verification");
|
|
73
|
+
rmSync(verificationDirectory, { recursive: true, force: true });
|
|
74
|
+
mkdirSync(verificationDirectory, { recursive: true });
|
|
75
|
+
const current = packProject(root, verificationDirectory);
|
|
76
|
+
for (const key of ["package_name", "package_version", "filename", "shasum", "integrity"]) {
|
|
77
|
+
if (pending[key] !== current[key]) {
|
|
78
|
+
throw new Error(`source changed after candidate preparation: ${key} no longer matches`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const record = {
|
|
83
|
+
schema_version: ACCEPTANCE_SCHEMA_VERSION,
|
|
84
|
+
result: "passed",
|
|
85
|
+
confirmation: ACCEPTANCE_CONFIRMATION,
|
|
86
|
+
package_name: current.package_name,
|
|
87
|
+
package_version: current.package_version,
|
|
88
|
+
filename: current.filename,
|
|
89
|
+
shasum: current.shasum,
|
|
90
|
+
integrity: current.integrity,
|
|
91
|
+
accepted_at: new Date().toISOString(),
|
|
92
|
+
};
|
|
93
|
+
verifyAcceptanceRecord(record, current);
|
|
94
|
+
const path = acceptancePath(root, pkg.version);
|
|
95
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
96
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
97
|
+
console.log(`Local owner acceptance recorded: ${path}`);
|
|
98
|
+
console.log("Commit this record with the candidate. Any packaged-file change invalidates it.");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function verifyAcceptance() {
|
|
102
|
+
const result = verifyCurrentReleaseAcceptance(root);
|
|
103
|
+
if (!result.required) {
|
|
104
|
+
console.log(`Local release acceptance is not required for ${result.version}.`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
console.log(`Local release acceptance matches ${result.metadata.filename} (${result.metadata.shasum}).`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function readPackage() {
|
|
111
|
+
return readJson(join(root, "package.json"), "package.json");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function readJson(path, label) {
|
|
115
|
+
try {
|
|
116
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
117
|
+
} catch (error) {
|
|
118
|
+
throw new Error(`${label} is unavailable or invalid: ${error.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function argumentValue(name) {
|
|
123
|
+
const exact = process.argv.find((value) => value.startsWith(`${name}=`));
|
|
124
|
+
if (exact) return exact.slice(name.length + 1);
|
|
125
|
+
const index = process.argv.indexOf(name);
|
|
126
|
+
return index >= 0 ? process.argv[index + 1] : "";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function confirmationPhrase(name, version) {
|
|
130
|
+
return `I TESTED ${name} ${version} LOCALLY AND IT WORKS`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function fail(message) {
|
|
134
|
+
console.error(`local release acceptance failed: ${message}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
lstatSync,
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
|
|
12
|
+
export const ACCEPTANCE_SCHEMA_VERSION = 1;
|
|
13
|
+
export const ACCEPTANCE_POLICY_VERSION = "1.2.8";
|
|
14
|
+
export const ACCEPTANCE_CONFIRMATION = "repository-owner-local-test";
|
|
15
|
+
const MAX_ACCEPTANCE_BYTES = 64 * 1024;
|
|
16
|
+
|
|
17
|
+
export function requiresLocalAcceptance(version) {
|
|
18
|
+
return compareVersions(parseVersion(version), parseVersion(ACCEPTANCE_POLICY_VERSION)) >= 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function acceptancePath(root, version) {
|
|
22
|
+
return join(root, "release-acceptance", `v${version}.json`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function packProject(root, destination) {
|
|
26
|
+
const npmCli = process.env.npm_execpath;
|
|
27
|
+
if (!npmCli) {
|
|
28
|
+
throw new Error("release acceptance commands must run through npm so npm_execpath is available");
|
|
29
|
+
}
|
|
30
|
+
const result = spawnSync(process.execPath, [
|
|
31
|
+
npmCli,
|
|
32
|
+
"pack",
|
|
33
|
+
"--ignore-scripts",
|
|
34
|
+
"--silent",
|
|
35
|
+
"--json",
|
|
36
|
+
"--pack-destination",
|
|
37
|
+
destination,
|
|
38
|
+
], {
|
|
39
|
+
cwd: root,
|
|
40
|
+
encoding: "utf8",
|
|
41
|
+
env: process.env,
|
|
42
|
+
windowsHide: true,
|
|
43
|
+
});
|
|
44
|
+
if (result.error) throw result.error;
|
|
45
|
+
if (result.status !== 0) {
|
|
46
|
+
throw new Error(`npm pack failed: ${String(result.stderr || result.stdout).trim()}`);
|
|
47
|
+
}
|
|
48
|
+
let value;
|
|
49
|
+
try {
|
|
50
|
+
value = JSON.parse(result.stdout);
|
|
51
|
+
} catch {
|
|
52
|
+
throw new Error("npm pack did not return valid JSON");
|
|
53
|
+
}
|
|
54
|
+
const pkg = readPackage(root);
|
|
55
|
+
const record = normalizePackRecord(value, pkg.name);
|
|
56
|
+
if (!record) throw new Error("npm pack did not return package metadata");
|
|
57
|
+
const metadata = {
|
|
58
|
+
package_name: pkg.name,
|
|
59
|
+
package_version: pkg.version,
|
|
60
|
+
filename: String(record.filename || ""),
|
|
61
|
+
shasum: String(record.shasum || ""),
|
|
62
|
+
integrity: String(record.integrity || ""),
|
|
63
|
+
};
|
|
64
|
+
validatePackMetadata(metadata);
|
|
65
|
+
verifyTarball(join(destination, metadata.filename), metadata);
|
|
66
|
+
return metadata;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function readAcceptance(root, version) {
|
|
70
|
+
const path = acceptancePath(root, version);
|
|
71
|
+
const stat = lstatSync(path);
|
|
72
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
73
|
+
throw new Error(`release acceptance record must be a regular file: ${path}`);
|
|
74
|
+
}
|
|
75
|
+
if (stat.size > MAX_ACCEPTANCE_BYTES) {
|
|
76
|
+
throw new Error(`release acceptance record exceeds ${MAX_ACCEPTANCE_BYTES} bytes`);
|
|
77
|
+
}
|
|
78
|
+
let value;
|
|
79
|
+
try {
|
|
80
|
+
value = JSON.parse(readFileSync(path, "utf8"));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
throw new Error(`release acceptance record is not valid JSON: ${error.message}`);
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function verifyAcceptanceRecord(record, metadata) {
|
|
88
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
89
|
+
throw new Error("release acceptance record must be an object");
|
|
90
|
+
}
|
|
91
|
+
if (record.schema_version !== ACCEPTANCE_SCHEMA_VERSION) {
|
|
92
|
+
throw new Error(`unsupported release acceptance schema: ${record.schema_version}`);
|
|
93
|
+
}
|
|
94
|
+
if (record.result !== "passed") throw new Error("local release acceptance result is not passed");
|
|
95
|
+
if (record.confirmation !== ACCEPTANCE_CONFIRMATION) {
|
|
96
|
+
throw new Error("local release acceptance confirmation is missing");
|
|
97
|
+
}
|
|
98
|
+
const acceptedAt = Date.parse(String(record.accepted_at || ""));
|
|
99
|
+
if (!Number.isFinite(acceptedAt)) throw new Error("local release acceptance timestamp is invalid");
|
|
100
|
+
for (const key of ["package_name", "package_version", "filename", "shasum", "integrity"]) {
|
|
101
|
+
if (record[key] !== metadata[key]) {
|
|
102
|
+
throw new Error(`local release acceptance ${key} does not match the current npm package`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return record;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function verifyCurrentReleaseAcceptance(root) {
|
|
109
|
+
const pkg = readPackage(root);
|
|
110
|
+
if (!requiresLocalAcceptance(pkg.version)) {
|
|
111
|
+
return { required: false, version: pkg.version };
|
|
112
|
+
}
|
|
113
|
+
const temp = mkdtempSync(join(tmpdir(), "mbm-release-acceptance-"));
|
|
114
|
+
try {
|
|
115
|
+
const metadata = packProject(root, temp);
|
|
116
|
+
const record = readAcceptance(root, pkg.version);
|
|
117
|
+
verifyAcceptanceRecord(record, metadata);
|
|
118
|
+
return { required: true, metadata, record };
|
|
119
|
+
} finally {
|
|
120
|
+
rmSync(temp, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function verifyTarball(path, metadata) {
|
|
125
|
+
const stat = lstatSync(path);
|
|
126
|
+
if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("release candidate tarball is not a regular file");
|
|
127
|
+
const bytes = readFileSync(path);
|
|
128
|
+
const shasum = createHash("sha1").update(bytes).digest("hex");
|
|
129
|
+
const integrity = `sha512-${createHash("sha512").update(bytes).digest("base64")}`;
|
|
130
|
+
if (shasum !== metadata.shasum || integrity !== metadata.integrity) {
|
|
131
|
+
throw new Error("release candidate tarball hash does not match npm pack metadata");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function normalizePackRecord(value, packageName) {
|
|
136
|
+
if (Array.isArray(value)) return value[0] ?? null;
|
|
137
|
+
if (!value || typeof value !== "object") return null;
|
|
138
|
+
if (value[packageName] && typeof value[packageName] === "object") return value[packageName];
|
|
139
|
+
return Object.values(value).find((item) => item && typeof item === "object") ?? null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function readPackage(root) {
|
|
143
|
+
const value = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
144
|
+
if (typeof value.name !== "string" || !value.name) throw new Error("package.json name is invalid");
|
|
145
|
+
if (!parseVersion(value.version)) throw new Error("package.json version is invalid");
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function validatePackMetadata(metadata) {
|
|
150
|
+
if (!metadata.filename.endsWith(".tgz") || metadata.filename.includes("/") || metadata.filename.includes("\\")) {
|
|
151
|
+
throw new Error("npm pack returned an invalid filename");
|
|
152
|
+
}
|
|
153
|
+
if (!/^[0-9a-f]{40}$/.test(metadata.shasum)) throw new Error("npm pack returned an invalid SHA-1 shasum");
|
|
154
|
+
if (!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(metadata.integrity)) throw new Error("npm pack returned an invalid SHA-512 integrity value");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseVersion(value) {
|
|
158
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/.exec(String(value || ""));
|
|
159
|
+
if (!match) return null;
|
|
160
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function compareVersions(left, right) {
|
|
164
|
+
if (!left || !right) throw new Error("version comparison requires semantic versions");
|
|
165
|
+
for (let index = 0; index < 3; index += 1) {
|
|
166
|
+
if (left[index] !== right[index]) return left[index] - right[index];
|
|
167
|
+
}
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
@@ -25,20 +25,36 @@ const changed = new Set([
|
|
|
25
25
|
...lines(git(["ls-files", "--others", "--exclude-standard"])),
|
|
26
26
|
]);
|
|
27
27
|
const relevant = [...changed].sort();
|
|
28
|
+
const packageRelevant = relevant.filter((path) => isPackageRelevant(path, pkg.files));
|
|
28
29
|
|
|
29
30
|
if (!relevant.length) {
|
|
30
|
-
process.stderr.write(`release impact check ok: no
|
|
31
|
+
process.stderr.write(`release impact check ok: no repository changes since ${latestTag}\n`);
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
if (!packageRelevant.length) {
|
|
35
|
+
process.stderr.write(`release impact check ok: ${relevant.length} repository-only file(s) changed since ${latestTag}; npm package content is unchanged\n`);
|
|
31
36
|
process.exit(0);
|
|
32
37
|
}
|
|
33
38
|
if (compareVersions(current, latest) <= 0) {
|
|
34
|
-
fail(`
|
|
39
|
+
fail(`npm-package changes exist since ${latestTag}, but package.json is still ${pkg.version}; bump the npm version and add a CHANGELOG section before merging`);
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
const changelog = readFileSync(new URL("../CHANGELOG.md", import.meta.url), "utf8");
|
|
38
43
|
const heading = new RegExp(`^## ${escapeRegExp(pkg.version)}(?:\\s+-[^\\n]*)?$`, "m");
|
|
39
44
|
if (!heading.test(changelog)) fail(`CHANGELOG.md has no section for ${pkg.version}`);
|
|
40
45
|
|
|
41
|
-
process.stderr.write(`release impact check ok: ${
|
|
46
|
+
process.stderr.write(`release impact check ok: ${packageRelevant.length} npm-package file(s) changed since ${latestTag}; next npm version ${pkg.version}\n`);
|
|
47
|
+
|
|
48
|
+
export function isPackageRelevant(path, packageFiles = []) {
|
|
49
|
+
const normalized = String(path || "").replaceAll("\\", "/").replace(/^\.\//, "");
|
|
50
|
+
if (!normalized) return false;
|
|
51
|
+
if (["package.json", "package-lock.json", "npm-shrinkwrap.json"].includes(normalized)) return true;
|
|
52
|
+
for (const entry of packageFiles || []) {
|
|
53
|
+
const candidate = String(entry || "").replaceAll("\\", "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
54
|
+
if (candidate && (normalized === candidate || normalized.startsWith(`${candidate}/`))) return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
42
58
|
|
|
43
59
|
function git(args) {
|
|
44
60
|
try {
|
package/src/worker/index.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from "./http.ts";
|
|
28
28
|
|
|
29
29
|
const SERVER_NAME = String(serverMetadata.name);
|
|
30
|
-
const SERVER_VERSION = "1.2.
|
|
30
|
+
const SERVER_VERSION = "1.2.8";
|
|
31
31
|
const MCP_PROTOCOL_VERSION = String(serverMetadata.protocolVersion);
|
|
32
32
|
const MCP_SUPPORTED_PROTOCOL_VERSIONS = serverMetadata.supportedProtocolVersions.map((value) => String(value));
|
|
33
33
|
const JSONRPC_VERSION = "2.0";
|