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: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ jobs:
--redact --no-banner --config .gitleaks.toml .

npm-install:
name: npm install (${{ matrix.os }}, Node 22.0.0)
name: npm install (${{ matrix.os }}, Node 22.11.0)
strategy:
fail-fast: false
matrix:
Expand All @@ -118,7 +118,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22.0.0"
node-version: "22.11.0"
cache: npm
- run: npm ci
- run: npm run build
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/npm-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
ref: ${{ github.event.release.tag_name }}
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22.0.0"
node-version: "22.11.0"
package-manager-cache: false
- name: Install and execute the exact npm release
run: node scripts/check-npm-install.mjs --spec "screenrig@${{ needs.publish.outputs.version }}"
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ notes and examples live in `src/cli-commands/guidance.ts`.

## Product and security boundaries

- Package metadata requires Node.js 22 or newer.
- Package metadata requires Node.js 22.11 or newer.
- `agent enroll --email ADDRESS` is the explicit first-agent step. Other
authenticated commands do not enroll as a side effect; they fail with
`not_enrolled` (exit 3). Keep that code stable.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ npm install --global screenrig@<YY.MM.SERIAL>
screenrig version
```

Node.js 22 or newer is required. `media upload` additionally requires ffmpeg
Node.js 22.11 or newer is required. `media upload` additionally requires ffmpeg
and ffprobe, as do batch uploads and playlist preparation from local media files. Run `screenrig doctor` to inspect
the optional media toolchain before an upload.

Expand Down
2 changes: 1 addition & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ fetch.
and attach the offline archive. It rejects a tag that is not `vYY.MM.N`.
5. Require all Linux, macOS, and Windows clean-install jobs to pass. Each installs
the exact registry version and runs `screenrig --json version` plus
`screenrig --json compose catalog` on Node 22.0.0.
`screenrig --json compose catalog` on Node 22.11.0.
6. Confirm the workflow attached `screenrig-cli.tgz` and its SHA-256 file to the
same GitHub release.
7. Verify the npm package page shows provenance before announcing availability.
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"!dist/transport/fake.d.ts.map"
],
"engines": {
"node": ">=22"
"node": ">=22.11"
},
"packageManager": "npm@10.9.4",
"scripts": {
Expand Down
12 changes: 9 additions & 3 deletions scripts/check-npm-install.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ import { PLACEHOLDER_VERSION, isReleaseVersion, resolvePackVersion, versionFromT

const root = fileURLToPath(new URL("..", import.meta.url));
const packageJson = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"));
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
// Windows command shims cannot be spawned without a shell. Invoke npm's JS
// entry point with this Node runtime so paths and arguments need no shell quoting.
const npm = process.platform === "win32" ? process.execPath : "npm";
const npmArgs = process.platform === "win32"
? [process.env.npm_execpath || path.join(path.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")]
: [];

function run(args, options = {}) {
const result = spawnSync(npm, args, { encoding: "utf8", ...options });
const result = spawnSync(npm, [...npmArgs, ...args], { encoding: "utf8", ...options });
if (result.error) throw result.error;
if (result.status !== 0) {
throw new Error(`${npm} ${args.join(" ")}: ${result.stderr.trim() || result.stdout.trim()}`);
throw new Error(`npm ${args.join(" ")}: ${result.stderr?.trim() || result.stdout?.trim() || `exit ${result.status}`}`);
}
return result;
}
Expand Down
7 changes: 5 additions & 2 deletions scripts/check-public-repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def check_metadata(errors: list[str]) -> None:
"version": PLACEHOLDER_VERSION,
"private": False,
"license": "Apache-2.0",
"engines": {"node": ">=22.11"},
}
for field, value in expected.items():
if package.get(field) != value:
Expand Down Expand Up @@ -81,7 +82,7 @@ def check_metadata(errors: list[str]) -> None:
errors.append("commands.ts must not freeze CLI_VERSION to the committed placeholder")

root_lock = (lock.get("packages") or {}).get("") or {}
for field in ("name", "version", "license"):
for field in ("name", "version", "license", "engines"):
if root_lock.get(field) != package.get(field):
errors.append(f"package-lock.json root {field!r} drifts from package.json")

Expand All @@ -101,7 +102,8 @@ def check_public_tree(errors: list[str]) -> None:
"npm run check",
"npm audit --audit-level=high --package-lock-only",
"name: screenrig-cli",
"name: npm install (${{ matrix.os }}, Node 22.0.0)",
"name: npm install (${{ matrix.os }}, Node 22.11.0)",
'node-version: "22.11.0"',
"os: [ubuntu-24.04, macos-14, windows-2022]",
"npm run check:npm-install",
"gitleaks\" git",
Expand Down Expand Up @@ -144,6 +146,7 @@ def check_public_tree(errors: list[str]) -> None:
"npm publish --access public",
"screenrig@${{ needs.publish.outputs.version }}",
"ubuntu-24.04, macos-14, windows-2022",
'node-version: "22.11.0"',
"gh release upload",
):
if fact not in npm_release:
Expand Down
29 changes: 29 additions & 0 deletions src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2604,6 +2604,35 @@ test("events follow --timeout exits during backoff without hanging", async () =>
await rm(configDir, { recursive: true, force: true });
});

test("doctor requires Node.js 22.11 or newer", async () => {
const descriptor = Object.getOwnPropertyDescriptor(process.versions, "node")!;
try {
for (const [version, status] of [
["20.19.0", "fail"],
["22.0.0", "fail"],
["22.10.0", "fail"],
["22.11.0", "pass"],
["22.11.1", "pass"],
["22.12.0", "pass"],
["23.0.0", "pass"],
["24.0.0", "pass"],
]) {
Object.defineProperty(process.versions, "node", { ...descriptor, value: version });
resetFfmpegToolchainCache();
const { code, stdout, configDir } = await withRuntime(["--json", "doctor"], memoryBackend(), { runProcess: fullToolchainProbe() });
try {
const envelope = JSON.parse(stdout) as { data: { checks: Array<{ name: string; status: string }> } };
assert.equal(envelope.data.checks.find((check) => check.name === "node")?.status, status, version);
assert.equal(code === ExitCode.Success, status === "pass", version);
} finally {
await rm(configDir, { recursive: true, force: true });
}
}
} finally {
Object.defineProperty(process.versions, "node", descriptor);
}
});

test("doctor reports checks over the published foundation routes", async () => {
resetFfmpegToolchainCache();
const transport = memoryBackend();
Expand Down
4 changes: 2 additions & 2 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5985,10 +5985,10 @@ async function doctor(
resolved: Awaited<ReturnType<typeof resolveConfig>>,
): Promise<CommandResult> {
const checks: DoctorCheck[] = [];
const nodeMajor = Number(process.versions.node.split(".")[0]);
const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split(".").map(Number);
checks.push({
name: "node",
status: nodeMajor >= 22 ? "pass" : "fail",
status: nodeMajor > 22 || (nodeMajor === 22 && nodeMinor >= 11) ? "pass" : "fail",
detail: `node ${process.versions.node}`,
});
checks.push({
Expand Down
Loading