Skip to content

Commit 7ce863e

Browse files
authored
ci: stabilize dependency updates and packed fixture versions (#60)
1 parent 245abaa commit 7ce863e

6 files changed

Lines changed: 85 additions & 13 deletions

File tree

‎.github/ACTIONLINT.md‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,49 @@ ShellCheck is available on the host. A passing local run does not emulate
2020
GitHub-hosted runners, exercise repository settings, prove secret or environment
2121
configuration, perform a live CometAPI request, or prove npm Trusted Publishing.
2222
Those remain separate remote evidence.
23+
24+
## Dependency update review
25+
26+
Dependabot keeps production updates on their existing compatibility policy and
27+
limits development version updates to minor and patch releases. Toolchain
28+
major upgrades need a coordinated change that verifies compiler, linter,
29+
bundler, test runner, and Node.js type compatibility with `npm ci` and
30+
`npm run verify`. Do not use `--force` or `--legacy-peer-deps` to suppress peer
31+
dependency conflicts. Security updates remain eligible; the version-update
32+
filter does not suppress them. See the official
33+
[Dependabot allow reference](https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#allow).
34+
35+
Packed fixtures declare the supported OpenAI range and select the exact install
36+
version from the root lock file at runtime. Update their source declarations
37+
only when intentionally changing the minimum supported range; lock-file patch
38+
updates do not require copying the new version into each fixture.
39+
40+
GitHub Actions version updates are grouped into one review. Every action
41+
remains pinned to a full commit SHA. The Publish contract additionally pins the
42+
parsed executable workflow with `PUBLISH_WORKFLOW_CONTRACT_SHA256` in
43+
`scripts/release-workflow-validation.mjs`. An action SHA change in
44+
`publish.yml` must fail that contract until a maintainer reviews the upstream
45+
action changes and updates the digest in the same change. This is an expected
46+
release safety check, not a transient failure to retry.
47+
48+
After reviewing the complete Publish workflow diff and each action's upstream
49+
changes, calculate the candidate digest from the repository root:
50+
51+
```bash
52+
node --input-type=module <<'EOF'
53+
import { createHash } from "node:crypto";
54+
import { readFileSync } from "node:fs";
55+
import { parse } from "yaml";
56+
57+
const workflow = parse(readFileSync(".github/workflows/publish.yml", "utf8"));
58+
console.log(createHash("sha256").update(JSON.stringify(workflow)).digest("hex"));
59+
EOF
60+
```
61+
62+
Update the constant only after confirming that the reviewed action pins and
63+
all release gates remain correct. Run `npm run verify` to cover the workflow
64+
mutation regressions and pinned `actionlint` validation. Do not remove the
65+
digest check or relax release permissions, immutable-tag checks, environment
66+
approvals, live request budgets, or publication replay limits. Existing open
67+
Dependabot branches retain their old contents until refreshed or replaced;
68+
changing the update policy does not repair those branches by itself.

‎.github/dependabot.yml‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ updates:
99
time: "04:00"
1010
timezone: Etc/UTC
1111
open-pull-requests-limit: 10
12+
allow:
13+
- dependency-type: production
14+
# Toolchain majors require a coordinated peer-dependency migration.
15+
# This filter affects version updates, not security updates.
16+
- dependency-type: development
17+
update-types:
18+
- version-update:semver-minor
19+
- version-update:semver-patch
1220
groups:
1321
production-dependencies:
1422
dependency-type: production
@@ -27,3 +35,7 @@ updates:
2735
time: "04:30"
2836
timezone: Etc/UTC
2937
open-pull-requests-limit: 10
38+
groups:
39+
github-actions:
40+
patterns:
41+
- "*"

‎fixtures/commonjs/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
"test": "node index.cjs"
66
},
77
"dependencies": {
8-
"openai": "6.47.0"
8+
"openai": "^6.47.0"
99
}
1010
}

‎fixtures/esm/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
"test": "node index.mjs"
77
},
88
"dependencies": {
9-
"openai": "6.47.0"
9+
"openai": "^6.47.0"
1010
}
1111
}

‎fixtures/openai-host/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"typecheck": "tsc --noEmit --project tsconfig.json && tsc --noEmit --project tsconfig.nonexact.json"
88
},
99
"dependencies": {
10-
"openai": "6.47.0"
10+
"openai": "^6.47.0"
1111
},
1212
"devDependencies": {
1313
"typescript": "5.9.3"

‎scripts/test-fixtures.mjs‎

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import assert from "node:assert/strict";
2-
import { cpSync, existsSync } from "node:fs";
2+
import { cpSync, existsSync, writeFileSync } from "node:fs";
33
import { basename, join, resolve } from "node:path";
44

55
import {
@@ -23,15 +23,9 @@ if (tarballArgument !== -1 && !suppliedTarball) {
2323
const lock = readJSON(join(ROOT, "package-lock.json"));
2424
const lockedOpenAIVersion = lock.packages["node_modules/openai"]?.version;
2525
assert.ok(lockedOpenAIVersion, "package-lock.json must lock OpenAI");
26-
for (const fixture of ["esm", "commonjs", "openai-host"]) {
27-
const manifest = readJSON(join(ROOT, "fixtures", fixture, "package.json"));
28-
assert.equal(
29-
manifest.dependencies?.openai,
30-
lockedOpenAIVersion,
31-
`${fixture} must pin the locked OpenAI version`,
32-
);
33-
}
34-
26+
const supportedOpenAIRange = readJSON(join(ROOT, "package.json")).dependencies
27+
?.openai;
28+
assert.ok(supportedOpenAIRange, "package.json must declare OpenAI support");
3529
try {
3630
const tarball =
3731
(suppliedTarball ? resolve(ROOT, suppliedTarball) : undefined) ??
@@ -42,11 +36,31 @@ try {
4236
cpSync(join(ROOT, "fixtures", fixture), fixtureDirectory, {
4337
recursive: true,
4438
});
39+
const manifestPath = join(fixtureDirectory, "package.json");
40+
const manifest = readJSON(manifestPath);
41+
assert.equal(
42+
manifest.dependencies?.openai,
43+
supportedOpenAIRange,
44+
`${fixture} must declare the supported OpenAI range before installation`,
45+
);
46+
manifest.dependencies.openai = lockedOpenAIVersion;
47+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
4548
run(
4649
"npm",
4750
["install", "--ignore-scripts", "--no-audit", "--no-fund", tarball],
4851
{ cwd: fixtureDirectory },
4952
);
53+
const dependencyTree = JSON.parse(
54+
run("npm", ["ls", "openai", "--all", "--json"], {
55+
capture: true,
56+
cwd: fixtureDirectory,
57+
}),
58+
);
59+
assert.equal(
60+
dependencyTree.dependencies?.openai?.version,
61+
lockedOpenAIVersion,
62+
`${fixture} must install the locked OpenAI version`,
63+
);
5064

5165
if (fixture === "openai-host") {
5266
const nestedOpenAI = join(

0 commit comments

Comments
 (0)