chore: adopt typescript-action-template's modernization bundle - #427
Draft
joris974 wants to merge 2 commits into
Draft
chore: adopt typescript-action-template's modernization bundle#427joris974 wants to merge 2 commits into
joris974 wants to merge 2 commits into
Conversation
Ports the bundle from freckle/typescript-action-template#555: pnpm, ESLint flat config, knip, a 70% coverage gate, format-check in CI, an ES2023/NodeNext tsconfig with a build/typecheck split, and the house Prettier config. The action's own behavior is unchanged. action.yml is untouched, so the inputs and outputs freckle/stack-cache-action delegates to still exist. dist/index.js is now minified; the built bundle was diffed against the previous one under a fake-stack harness and produces byte-identical output, and the compiled lib/ differs only by three dropped `export` keywords on module-internal constants. Deliberate deviations from a straight template copy: - .github/CODEOWNERS moves to @freckle/team-student-activities. This is an ownership reassignment across a batch of repos, not template matching; the template's own CODEOWNERS says @freckle/team-platform. - .prettierrc drops jsxBracketSameLine. Prettier 3 renamed it to bracketSameLine and 3.9.6 does not recognize the old name (`prettier --support-info` confirms), so the template's copy is dead config rather than something to carry forward. - ci.yml gains no `integration` job. The template's bare `uses: ./` would fail here, since there is no Haskell project at the repo root. example.yml already runs the built action end to end across three runners and four resolvers; its dist job moves to pnpm. typescript rolls back from ^7.0.2 to the template's ^6.0.3, since typescript-eslint hard-errors on 7.x. Fixes surfaced by the new tooling rather than ported: - CacheDelegate.restoreCache returned Promise<string>, but @actions/cache resolves undefined on a miss and with-cache.ts already handles that. typecheck now covers test files and caught it. - Removes @types/js-yaml (js-yaml 5.4.1 bundles its own types), ts-node (unreferenced), and action-docs (invoked via npx, as the template does). knip flagged all three. - Drops `export` from ALL_SOURCES_PATTERNS, BUILD_FILES_PATTERNS, HIE_YAML, and the Compiler type; nothing imports them. Adds tests for inputs.ts, hash-project.ts, and hie.ts (all at 0%) and extends stack-cli.test.ts, taking coverage from 48.27% to 84.82% lines so the 70% gate passes on merit rather than a lowered threshold. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
joris974
marked this pull request as ready for review
September 2, 2026 21:59
joris974
requested review from
z0isch
and removed request for
a team and
z0isch
September 2, 2026 21:59
joris974
marked this pull request as draft
September 2, 2026 22:20
…uilds dist Release runs on GitHub-hosted ubuntu-latest, which does not ship pnpm. .releaserc.yaml's prepareCmd (pnpm install && pnpm run build) would fail the first time this job actually runs, since release.yml never triggers on pull_request and so this path isn't covered by this PR's green CI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ports the current modernization bundle from freckle/typescript-action-template#555 to this repo. The action's runtime behavior does not change:
action.ymlandgenerate-matrix/action.ymlare untouched, and the rebuiltdist/index.jswas diffed against the previous one under a fake-stackharness with identical output.Summary
packageManager.yarn.lockdeleted,pnpm-lock.yamladded.typescriptrolled back^7.0.2->^6.0.3.eslint.config.mjs),knip.json,.prettierrc,tsconfig.build.json.tsconfig.jsonmodernized to ES2023, redundant-with-strictflags dropped,includeno longer excludes test files sopnpm typecheckcovers them.format-check,typecheck,lint,knip,coverage,buildand uploadsdist/index.js.buildis nowtsc -p tsconfig.build.json && ncc build lib/main.js --minify..github/CODEOWNERSset to@freckle/team-student-activities.conventional-changelog-conventionalcommitspinned to@9.@types/js-yaml,ts-node, and theaction-docsdevDependency.src/with-cache.tsthat the newtypecheckscope exposed.## Developmentsection; LICENSE year becomes open-ended, both matching the bundle.Not breaking for consumers
Consumers use this action as
uses: freckle/stack-action@v5in a workflow, not as an npm import, so the ESM/pnpm/TypeScript changes here are invisible to them.action.yml's inputs, outputs, andruns.using: node24are byte-for-byte unchanged:freckle/stack-cache-actiondelegates tofreckle/stack-action@v5withworking-directory,cache-prefix,stack-build-arguments: --dry-run,install-stack: false,upgrade-stack: false, andenv.STACK_YAML. All six still exist and are unchanged. (Unrelated to this PR: that action declares an input namedprefixbut passes${{ inputs.cache-prefix }}, so its prefix has never actually reached us. Not touched here.)Commit type is
chore:rather thanfeat!:deliberately. Nothing consumer-visible changes, so this should not cut a release on its own; the next realfix:/feat:will pick up the rebuiltdist/via@semantic-release/exec.dist/ behavioral equivalence
Given how widely this action is used,
--minifywas verified rather than assumed, in two independent ways.1. Compiled output diff. Compiled the original
src/and the portedsrc/with the sametsc, then normalized both through Prettier to remove the quote/semicolon churn. The entire semantic difference across all modules is three lines:main.tsand every other module compile identically. Thewith-cache.tschange is type-only and emits nothing.2. End-to-end diff of the built bundles. Ran the old and new
dist/index.jsagainst a fakestackonPATH(answeringquery,path, andexec -- which gen-hie) in a fresh git repo, withinstall-stack: false,upgrade-stack: false,color: true,cache-prefix: v9/,compiler-tools: hlint. This exercises input parsing, shell-word splitting and envsubst, project hashing,stack path/stack queryYAML parsing, cache-key construction, and the setup/build-dependencies/compiler-tools sequence, before failing at cache save as expected outside a real runner.Sample of the 89 identical lines, showing the commands actually issued:
The
dist/build is also deterministic — rebuilding after committing leaves the tree clean, which is what CI would assert:Size dropped 4,604,727 -> 1,769,988 bytes from
--minify. One consequence worth naming: an uncaught stack trace indist/index.jswould now show minified frames. In practice the action reports failures throughcore.setFailed(error.message), so no stack traces surface today.dist/package.json({"type": "module"}) is now committed.ncchas always emitted it for an ESM build; it was simply never committed here, and nothing in CI checked. It resolves to the same module type the rootpackage.jsonalready gives, so it changes nothing at runtime. The template commits it too.TypeScript rollback
package.jsondeclared"typescript": "^7.0.2", and 7.0.2 was what installed. Rolled back to the template's^6.0.3—typescript-eslinthard-errors on 7.x, and holding at 6.x is the standing convention.Full suite re-run after the rollback; results in Verification below. Nothing broke that wasn't already broken.
Type error found by the new typecheck scope
The old
tsconfig.jsonhad"exclude": ["./src/**/*.test.ts"], so test files were never type-checked. The template's split moves that exclusion intotsconfig.build.jsonand letspnpm typechecksee tests. Five errors appeared immediately, all the same one:The root cause is in production code, not the tests.
CacheDelegate.restoreCachewas typed=> Promise<string>, but@actions/cacheresolvesundefinedon a cache miss, andwith-cache.tsalready handles that (if (restoredKey) { ... } else { core.warning('No cache found') }). The real delegate escaped the check only becausecache ?? realCacheinfers a union rather than checking assignability. Widened the interface to match reality:Type-only; emits no JavaScript.
Coverage
Baseline with the gate in place but no new tests: 48.27% lines, 62.12% branches, 53.19% functions, 48.27% statements — all four below 70%.
inputs.ts,hash-project.ts, andhie.tswere at 0%.Rather than lower the threshold, added
src/inputs.test.ts(5 tests),src/hash-project.test.ts(1),src/hie.test.ts(5), and 9 tests tosrc/stack-cli.test.tscoveringinstalled,install,upgrade,which,path,query,read, and the$STACK_YAMLconfig fallback. Test count went 44 -> 65.src/main.tsis excluded from the metric, as in the template. Here the justification is stronger than the template's "thin wiring":main.tsdrives realstackinvocations, and.github/workflows/example.ymlalready runs it end to end across three runners and four resolvers.Prettier
This repo had no
.prettierrcat all —format-checkexisted as a script but ran against Prettier's defaults and was never wired into CI. Adopted the template's config, which reformatted all 20 source and test files (double quotes -> single, semicolons removed,printWidth100,bracketSpacing: false). Done withpnpm format, not by hand.One option was not carried forward. The template's
.prettierrcincludesjsxBracketSameLine, which Prettier 3 renamed tobracketSameLine; 3.9.6 does not recognize the old name:Renamed it. The value is
falseeither way, so no formatting changes as a result. The template itself still carries the dead key — worth fixing upstream.Reformatting does not affect the build: the compiled-output diff above is clean apart from the three
exportlines.Release workflow
Both
release.ymland.releaserc.yamlalready existed and follow the template's mechanism —cycjimmy/semantic-release-action, git tags plussemantic-release-major-tagfor the@v5pointer, and@semantic-release/gitpushing the rebuiltdist/index.jsback. No npm publish anywhere, as expected for an Action. Two reconciliations:.releaserc.yaml's@semantic-release/execprepareCmdwasyarn install && yarn run build, which the pnpm migration would have broken. Nowpnpm install && pnpm run build.release.ymlpinsconventional-changelog-conventionalcommits@9with the template's explanatory comment. Unpinned,extra_pluginswould resolve v10, which needsconventional-changelog-writer@9thatsemantic-release@25does not ship.The template's
if: falserelease guard is a placeholder for generated repos and is correctly absent here.Follow-up fix (post-review): switching
prepareCmdtopnpmabove wasn't enough on its own —release.yml'sreleasejob never installed pnpm, so thepnpm install && pnpm run buildstep would have failed the first time this job actually ran (GitHub-hostedubuntu-latestdoesn't ship pnpm the way it ships yarn).release.ymlonly triggers on push tomain/rc/*, never onpull_request, so this PR's green CI never exercised that path — an independent review caught it. Added the samepnpm/action-setup@v6+actions/setup-node@v7stepsci.ymlalready has.lodash
Not applicable. This repo has no
lodashdependency:Its runtime dependencies are
@actions/cache,@actions/core,@actions/exec,@actions/glob,js-yaml, andshellwords-ts. None were touched.Dependencies removed
pnpm knipflagged three devDependencies and four exports. Each was genuinely dead:@types/js-yaml@4.0.9—js-yaml@5.4.1ships its own types ("types": "./dist/js-yaml.d.ts"in itspackage.json). Removed;typecheckstill passes.ts-node@10.9.2— no reference anywhere insrc/, config, or workflows.action-docs@2.5.1— thereadmescript invokes it asnpx action-docs, which is exactly what the template does without declaring it.ALL_SOURCES_PATTERNS,BUILD_FILES_PATTERNS,HIE_YAML, and theCompilertype were exported but imported nowhere. Made module-internal.Divergences from typescript-action-template
Every remaining difference, and why it stays:
Runtime dependencies. This repo ships
@actions/cache,@actions/exec,@actions/glob,js-yaml, andshellwords-ts; the template ships@actions/coreand@actions/github. The template's@actions/github,@octokit/plugin-rest-endpoint-methods, and@octokit/typesexist to demo a GitHub API call. This action makes none, so they are not added.No
integrationjob inci.yml. The template's job downloadsdistand runs a bareuses: ./. Here that would invokestack buildwith no Haskell project at the repo root and fail..github/workflows/example.ymlis this repo's integration suite and is considerably more thorough: 3 runners x 4 resolvers againstexample/, agenerate-matrixsub-action job, astack.yamlmatrix job with a symlink guard, and a should-fail job asserting a bad resolver fails the action. Itsdistjob moved to pnpm.example.yml'sdistjob pinsworking-directory: .. The workflow setsdefaults.run.working-directory: examplefor all jobs, which the laterstack-yamlsymlink check depends on. yarn walks up to findpackage.json; pnpm does not, and errors withERR_PNPM_NO_IMPORTER_MANIFEST_FOUND. A job-level override is the smallest correct fix and leaves the other jobs alone.generate-matrix/action.ymlhas no template counterpart. It is a composite sub-action with no TypeScript and no build target, so nothing in the bundle applies to it. Left as is..github/CODEOWNERSis@freckle/team-student-activities, not the template's@freckle/team-platform. This is a deliberate ownership reassignment across this batch of repos, decided independently of the template. Not template matching; the divergence is the intent..prettierrcusesbracketSameLinewhere the template usesjsxBracketSameLine. Covered above — the template's key is not a recognized Prettier 3.9.6 option.vitest.config.js's coverage-exclusion comment differs. The template saysmain.tsis "thin wiring, covered by the integration CI job"; here it points at.github/workflows/example.yml, which is the workflow that actually covers it.release.ymlhas noif: false. The template's guard exists so generated repos do not release before they are ready. This repo releases.No
ARCHITECTURE.md. The template's predates PR #555 and is not part of this bundle. This repo documents itself in a long README instead.package.jsonmetadata.name,description, andrepositoryare this repo's.mainwas dropped, matching the template — nothing read it, sincebuildhardcodeslib/main.jsand the action entry point isaction.yml'sruns.main.Now aligned with the template and previously not:
.nvmrc(24.20.0, was already correct),renovate.json(already identical),.restyled.yaml(!dist/index.js->!dist/**/*), andmergeabot.yml(@v3->@v3.2.0).Verification
All from a clean state:
rm -rf node_modules lib coverage && pnpm install --frozen-lockfile, on Node 24.20.0 (.nvmrc) with pnpm 11.24.0.Integration smoke test of the built bundle, standing in for the template's
integrationjob:Byte-identical to the same invocation against the pre-change
dist/index.js. The fuller fake-stackharness diff is in the dist section above..github/workflows/example.ymlwill run the real end-to-end suite on this PR; that is the check to watch before merging.