Conversation
9afc346 to
4a637b1
Compare
MasselinkJ
left a comment
There was a problem hiding this comment.
[Kiro]: Reviewed by cloning the branch, running pnpm install --frozen-lockfile / lint / test / build locally, and diffing the approach against Alliander/cp-aws-sso-config, Alliander/cp-gh-actions-ami-builder and the shared cp-gh-shared-actions they consume.
First, the parts I checked that hold up, because this PR makes a lot of specific claims and most of them are accurate:
- All the version pins resolve.
aws-cdk@3.0.0really is deprecated with exactly the quoted message and really does still hold thelatestdist-tag. - jsii 6.0.12 depends on
typescript: ~6.0and jsii 5.9.53 on~5.9, so both the version-coupling argument and the pre-existing mismatch are real. - The bundled
@jsii/check-node@1.140.0lists 20/22/24 as supported. A real build on Node 26 printed exactly the predicted warning (^24.0.0,^22.0.0,^20.0.0 [DEPRECATED]), so pinning CI to 24 is right. nodeLinker: hoistedgenuinely is required forbundleDependencies; theBUNDLED_DEPENDENCIES_WITHOUT_HOISTEDguard is real, not a preference.- The
minimumReleaseAgereasoning is correct for pnpm 11.25.0. Full lockfile re-validation landed in 11.1.3 (pnpm#10438, fixed by pnpm#11583), and my install printed✓ Lockfile passes supply-chain policies (462 entries in 16.4s). So the 3-days-not-7 rationale is verified rather than assumed. - pnpm does implement OIDC trusted publishing natively, so
NODE_AUTH_TOKEN: ''plusid-token: writeis a valid setup even though pnpm 11 dropped the npm CLI delegation. It is absent from thepnpm publishdocs, so a--dry-runon the first release would be worth it. - The lockfile carries no registry URLs, so "don't commit a registry" really does avoid churn between the Artifactory and public paths.
- Both README fixes match the source, and dropping
npxfrombuild.shis a real improvement. - Install, test and build all pass: 40/40 tests, 100% coverage, jsii 0 errors / 0 warnings.
Four things to look at, three inline below plus this one.
Renovate / Dependabot
There is no renovate.json or .github/dependabot.yml in the repo. Both reference repos ship renovate.json, and this PR makes the gap matter more than it did before:
- Every action is now hash-pinned, which is the right call, but hash pins do not update themselves. Without update tooling they rot silently and the repo drifts onto old action versions with no signal.
- Because
minimumReleaseAgeis now set explicitly,minimumReleaseAgeStrictauto-enables (pnpm 11.1.3+). In CI, which is non-TTY, an immature pick aborts withERR_PNPM_NO_MATURE_MATCHING_VERSIONrather than auto-excluding. So a bot bumping a dependency inside the 3-day window produces a hard CI failure, and whoever picks it up needs to know thatminimumReleaseAgeExcludeis the intended escape hatch and not to lower the window. minimumReleaseAgealso interacts with bot scheduling: Renovate's default is to open PRs as soon as a release appears, which is inside the cooldown by construction. Worth pairing the config withminimumReleaseAgein Renovate itself so the two agree, otherwise most bot PRs land red.
Copying the reference repos' renovate.json and adding a note in CONTRIBUTING.md about the cooldown interaction would close this. Fine as a follow-up rather than a blocker, as long as it does not get lost.
One unrelated note: the commit message is better than the PR description. It has a whole "Registry" section explaining the fallback design and the minimumReleaseAge choice that never made it into the PR body, and that is the context a reviewer needs most.
| # protection rule first if you restructure this. | ||
| pre-commit: | ||
| runs-on: ubuntu-latest | ||
| runs-on: self-hosted |
There was a problem hiding this comment.
[Kiro]: self-hosted on a public repo with a pull_request trigger.
The two repos this was modelled on are both private (I confirmed via the API); aws-jwt-sts is public. That is the case GitHub explicitly advises against: forks of a public repo can run code on your runner just by opening a pull request. It is also a regression, since this job ran on ubuntu-latest before this PR.
What makes it concrete rather than theoretical is that a fork controls the very files doing the hardening. pnpm-workspace.yaml is checked out from the PR head, so a fork can set allowBuilds: {anything: true} or dangerouslyAllowAllBuilds, and pnpm install --frozen-lockfile then runs their build scripts on Alliander infrastructure. The supply-chain hardening in this PR protects maintainers installing locally; on the fork path the attacker supplies the policy, so it protects nothing there.
It compounds with build-and-publish.yml, which also runs self-hosted and holds id-token: write for npm trusted publishing. Fork PR code and the job that can publish @alliander-opensource/aws-jwt-sts share a runner pool, so anything a PR leaves behind in $HOME, the tool cache, or on PATH is reachable by the release job.
Suggest ubuntu-latest here at minimum, and preferably for the release job too. Once the Artifactory branch is settled (see my other comment) nothing in either job needs Alliander-internal network access, so there is no reason to be on self-hosted. If self-hosted has to stay, it needs ephemeral runners plus "Require approval for all outside collaborators" on fork PRs. I could not check that setting myself, the Actions permissions API returns 403 for my token.
There was a problem hiding this comment.
dit komt vanuit max-glaser maar bleek niet te kloppen
There was a problem hiding this comment.
[kiro]: Agreed, and changed in c24117f - both pr.yml and build-and-publish.yml are back on ubuntu-latest.
One thing to add, because it makes this more than a hardening argument: it was also non-functional. GET /repos/.../actions/runners returns total_count: 0, and the pre-commit job on this branch sat queued for five hours with nothing picking it up. Since branch protection requires a check named exactly pre-commit, that check could never report and the PR could not have merged. Every earlier run on the branch is cancelled or queued, none completed.
So the fork-PR exposure you described was the right call, and the practical effect was simply that CI never ran.
Your point about the attacker supplying the policy is the load-bearing one: pnpm-workspace.yaml is checked out from the PR head, so a fork controls allowBuilds, which is what makes pnpm install --frozen-lockfile on our infrastructure a concrete concern rather than a theoretical one. The release job carries the separate reason in a comment at the top of its job block, since it holds id-token: write and must not share a pool with fork-supplied code.
Two caveats, both of which now live only in this thread:
- GitHub-hosted runners still need enabling at the organisation level for this repository. Until they are, the job stays queued, which looks identical to a hung build.
- I could not verify the "Require approval for all outside collaborators" setting either, since the Actions permissions API returns 403 for my token too. Moot while both jobs are hosted, but worth confirming if self-hosted ever comes back.
Correction to an earlier version of this reply. It said both of those were recorded as comments in pr.yml. They were, in c24117f, but 4541611 trimmed the workflow comments back and they are gone. The reasoning survives here and in CONTRIBUTING.md; the org-level runner caveat does not appear in pr.yml any more, so if that check is ever silently not running, this thread is the only pointer.
| # authenticated before it runs. | ||
| - name: select npm registry | ||
| env: | ||
| ARTIFACTORY_TOKEN: ${{ secrets.ARTIFACTORY_TOKEN }} |
There was a problem hiding this comment.
[Kiro]: Long-lived secret where the org pattern uses a short-lived OIDC token.
Credit first: this select npm registry step is a faithful reproduction of the "Configure npm" step in cp-gh-shared-actions/actions/setup-artifactory — same ~/.npmrc layout, same ${NODE_AUTH_TOKEN} indirection so the token is not written into the file, same umask 077, and the same ordering constraint ahead of pnpm/action-setup because its self-installer fetches @pnpm/exe first. That reasoning is all correct.
What did not carry over is the credential model. The shared action mints a short-lived token through Alliander/get-artifactory-credentials over OIDC, and describes itself as "the single place that obtains Artifactory credentials". This step substitutes a long-lived static secret, on a public repository, and exports it via $GITHUB_ENV — which puts it in the environment of every later step: pnpm install, lint, test, build, including esbuild's postinstall that allowBuilds deliberately permits.
The stated reason for inlining is accurate: GitHub does not let a public repo consume actions from private or internal repos, and both cp-gh-shared-actions and get-artifactory-credentials are private. But "cannot call the shared action" does not have to mean "replace OIDC with a static secret".
Worth asking whether the Artifactory path belongs in this repo's CI at all. Fork PRs never receive the secret, so the public-registry fallback is already the load-bearing path for external contributors, and it demonstrably works. Dropping the Artifactory branch would remove the secret, roughly 40 lines of shell across both workflows, and all the ~/.npmrc handling. If it stays, scope the credential with a step-level env: on the install step instead of $GITHUB_ENV.
Two smaller notes on this block:
- The comment a few lines down says the token is "never written to disk". True of
.npmrc, but$GITHUB_ENVis a file on disk. - The fallback branch writes
registry=https://registry.npmjs.org/into the shared runner's~/.npmrcand never cleans it up, and the publish step does the same. The org's shared action only ever writes the mirror there, and pairs withcleanup-artifactoryonif: always(). A later job on the same runner inheriting that file would silently resolve from the public registry, bypassing the mirror's ~7 day cooldown. Pointingnpm_config_userconfigat$RUNNER_TEMPavoids this. (The file holds the${NODE_AUTH_TOKEN}placeholder rather than the token itself, so this is config pollution, not credential leakage.)
There was a problem hiding this comment.
[kiro]: Taken, and gone further than scoping the env: - the whole Artifactory branch is removed from both workflows in c24117f.
What decided it: the credential does not exist. GET /repos/.../actions/secrets returns none, GET /repos/.../actions/organization-secrets returns total_count: 0, and there are no environments. So secrets.ARTIFACTORY_TOKEN was always empty and only the else branch ever executed. The supply-chain hardening was guarding a path nothing took, and the static-secret exposure you describe was latent rather than active - but it would have become active the moment someone added the secret, which is the worst version of that.
That also settles your "worth asking whether the Artifactory path belongs in this repo's CI at all": the public-registry fallback was not a fallback, it was the only path. Removing the branch drops roughly 40 lines of shell, both ~/.npmrc writes and the $GITHUB_ENV export.
Your two smaller notes were both correct and are both resolved by the removal:
- The "never written to disk" comment was wrong,
$GITHUB_ENVis a file. Comment deleted rather than reworded. - The uncleaned
~/.npmrcis gone from both workflows. On a hosted runner it would have been moot anyway since the filesystem is ephemeral, but the write no longer exists.
The design intent is not lost. CONTRIBUTING.md now records the three constraints for wiring the mirror into CI if the credential does appear: short-lived and obtained per run over OIDC in the way get-artifactory-credentials does it rather than a static secret; scoped with a step-level env: on the install step rather than $GITHUB_ENV; and the public path stays the one fork pull requests take, so external contributions keep working. Your reading of why the shared action cannot be consumed here is accurate and is recorded alongside it.
minimumReleaseAge is now documented as the primary control rather than a backstop, since every CI install takes the public path.
Still open from your review: the pnpm publish --dry-run on the first release. Worth doing, since pnpm's OIDC trusted publishing is absent from its docs as you noted, so the first tag is the wrong place to discover a problem.
| "test": "jest --coverage", | ||
| "cdk": "cdk", | ||
| "lint": "npx oxlint ." | ||
| "lint": "oxlint ." |
There was a problem hiding this comment.
[Kiro]: The new lint gate cannot fail.
This PR promotes pnpm run lint to a gating step in both workflows, which is a good change, but as written it can never fail. Two independent reasons, both reproduced on this branch:
- The config is never loaded. The file is
oxlintrc.json, with no leading dot; oxlint only auto-discovers.oxlintrc.json. I planted adebuggerstatement insrc/index.tsand toggled only the filename: with.oxlintrc.json(carryingno-debugger: off) the warning was suppressed, withoxlintrc.jsonit fired. So all 87 rules in that file are inert and CI lints against oxlint's defaults, not this repo's config. - Warnings do not fail. 86 of the 87 rules are
warn, and oxlint exits 0 on warnings. Measured:exit=0plain,exit=1with--deny-warnings.
cp-aws-sso-config gets both right: .oxlintrc.json, plus --deny-warnings in its lint:check script and in its oxlint pre-commit hook.
Suggested fix — rename oxlintrc.json → .oxlintrc.json and deny warnings:
"lint": "oxlint --deny-warnings ."I checked that this is safe to apply here: with the config actually loaded and --deny-warnings on, the current tree still passes clean (exit 0, no findings), so it will not turn the PR red or need a follow-up cleanup commit.
Pre-existing on main, so not introduced here, but this PR is what turns it into a gate that reviewers and branch protection will trust, which is why it seems worth fixing in this change rather than later.
There was a problem hiding this comment.
[kiro]: Confirmed independently and both fixes applied in c24117f - oxlintrc.json to .oxlintrc.json, and "lint": "oxlint --deny-warnings .".
I reproduced it with a different discriminator than your debugger test, to check the conclusion rather than the method. unicorn/no-invalid-fetch-options is the single rule the config sets to off, so it distinguishes "config loaded" from "config ignored" without touching rule severity:
oxlint probe.ts -> unicorn(no-invalid-fetch-options) fires
oxlint -c oxlintrc.json probe.ts -> suppressed
oxlint probe.ts (after rename) -> suppressed
That is the stronger form of your finding: not just that the file was not discovered, but that the repository was linting against oxlint's defaults with all 87 rules inert, including the one deliberate off. Exit codes match what you measured: 0 with warnings present, 1 under --deny-warnings.
Also confirmed your claim that this is safe to apply as-is. With the config loaded and warnings denied, the current tree exits 0 with no findings, so it needs no cleanup commit.
One extra check, since the same class of bug bit twice: .mjs is not in the config's ignorePatterns, so I verified oxlint actually covers the new scripts/check-peer-floors.mjs rather than assuming a clean pass meant coverage. A .mjs probe with a debugger statement fails under --deny-warnings, so it is genuinely linted.
Agreed on the framing that this was pre-existing on main but that this PR is what turns it into a gate people rely on, which is why it belongs here.
|
|
||
| This repository uses [pnpm](https://pnpm.io). The version is pinned in the `packageManager` field of `package.json`, and pnpm installs that version for you on first use, so there is nothing to install beyond pnpm itself. | ||
|
|
||
| Use Node.js 20, 22 or 24. Do not use a newer major: jsii compiles this construct and only supports those release lines, so a newer Node produces an "untested version" warning and unsupported behaviour. |
There was a problem hiding this comment.
[Kiro]: This recommends a Node line that jsii already warns on today, and drops entirely in 50 days.
I traced @jsii/check-node@1.140.0, which is what jsii 6.0.12 bundles and what emits the "untested version" warning this sentence describes. Its release table:
new NodeRelease(20, { endOfLife: new Date('2026-04-30') }),
new NodeRelease(22, { endOfLife: new Date('2027-04-30') }),
new NodeRelease(24, { endOfLife: new Date('2028-04-30') }),Node 20's EOL was 2026-04-30, already past. jsii's own support window defaults to EOL + 6 months (DEFAULT_EXTENDED_SUPPORT_MONTHS = 6) and the state is deprecated = endOfLife && !endOfJsiiSupport. So a contributor who follows this line today gets a yellow warning on every build:
Node 20 has reached end-of-life on 2026-04-30 and will no longer be supported in new releases after 2026-10-30. Please upgrade to a supported node version as soon as possible.
On 2026-10-30, 50 days out, Node 20 crosses jsii EOS and flips to the red bgRed.white.bold branch: "Node 20 is end-of-life and not supported anymore by this software since 2026-10-30."
This is visible in the build output that the PR's own Node reasoning is drawn from. My pnpm run build printed the supported set as:
- ^24.0.0 (Planned end-of-life: 2028-04-30)
- ^22.0.0 (Planned end-of-life: 2027-04-30)
- ^20.0.0 (Planned end-of-life: 2026-04-30) [DEPRECATED]
22 and 24 carry no marker; 20 does. So the reasoning behind pinning CI to 24 is right, it just did not carry through to the contributor-facing guidance. Simplest fix is to drop 20:
Use Node.js 22 or 24.
While in here, the repo currently spreads across three Node versions with nothing reconciling them: this line says 20/22/24, both workflows pin node-version: 24, and @types/node is ^26.4.1 — types describing a runtime newer than the one CI uses and newer than anything jsii supports. There is no engines field, so none of it is enforced.
If you want it enforced rather than documented, devEngines.runtime is the right field. It is development-only by design, so unlike engines it does not constrain consumers of the published construct:
"devEngines": {
"runtime": { "name": "node", "version": "^24.0.0", "onFail": "download" }
}pnpm resolves the range, records the exact version and checksum in pnpm-lock.yaml, and runs scripts on it, which would collapse the three-way spread to one pinned source. One caveat: with onFail: "download" pnpm provisions Node itself, which makes actions/setup-node's node-version: 24 redundant, so it is worth picking a single owner for that rather than having both.
There was a problem hiding this comment.
[kiro]: Correct, and fixed in c24117f - the line now reads Node 22 or 24, with a short note that 20 is accepted but deprecated and why.
I verified the release table you quoted from the installed @jsii/check-node@1.140.0: Node 20 at endOfLife: 2026-04-30, DEFAULT_EXTENDED_SUPPORT_MONTHS = 6, deprecated = endOfLife && !endOfJsiiSupport. A local jsii run prints the same three lines you saw, with [DEPRECATED] on 20 only. So the guidance was recommending a line that warns today and stops being supported on 2026-10-30.
On the version spread, updated after cd4915e
Your observation that the repo spread across three Node versions with nothing reconciling them is now resolved rather than documented around, but it took two changes and the second one invalidates the reasoning in the first version of this reply. Recording both, since the conclusion moved.
First pass (c24117f): @types/node went from ^26.4.1 to ^24.13.3. My argument was that these typings bound what the handlers may reference, that the handlers deployed to Runtime.NODEJS_22_X, and therefore that the bound must not run far ahead of the runtime. On that criterion the strictly correct answer was ^22, and ^24 was defensible only because I measured that 22 and 24 declare an identical module set while 26 adds three modules that do not exist on a Node 22 Lambda.
Second pass (cd4915e): the Lambda runtime itself moved to NODEJS_24_X, which removes the compromise. Typings, CI and runtime are now all 24, so ^24 is exactly right rather than one major loose. nodejs22.x reaches Lambda deprecation on 2027-04-30 against 2028-04-30 for nodejs24.x, and nodejs24.x has been GA since 2025-11-25, so the runtime bump stands on its own merits and not just on tidiness.
The measurement that still matters is why ^26 stays out, and it is unchanged by the runtime move:
@types/node@26shipsquic.d.ts,ffi.d.ts,vfs.d.ts@types/node@24and@types/node@22ship an identical 48-module set, none of those three
Compiled both ways rather than inferring, using import * as quic from 'node:quic': accepted under 26, TS2591 under 22. So ^26 would let a handler import a module absent from the Node 24 Lambda and still typecheck. .github/dependabot.yml ignores @types/node majors for that reason, and CONTRIBUTING.md now says the typings and the runtime move together.
Two corrections to my own earlier framing, since you may have read it before the edit:
- I described the binding constraint as
NODEJS_22_X. That was true when written and is no longer. - I said
@types/node's major line is a coarse bound, which is still worth knowing:@types/node@22already declaresprocess.threadCpuUsageandprocess.execve, neither of which is in Node 22.0. The pin reliably bounds whole modules, not every individual member.
Worth being explicit that nothing was failing at ^26.4.1 - the branch built clean, as your own run found. That change closed a latent hole rather than fixing a break.
On devEngines: still not adopted, and the runtime bump does not change that. Your own caveat is the reason. With onFail: "download" pnpm provisions Node and actions/setup-node's pin becomes redundant, so it trades a documented spread for two competing owners of the same decision. The spread is now genuinely gone, so there is less for it to fix.
Follow-up to the review on #43. Each item below was verified rather than reasoned about, and the measurement is included where it changed the outcome. Runners - pr.yml and build-and-publish.yml move from self-hosted back to ubuntu-latest. This repository is public with a pull_request trigger, so a fork can run its own code in the PR job -- including the files that configure the install (pnpm-workspace.yaml, and therefore allowBuilds). The release job holds id-token: write for trusted publishing and must not share a runner pool with that. Beyond the security argument this was also non-functional: no self-hosted runner is registered for this repository, so the pre-commit job sat queued for five hours and the required status check could never report. GitHub-hosted runners still need enabling at the organisation level; the comment in pr.yml records that so the symptom is not mistaken for a hang. Registry - Drop the Artifactory branch from both workflows. No ARTIFACTORY_TOKEN is reachable from this repository -- no repository secret, no organisation secret visible to it, no environments -- so only the public-registry fallback ever executed. Removing it also removes a long-lived credential exported through $GITHUB_ENV into every later step, two uncleaned ~/.npmrc writes, and a comment claiming the token was never written to disk. CONTRIBUTING.md records the constraints for reintroducing it over OIDC if the credential appears. - minimumReleaseAge is now the primary supply-chain control rather than a backstop, since every CI install takes the public path. Comments in pnpm-workspace.yaml and CONTRIBUTING.md updated accordingly. Lint - Rename oxlintrc.json to .oxlintrc.json and add --deny-warnings. The config was never loaded: oxlint only auto-discovers the dotted name. Verified with a probe on unicorn/no-invalid-fetch-options, the one rule the file sets to "off" -- it fired without -c and was suppressed with it, so all 87 rules were inert and CI linted against oxlint's defaults. Warnings also exited 0. The current tree passes clean with the config loaded and warnings denied. Peer floors - Add scripts/check-peer-floors.mjs, run from build.sh ahead of tsc. jsii compares devDependencies[name] against semver.minVersion(peerRange).raw in lib/project-info.js, but reports a mismatch as JSII6, a warning that exits 0, and its severity cannot be raised because it is emitted before diagnostic overrides load. Measured: --fail-on-warnings also exits 0 with the warning present. The invariant was therefore unenforceable in CI and a bump would have merged green. The check fails on inconsistency, not on upgrades: raising the devDependency and the peer range together passes. Dependency updates - Add .github/dependabot.yml. Without it only security updates ran, which matters more now that every action is hash-pinned, because a SHA pin never resolves to anything newer and the workflows would rot silently. cooldown is 4 days, one above minimumReleaseAge, because Dependabot resolves without honouring it and can pull in a younger transitive dependency. - The ignore list is scoped throughout. A bare dependency-name ignore expands to >= 0 and applies on the security path too, so aws-cdk-lib and constructs are not listed at all -- the check above gates them instead. aws-cdk ignores only the accidental 3.0.0 publish, typescript and @types/node only majors, and jsii is not ignored, since a jsii release is the signal that a paired TypeScript bump exists and both land in the same grouped pull request. Dependencies - @types/node moves from ^26.4.1 to ^24.13.3, matching the Node CI builds on. These typings bound what the handlers may reference and they deploy to NODEJS_22_X: @types/node 26 declares node:quic, node:ffi and node:vfs, none of which exist there. Confirmed by compiling an import of node:quic -- accepted under 26, TS2591 under 22. @types/node 24 declares the same module set as 22, so it does not reintroduce that gap. Nothing was failing at 26; this closes a latent hole rather than fixing a break. Docs - CONTRIBUTING.md: drop Node 20 from the supported set. It reached end-of-life on 2026-04-30 and @jsii/check-node already marks it [DEPRECATED], with jsii support ending 2026-10-30. Also fix a circular reference to a README section that this branch had already moved into CONTRIBUTING.md. Signed-off-by: jehannes <jkzuidema@outlook.com>
|
[kiro]: All four points addressed, with replies in each thread. Summary here plus the Renovate/Dependabot item, and two findings that changed the shape of the answer. Commits: DependabotAdded
Where your suggestion led somewhere unexpectedWriting the A bare The floor invariant was unenforceable, which is why ignoring looked necessary. jsii does check it. So a Dependabot bump on either would have merged green while quietly widening the range the package claims to support. That is worth flagging beyond this PR: the Rather than hide those two from Dependabot, That lets the
A second vacuous assertion, found by the runtime bumpYour Node reasoning prompted moving the Lambda runtime to The synthesized template holds three The assertion now matches on an environment variable unique to each handler, The uncomfortable part is that it only ever pinned our runtime by luck, for as long as CDK's internal handler happened to sit on a different version. It would have gone quiet on its own the next time aws-cdk-lib bumped that handler to match. Still open
On the PR bodyAgreed, and it is now further out of date. The "Registry" section describes a CI Artifactory path that no longer exists, the Node reasoning has changed twice, and the Lambda runtime moved. Over to @jehannes for the description; the commit messages on Thanks for the review. The oxlint one in particular was a gate everyone would have trusted without it doing anything. |
Release 0.4.0.
Package manager
- Migrate npm -> pnpm 11.25.0, pinned via packageManager. package-lock.json
is replaced by pnpm-lock.yaml.
- pnpm-workspace.yaml sets nodeLinker: hoisted. This is required, not a
preference: the package publishes bundleDependencies (the runtime deps that
aws_lambda_nodejs re-bundles at the consumer's synth time), and pnpm refuses
to pack those under its default symlinked layout, failing with
BUNDLED_DEPENDENCIES_WITHOUT_HOISTED. hoisted also produces the same flat,
real node_modules npm does, so npm pack/publish stays usable as a fallback.
- Rule on dependency build scripts via allowBuilds: esbuild allowed (it bundles
the Lambda handlers), unrs-resolver and @parcel/watcher denied. Note that
onlyBuiltDependencies was removed in pnpm 11 and is now silently inert.
- Move the npm overrides into pnpm-workspace.yaml, replacing the deprecated
"$jest" reference with an explicit version.
Registry
- Do not commit a registry at all. Alliander developers pick up the Artifactory
npm mirror from their user-level ~/.npmrc, external contributors get
registry.npmjs.org, and CI selects one explicitly, falling back to the public
registry when no ARTIFACTORY_TOKEN secret is present so that pull requests
from forks still build. Committing either one would break the other case, and
no other repository in alliander-opensource pins an internal registry.
- The org's shared setup-artifactory composite action cannot be used here:
cp-gh-shared-actions is private and get-artifactory-credentials is internal,
and GitHub does not permit a public repository to consume actions from
private or internal repositories. The step is inlined instead.
- Add minimumReleaseAge: 4320 (3 days) as a supply-chain backstop for the
public-registry path, which gets no mirror-side cooldown. Deliberately
narrower than Artifactory's ~7 days, because pnpm re-validates the entire
committed lockfile against this policy on every install.
Dependencies
- Upgrade the runtime and dev dependency set. Publishing now targets the public
registry explicitly via publishConfig.
- Hold typescript at ~6.0.x and move jsii to ~6.0.12. jsii's major.minor tracks
the TypeScript compiler it wraps, so jsii@6.0 IS the TypeScript 6.0 line.
This also resolves a pre-existing mismatch, since jsii ^5.9 wants TS ~5.9
while the repo already pinned TS ~6.0.
- Hold aws-cdk on 2.x. aws-cdk 3.0.0 is an accidental publish that AWS
deprecated ("published accidentally, please use 2.x.x instead") but which
still holds the "latest" dist-tag, so upgrade tooling will offer it.
- Keep the aws-cdk-lib and constructs devDependencies at the floor of their
peerDependencies ranges. jsii requires this and warns otherwise
(JSII6, metadata/missing-dev-dependency); compiling against the oldest
supported version is what keeps those ranges honest.
Workflows
- Hash-pin every action to a full commit SHA, matching the convention already
used elsewhere in alliander-opensource.
- Pin CI to Node 24: jsii supports ^20, ^22 and ^24 only.
- Run lint, test and build on pull requests, and declare least-privilege
permissions.
- Keep the job named "pre-commit": branch protection on main requires a status
check with exactly that name.
Docs
- Correct the install instructions, which pointed at GitHub Packages although
the package is published to npmjs, and fix the WafUsage enum casing in the
usage example.
- Move development documentation to CONTRIBUTING.md. jsii embeds README.md into
the .jsii manifest, which is published and rendered on Construct Hub, so
contributor-only detail does not belong there.
BREAKING CHANGE: the aws-cdk-lib peerDependency changes from the exact pin
"2.254.0" to "^2.268.0". The range is more permissive above 2.268.0, but the
minimum supported version rises, so consumers on aws-cdk-lib 2.254.0 through
2.267.x must upgrade aws-cdk-lib to take this release.
Signed-off-by: jehannes <jkzuidema@outlook.com>
Follow-up to the review on #43. Each item below was verified rather than reasoned about, and the measurement is included where it changed the outcome. Runners - pr.yml and build-and-publish.yml move from self-hosted back to ubuntu-latest. This repository is public with a pull_request trigger, so a fork can run its own code in the PR job -- including the files that configure the install (pnpm-workspace.yaml, and therefore allowBuilds). The release job holds id-token: write for trusted publishing and must not share a runner pool with that. Beyond the security argument this was also non-functional: no self-hosted runner is registered for this repository, so the pre-commit job sat queued for five hours and the required status check could never report. GitHub-hosted runners still need enabling at the organisation level; the comment in pr.yml records that so the symptom is not mistaken for a hang. Registry - Drop the Artifactory branch from both workflows. No ARTIFACTORY_TOKEN is reachable from this repository -- no repository secret, no organisation secret visible to it, no environments -- so only the public-registry fallback ever executed. Removing it also removes a long-lived credential exported through $GITHUB_ENV into every later step, two uncleaned ~/.npmrc writes, and a comment claiming the token was never written to disk. CONTRIBUTING.md records the constraints for reintroducing it over OIDC if the credential appears. - minimumReleaseAge is now the primary supply-chain control rather than a backstop, since every CI install takes the public path. Comments in pnpm-workspace.yaml and CONTRIBUTING.md updated accordingly. Lint - Rename oxlintrc.json to .oxlintrc.json and add --deny-warnings. The config was never loaded: oxlint only auto-discovers the dotted name. Verified with a probe on unicorn/no-invalid-fetch-options, the one rule the file sets to "off" -- it fired without -c and was suppressed with it, so all 87 rules were inert and CI linted against oxlint's defaults. Warnings also exited 0. The current tree passes clean with the config loaded and warnings denied. Peer floors - Add scripts/check-peer-floors.mjs, run from build.sh ahead of tsc. jsii compares devDependencies[name] against semver.minVersion(peerRange).raw in lib/project-info.js, but reports a mismatch as JSII6, a warning that exits 0, and its severity cannot be raised because it is emitted before diagnostic overrides load. Measured: --fail-on-warnings also exits 0 with the warning present. The invariant was therefore unenforceable in CI and a bump would have merged green. The check fails on inconsistency, not on upgrades: raising the devDependency and the peer range together passes. Dependency updates - Add .github/dependabot.yml. Without it only security updates ran, which matters more now that every action is hash-pinned, because a SHA pin never resolves to anything newer and the workflows would rot silently. cooldown is 4 days, one above minimumReleaseAge, because Dependabot resolves without honouring it and can pull in a younger transitive dependency. - The ignore list is scoped throughout. A bare dependency-name ignore expands to >= 0 and applies on the security path too, so aws-cdk-lib and constructs are not listed at all -- the check above gates them instead. aws-cdk ignores only the accidental 3.0.0 publish, typescript and @types/node only majors, and jsii is not ignored, since a jsii release is the signal that a paired TypeScript bump exists and both land in the same grouped pull request. Dependencies - @types/node moves from ^26.4.1 to ^24.13.3, matching the Node CI builds on. These typings bound what the handlers may reference and they deploy to NODEJS_22_X: @types/node 26 declares node:quic, node:ffi and node:vfs, none of which exist there. Confirmed by compiling an import of node:quic -- accepted under 26, TS2591 under 22. @types/node 24 declares the same module set as 22, so it does not reintroduce that gap. Nothing was failing at 26; this closes a latent hole rather than fixing a break. Docs - CONTRIBUTING.md: drop Node 20 from the supported set. It reached end-of-life on 2026-04-30 and @jsii/check-node already marks it [DEPRECATED], with jsii support ending 2026-10-30. Also fix a circular reference to a README section that this branch had already moved into CONTRIBUTING.md. Signed-off-by: jehannes <jkzuidema@outlook.com>
Signed-off-by: jehannes <jkzuidema@outlook.com>
781d33f to
4541611
Compare
Move both NodejsFunction runtimes from NODEJS_22_X to NODEJS_24_X. Why now - nodejs22.x reaches Lambda deprecation on 2027-04-30 (create blocked 2027-07-01); nodejs24.x runs to 2028-04-30. nodejs24.x has been GA since 2025-11-25, so this is not a fresh runtime. - It removes the last version mismatch in the repository. @types/node is ^24 and CI pins Node 24, so the typings were sitting a major ahead of the runtime they bound -- the same class of gap that made ^26 wrong, only narrower. Of the two internally coherent pairings, types 24 + runtime 24 and types 22 + runtime 22, the tree had neither. Now typings, build and runtime are one version. Cost - No dependency change: Runtime.NODEJS_24_X already exists in the pinned aws-cdk-lib 2.268.0. - No runtime dependency constrains it: @aws-sdk/client-kms and client-s3 declare node >=20, base64url >=6, powertools/logger and jsrsasign declare nothing. - No bundling or esbuild target override in src/index.ts, so the bundle target follows the runtime automatically. Consumer impact - This is an in-place CloudFormation update of the Runtime property. The functions are not replaced and their ARNs are preserved. - The handlers are authored and bundled by this package, so no consumer-supplied code runs on the changed runtime. - The jsii API surface is unchanged; .jsii is byte-identical. Test correctness - The existing assertion in src/test/index.test.ts became vacuous under this change and had to be repaired, not just retargeted. The synthesized template holds THREE AWS::Lambda::Function resources: our keyrotate and sign handlers, plus aws-cdk-lib's own S3 auto-delete-objects custom resource handler, whose runtime aws-cdk-lib controls and which is currently also nodejs24.x. hasResourceProperties passes when ANY resource matches, so a bare `Runtime: 'nodejs24.x'` match is satisfied by that third resource on its own. Verified: with the assertion at nodejs24.x and our handlers left on NODEJS_22_X, the test still passed. - The assertion now matches on an environment variable unique to each handler (PENDING_KEY for keyrotate, DEFAULT_AUDIENCE for sign) alongside Runtime, and covers both functions rather than one. Confirmed load-bearing by mutation: reverting src/index.ts to NODEJS_22_X fails it with "Expected nodejs24.x but received nodejs22.x", and the CDK-internal handler no longer satisfies it. - This also means the runtime was only ever pinned by test for as long as aws-cdk-lib's internal handler happened to differ from ours. Docs - The @types/node note in CONTRIBUTING.md and the matching comment in .github/dependabot.yml cited NODEJS_22_X as the bound; both now cite NODEJS_24_X and say the two move together. Signed-off-by: jehannes <jkzuidema@outlook.com>
|
| //alliander.jfrog.io/artifactory/api/npm/alliander-npm-all/:_authToken <token> | ||
| ``` | ||
|
|
||
| Keep both the registry and the token in that user-level file. This repository intentionally commits no `.npmrc` at all; if you add one, never put credentials in it, and note that since pnpm 10 only registry and auth settings are read from `.npmrc` — every other pnpm setting belongs in `pnpm-workspace.yaml`. |
There was a problem hiding this comment.
Would it make sense to add .npmrc (and perhaps also package-lock.json) to .gitignore?
| run: 'pip3 install --only-binary :all: pre-commit==4.6.2' | ||
|
|
||
| - name: run pre-commit | ||
| run: pre-commit run --all-files -c .pre-commit-config-ci.yaml |
There was a problem hiding this comment.
Would it be a good idea to also hash pin .pre-commit-config-ci.yaml?
| @@ -1,14 +1,15 @@ | |||
| { | |||
| "name": "@alliander-opensource/aws-jwt-sts", | |||
| "license": "MIT", | |||
There was a problem hiding this comment.
Here it is MIT license where as everywhere else it is Apache. Perhaps bring that also inline with the rest unless there is a good reason for it.



Release 0.4.0.
Package manager
Dependencies
Workflows
Docs