feat(workflows): host the reusable GitHub workflows in this repo - #120
Conversation
Moves the contents of apify-store/github-actions-source here, so a change that spans a CLI feature and the workflow calling it is one PR against one branch instead of two repos with a manual ref dance between them. Consumers pin the `@v1` major tag rather than `@master`, and the setup action installs `apify-test-tools@>=<floor>` (from .github/workflows-min-package-version) instead of `@latest`. That resolves to the same newest stable in the normal case, but makes a workflow's package requirement explicit. The release cadences stay independent, because most changes only touch one side: - workflow-only change: merge, `v1` moves, live, no npm release - package-only change: merge and release when ready, workflows untouched - workflow calling a new CLI feature: raise the floor in the same PR. On merge the `v1` move is held with a warning until that version is on npm, so consumers keep running the previous workflows instead of calling a CLI that doesn't exist yet. A stable release moves the tag; `manual_move_major_tag` is the escape hatch. Also: - run-with-apify-tokens.mjs moves to .github/scripts/, next to the other CI helper and out of eslint's path; the action's `scripts-path` output follows it - pass github.head_ref/base_ref through the environment in pr-build-test, since branch names are attacker-controlled on fork PRs and actionlint gates master here - prettier formatting on the imported files, which husky enforces here Refs in the consumer snippets in the README were still `@new_master` and are updated along with the repo path.
actionlint gates master here and the action wrapper runs shellcheck, which the old repo's CI did not, so this SC2086 came in with the import. Unquoted it would word-split if the runner ever pointed $GITHUB_OUTPUT at a path with spaces.
…ctions-package-ep3lzw
Catches the merged copy up with the four commits that landed on github-actions-source master after the import (#57, #59, #60, #63), all of which built the claude-review action. Two new files, nothing else changed upstream. `review.yaml` fetches its instructions over HTTP rather than from a checkout, because a reusable workflow runs with the caller's repo checked out and never gets its own, so the URL had to follow the move to this repo. Its `prompt-ref` default moves from `master` to `v1`: consumers call the workflow at `@v1`, and defaulting the prompt to master would run released workflows against unreleased instructions, which is the skew the tag gate exists to prevent. Also drops `RUN_PLATFORM_TESTS` from pr-build-test. Master removed it in #122 in favour of gating on `TESTER_APIFY_TOKEN`, which that step already sets, so after merging master the variable was config nothing reads. Two notes on the checks: - .github/actionlint.yaml ignores two errors on review.yaml. actionlint bakes in a snapshot of popular actions' interfaces from when the pinned version shipped, and claude-code-action has grown since, so it flags `display_report` and the `conclusion` output as undefined. Both are declared in the action's action.yml at @v1 — verified before suppressing, and the patterns name those two symbols so unrelated bad inputs and outputs in that file still fail. - .github/review-prompt.md is prettier-ignored. Prettier collapses the nested bullet list under "do not visit, fetch, infer, or evaluate the following external links" into one run-on line, changing what the model is told. Keeping it byte-identical also makes re-syncing it a plain copy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
… the refs Cuts the major tag as `v0` rather than `v1`, since the package is on 0.x and will keep releasing 0.x for a while. Nothing in the gate logic reads the number, so this is only the tag name and the refs that repeat it. Keeping consumers on `@master` until the package hits 1.0 was the alternative, and it reopens the gap this was built to close: the floor gate has teeth only because consumers track a tag that can be held. On `@master` a workflow calling an unreleased CLI feature ships on merge and fails in every consumer's CI, instead of simply not shipping. The tag tracks the workflows' contract, not the package version — they move independently on purpose, so `v0` is expected to outlive the package reaching 1.0. Bump it when a workflow breaks its callers, not when the package does. Adds check-major-tag-refs.mjs to _check_code, because `uses:` cannot take an expression, so the tag is repeated in all seven refs into this repo plus the README snippets people copy. Missing one during a bump is silent in the case that matters: right after a bump both tags exist, so workflows called at the new tag pull the composite action from the old one and run a stale version of it without failing. The check compares every self-reference and review.yaml's prompt-ref default against MAJOR_TAG and names the file and line that disagree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
GitHub only reads workflow files at the top level of .github/workflows, so the workflows other repos call sit in the same directory as this repo's own CI with nothing distinguishing them. The prefix makes the boundary readable: `public_` is the API other repos depend on, `_` is internal plumbing, `on_`/`manual_` are this repo's own triggers. Renamed: public_pr-build-test, public_platform-tests, public_push-build-latest, public_claude, public_review, public_platform-tests-claude-investigate-and-fix. Breaking for anyone already calling these by path, which today is nobody: the v0 tag does not exist yet and consumers still point at the old repo. Doing it now costs nothing; after the first tag it would cost a major bump, since a filename is baked into every consumer's `uses:` line. CONTRIBUTING says so where the convention is introduced. Two references would have broken quietly rather than loudly: - .github/actionlint.yaml keys its ignores by path, so a stale key silently stops suppressing and the claude-code-action false positives come back. Verified by pointing the key at the old name and watching both errors return. - check-major-tag-refs.mjs reads review.yaml by path to check its prompt-ref default; a stale path would have downgraded that to a soft "could not read". The separators are now mixed (public_platform-tests), since the prefix is underscore-style and the imported names are hyphenated. Left as-is because the rest of the filename is the part consumers type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
…t a floor bump The floor is a hand-written declaration, so the tag hold only protects consumers if someone remembers to raise it. Forgetting is silent at merge time and loud everywhere else: the tag moves, the workflow goes live, and it calls a CLI that is not on npm, breaking every consumer's CI at once. The check fires only when a single PR touches both sides — a `public_` workflow or the composite action, and `bin/`, `lib/` or `index.ts` — and no floor bump. `lib/` counts because the platform tests import it, so a workflow can depend on its behaviour as much as on a CLI flag. Deliberately scoped to one PR. A workflow could also start using something from an earlier unreleased PR, which this will not see. Catching that means comparing against the floor's release tag, which flags every workflow edit made while any package change sits unreleased — far more noise than a case that needs someone to land a CLI change and then sit on it. Unrelated changes do land in one PR, so `no-floor-bump-needed` skips the check rather than leaving no way past it. The failure message names both halves of the diff and spells out both exits. Verified against ten path combinations, including the real diff of this branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
Syncs github-actions-source#62, the one commit that landed there since the last sync. Gives the investigate phase read-only access to the failing run through the Apify MCP server, so it can read the run, its log and its storages instead of working from workflow logs alone, and pins claude-code-action to a SHA. Applied to the renamed public_ file; the patch landed unchanged otherwise. Documents the new TESTER_APIFY_TOKEN_READ_ONLY in the README secrets table, since it is consumer-facing: the workflow declares it `required: true`, so a repo calling this workflow without that secret set will fail the call. Upstream has it on master already, which is live, so this is not new breakage — but repos that pick it up when they migrate to @v0 need the secret in place first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
ruocco-l
left a comment
There was a problem hiding this comment.
It looks good to me, just a question on the v0->v1 migration, which maybe I don't quite fully understand 😅
| # Bump this only for a breaking change to a workflow's inputs, secrets or behaviour. | ||
| # The previous tag then stops moving and keeps working, so repos migrate when they get | ||
| # to it instead of all at once. | ||
| # |
There was a problem hiding this comment.
Not that is the most pressing thing, but migrating the tag from v0 to v1 is confusing for me. It's true that v0 freezes, but the actions will still install the latest version of the library (which will be allowed by the floor because it would set a lower version of the library for sure). That way we will probably have a library that works just for v1 and v0 breaks as soon as the newer version of the library is published.
There was a problem hiding this comment.
Good catch. Claude instead went with the exact version pin per action release tag. I think that also generally simplifies the mental model, each actions commit release will bound to extract library version.
There was a problem hiding this comment.
@artogahr and @JuanGalilea smuggled two commits in while you were doing this so this action is not 1:1 what we have in the other repo 👀
There was a problem hiding this comment.
I will sync the new updates before merging. Will need to keep syncing before people move.
…floor The workflows installed `apify-test-tools@>=<floor>`. That is open-ended, so it resolved to whatever was newest on npm at run time, and a frozen major tag froze the workflow files but not their dependency: `v0` would have kept pulling newer library versions after `v1` was cut. Since the release that enables `v1` is normally the same release that breaks `v0`, the migration window was zero, and the README's claim that the old tag keeps working was false. The file now holds one exact version and the workflows install exactly it, so a tag is a complete statement — these workflows and this library — and a frozen tag keeps what it was tested against however far the package moves on. Verified against the registry: pin 0.8.0 installs 0.8.0 where `>=0.8.0` resolved to 0.9.0. The stable release writes the pin into the commit that already bumps package.json and CHANGELOG.md, so a released commit always names the version it published and rollout latency is unchanged — the release publishes and moves the tag in one run. `signed-commit` stages `.` by default, so the file rides along. The write is guarded with `release_type != 'prerelease'`. _update_release_metadata serves both paths and on_master passes `prerelease`, so writing unconditionally would have pinned a `-beta` and shipped betas to every consumer. Betas stay reachable through the lockfile path in the setup action, which is how branch testing works. Accepted trade-off: a standalone manual_publish_to_npm dispatch no longer reaches consumers, because it does not write the pin. An unrecorded publish silently changing what every consumer runs is the behaviour worth losing. Renames follow the concept: check-floor-bump.mjs becomes check-package-version-bump.mjs, its override label becomes `no-version-bump-needed`, and the two PR jobs become `Package version bump needed` and `Workflows package version`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
|
so the idea is for this repo to become all things CI/CD? since there are several unrelated actions 👀 (like claude stuff) |
The main reason is that the actions are closely tied to this repo and we need to deploy them in sync. Haing them here allows us to to have the plumbing for that. Also one less repo to maintain.
It is called |
JuanGalilea
left a comment
There was a problem hiding this comment.
can we move the actions not being used in this repo into a subfolder?
So at least we know which ones are used and which ones are "exports"?
I know the cost of this will be one more ./something when calling them but some order here would be benefitial
|
btw, this will fuck up sync apify-store/github-actions-source#67 (its very short tho) |
|
Sadly,.Github seems to force workflows to be in the same top level folder so the only solution is a prefix. |
…ctions-package-ep3lzw
Carries across everything that landed upstream since the last sync (9263146), and merges apify-test-tools master, which had moved six commits including the config rework and the pr-title-check bump to v1.6.1. - #61 + #66 — run changed platform tests when the branch rebuilds no actor. Test files sit outside every actor's Docker build context, so a test-only PR used to pass in 25s having run nothing. #66 fixes #61: a changed helper passed to vitest as a plain filter matches no test file and exits 1, so it uses `related`. - #65 — a tighter Claude issue-investigator prompt. - #67 — off Node 20: setup-node v5 (with package-manager-cache disabled, since the node_modules cache below it is the one we want), checkout v5, upload-artifact v6. #65 applied clean. The other three needed hand-application, since the local copies have diverged — the composite action for the version pin, pr-build-test for the injection fix and prettier. Two upstream details deliberately not carried over: - the new step interpolates github.head_ref and github.base_ref straight into `run:`. Branch names are attacker-controlled on fork PRs and actionlint gates master here, so they go through the environment like the other steps. The two `$GITHUB_OUTPUT`/`$GITHUB_STEP_SUMMARY` writes are quoted for the same reason. - upstream still sets RUN_PLATFORM_TESTS, which master dropped in #122 in favour of gating on TESTER_APIFY_TOKEN. It stays dropped; RUN_ALL_PLATFORM_TESTS is new and is still read by lib, so that one is carried. `[ "$work_dir" = "." ] && work_dir=""` looks like it would trip GitHub's `bash -e` shell when the working directory is not `.`, but bash exempts a failing command that is not the last in an AND list — checked before porting it unchanged. The new step calls `get-affected-actors`, which the pinned 0.9.0 already has, so no version bump. The `Package version bump needed` check stays quiet correctly: this PR changes no bin/, lib/ or index.ts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
This PR introduces a complete set of reusable GitHub workflows and supporting infrastructure that consumer repositories can use for testing, building, and releasing their Actors.
Summary
The package now includes production-ready reusable workflows alongside the CLI, enabling consumer repos to standardize their CI/CD pipelines. These workflows handle platform testing, PR validation, releases, and Claude-powered automated investigation and fixes for failing tests.
Key Changes
Reusable Workflows (
.github/workflows/):pr-build-test.yaml: Runs unit tests and platform tests on pull requestsplatform-tests.yaml: Scheduled platform test runs with Slack reportingpush-build-latest.yaml: Builds and releases Actors on master pushclaude.yaml: Responds to@claudementions to implement fixes via Claude Codeplatform-tests-claude-investigate-and-fix.yaml: Two-phase workflow that uses Claude to investigate failing tests and automatically open issues and PRs_move_major_tag.yaml: Manages the floating@v1tag, only moving it when the required package version is publishedmanual_move_major_tag.yaml: Escape hatch for manual tag managementGitHub Actions (
.github/actions/):checkout-restore-dependencies/action.yaml: Composite action for checkout, Node setup, and dependency caching with npm token isolationHelper Scripts (
.github/scripts/):run-with-apify-tokens.mjs: Securely passes only required Actor tokens to CLI commands, preventing secret leakage tonode_modulesConfiguration:
.github/workflows-min-package-version: Declares the minimum CLI version required by the workflows, gating tag movement until that version is publishedDocumentation:
README.mdwith workflow usage examples, secret handling details, and versioning guidanceCONTRIBUTING.mdto document the workflows as a third component of the packageNotable Implementation Details
Secret Isolation: Secrets are passed only to the steps that need them, never as job-wide environment variables. The
run-with-apify-tokens.mjsscript readsapify-test-tools.config.jsonto determine which Actor tokens to pass, preventing accidental exposure.Versioning Strategy: Consumer repos pin workflows to
@v1(a floating major tag), not@master. The tag only moves when the declared minimum package version is published on npm, preventing workflows from calling unreleased CLI features.Claude Integration: The
platform-tests-claude-investigate-and-fix.yamlworkflow chains investigation and fix phases as job dependencies rather than label triggers, working around GitHub's limitation that scheduled runs cannot trigger other workflows.Backward Compatibility: The
platform-tests.yamlworkflow supports both the deprecatedsubtestinput and the newtest-files-globinput for gradual migration.https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM