Repository navigation
feat: add Backstage 1.54.0 migration recipe (#155) - #160
Conversation
|
Current blocker (CLI 1.18.3; five registry codemods at v0.2.0): recipe dry-run completes, but it does not execute shell steps. Apply runs steps 1–4; the final nested Repro from |
…n-recipe # Conflicts: # README.md # yarn.lock
Run each Backstage 1.54.0 codemod script as a local js-ast-grep or shell step in the recipe workflow instead of nesting registry codemod packages, which failed in CLI 1.18.3 with an unbound state.targetDirEncoded in the nested OpenAPI step. - Move the five codemods' scripts into the recipe's scripts folder, each script kept separate and runnable - Remove the five standalone packages so the recipe is the only published 1.54.0 artifact - Verify with an end-to-end apply on a scratch app: package.json, GitHub Actions and shell scripts are rewritten
Restore the fixture suites from the five removed packages under tests/<codemod-name>/ and wire them into the recipe's yarn test: - Connections scripts run as jssg fixture tests (34+6+25 cases) - Extension config schema fixtures are full app trees run through the entire recipe workflow via assert-fixtures.sh (27 fixtures; the diff also proves the other steps no-op on them), plus a tsc type check - OpenAPI scripts keep their JSON/YAML/shell fixture suites and node --test unit tests for rewriteCommand and the shell walker Path references in the moved test scripts are updated for the new layout; script logic is unchanged.
Drop references to the CLI bug and the removed standalone packages from the README; the local-scripts recipe layout is the pattern for future Backstage migration recipes.
Replace the recipe's hardcoded test chain with scripts/run-recipe-tests.sh, shared by every version recipe. Suites are discovered by convention so adding a script or fixture suite needs no test-command change: - scripts/<name>.ts + tests/<name>/<case>/input.<ext> -> codemod jssg test, language inferred from the fixture extension - tests/<name>/assert-fixtures.sh -> workflow fixture run - tests/**/tsconfig.json -> tsc - tests/**/*.test.mjs -> node --test - codemod workflow validate --list prints the plan without executing; the runner has its own CI regression test (scripts/test-run-recipe-tests.sh) following the existing scripts/test-*.sh convention. OpenAPI fixture dirs renamed to match their script basenames so the mapping is uniform. Migration recipe layout documented in CONTRIBUTING.md.
Convert run-recipe-tests from a root bash script referenced by relative path into TypeScript in packages/recipe-test-runner, a private workspace package exposing a run-recipe-tests bin. Recipes declare it as a devDependency and their test script is simply "run-recipe-tests" - no relative paths and no coupling to workspace depth. Node 24 runs the .ts bin natively. The regression test moves with it (test.ts, still run by CI via scripts/test-recipe-test-runner.sh and root yarn test). Discovery rules, --list plan mode and output are unchanged.
Replace every bash script with TypeScript bins in the private @backstage/scripts workspace package (packages/scripts), using cleye for CLI parsing and node:test for the regression tests: - check-changed, format-staged, test-staged (quality gates and lint-staged hooks) - generate-readme-codemods, sync-codemod-versions (generation/versioning) - tag-and-publish, sync-readme-pr (release automation) - run-recipe-tests (migration recipe test runner; the separate recipe-test-runner package is folded in and removed) Entry points reference bins by name: root package.json scripts, .lintstagedrc.json, .husky hooks and the CI/release/sync-readme workflows. runYarn executes Yarn's JS entry directly when available and falls back to a quoted shell invocation for corepack shims; git and gh run as native executables with argument arrays. The recipe's assert-fixtures suite is TypeScript too - no bash remains in the repo. The package is linted and formatted exactly like the codemods: type-aware oxlint with the same strict tsconfig, oxfmt over src/, tests/ ignored like codemod fixtures. Regression tests port every scripts/test-*.sh scenario (argv batching, paths with spaces, failure propagation, hook and gh-stub lifecycles); Unix-only scenarios are skipped on Windows and covered by CI.
Two bugs that only run on Linux (the scenarios are skipped on Windows): - sync-readme-pr inverted the staged-changes guard: git diff --cached --quiet exits 0 when nothing is staged, so the failure condition must trigger when it does NOT exit 0 - the pre-commit hook regression was missing the executable lint-staged shim in the fixture repo's node_modules/.bin and the git init/staging setup lint-staged requires; runSync failures now surface the bin's output in the assertion message
… regression The fixture shim pointed at bin/lint-staged.mjs, but lint-staged's bin is bin/lint-staged.js; resolve it from the installed manifest so the shim survives bin renames.
The changed-codemod gate runs the recipe's 27 workflow-level fixtures and outran the Test job's 15-minute timeout on 2-core runners (the bash-era runs already brushed against it). Two changes: - assert-fixtures runs fixtures concurrently (4 workers); fixtures are independent workflow runs, and each now has a 5-minute timeout that dumps the workflow log instead of hanging on a stuck CLI - Test job timeout raised to 40 minutes with a comment
The assert-fixtures timeout resolved its promise on 'close', which only fires once the stdout pipe drains - a killed shell leaves codemod holding the pipe, so a hung workflow run stalled the whole suite with no diagnostics (the 40-minute CI cancellation). Resolve on 'exit', kill the process group on unix, and drop the per-fixture budget to 120s; the timeout now dumps the workflow log so the hang is visible.
The run step executed node -e with an unquoted parenthesized payload, which is a syntax error under sh on Linux (cmd accepted it, so Windows runs passed). The failed step then triggered the codemod CLI's post-failure scheduler hang, stalling every workflow fixture on CI. Double quotes are valid in both shells; verified with an sh parse check and a full Windows apply.
The Linux shell-step fix removed the hang that needed the headroom; fixture runs now finish in seconds.
- tag-and-publish aborted only via the original bash's set -e; the TypeScript port parsed git ls-remote output without checking status, so a failed remote lookup looked like an empty tag set and re-tagged already-released codemods. Abort on lookup failure; add regressions for the failure path and the create/push/idempotence success path. - assert-fixtures created a unix process group only in the shell-Yarn branch; the JS-entry branch could ESRCH on timeout. Detach both branches and tolerate the exit race in the kill. - runYarn's cmd.exe fallback expands %VAR% inside double quotes; fail loudly on percent-containing arguments rather than silently rewriting them (both spawn helpers). - restore the failing-package hook regression: the pre-commit hook must fail when a package test fails, after running both packages.
run-recipe-tests now structurally validates workflow.yaml before any suite runs: every js-ast-grep js_file and run-step CODEMOD_SCRIPT must exist locally and registry codemod: steps are rejected. Every future recipe inherits the check; recipes keep only recipe-specific tests. The 1.54 recipe's workflow.test.mjs gains the recipe-specific coverage from the review: exact step ordering, the state couplings (zod index before its transform, package.json step before the shell step reading targetDirEncoded) and the quoted node -e payload that keeps the run step valid under both sh and cmd.
Summary
@backstage/v1-54-0-migration-recipeis now the only published Backstage 1.54.0 artifact:scripts/folder, each kept separate so users can still run one individually (see recipe README).js-ast-grep/shell step instead of nesting registry codemod packages. This avoids the CLI 1.18.3 bug where nested workflow steps fail with unboundstate.targetDirEncodedin the OpenAPI shell step.packages/scripts: every former bash script (quality gates, README generation, release automation, the recipe test runner) is now a cleye-based bin in the private@backstage/scriptsworkspace package, withnode:testports of every regression test. Entry points (root scripts, lint-staged, husky, CI/release workflows) call bins by name — no bash and no relative paths remain in the repo. The package is linted and formatted exactly like the codemods (type-aware oxlint, oxfmt, same strict tsconfig).tests/<name>/matching itsscripts/<name>.ts, discovered by therun-recipe-testsbin from@backstage/scripts: jssg fixtures (language inferred from fixture extension),assert-fixtures.tsworkflow-level app trees, fixturetsconfig.jsontype checks,*.test.mjsunit tests. Adding a step never changes a test command; a recipe's test script is justrun-recipe-tests. The runner has--listplan mode and its own regression tests; the recipe layout is documented in CONTRIBUTING.md as the pattern for future versions.aiFixup=false; the optional AI node is gated byif: params.aiFixup == trueand remains untested/unapproved. Minor changeset included. Relates to feat: Backstage 1.54.0 migration - add v1.54.0 migration recipe workflow #155.Evidence
package.json+.github/workflows/ci.yml+check.sh, each containingbackstage-repo-tools repo schema openapi verify): all 8 steps succeed and all three files are rewritten tovalidate. The previously failing shell step now bindsstate.targetDirEncodedbecause state is set by the package.json step in the same workflow. ai-fixup node correctly skipped (condition not met: params.aiFixup == true).yarn testat the root (all workspaces) passes at8e9a0c7: the scripts package's 13node:testregressions (argv batching, paths with spaces, failure propagation, wiring), the recipe's 65 jssg fixtures, 27 workflow-level fixtures, tsc, unit tests andcodemod workflow validate. The TS README generator reproduces the committed README byte-for-byte.yarn lint,yarn formatand the fullyarn check:changedgate (now itself a TS bin, also run by the pre-push hook) pass. Unix-only regression scenarios (sh/gh stubs) are skipped on Windows and run on CI's ubuntu runner.yarn lint,yarn format, and the fullyarn check:changedCI gate pass at8e9a0c7../command.jsimport path in the OpenAPI scripts, exercised by the apply above and the unit tests.openai-codexused.Merge notes
The five packages were already published at v0.2.0; removing them here stops further releases of the standalone packages — the recipe becomes the single 1.54.0 release artifact. Published registry entries remain until deprecated separately.
Full Backstage-app QA (running the recipe against a real upgraded app) is still open; the scratch-app apply proves the workflow mechanics and the previously failing state binding, and the restored fixtures cover each step's transform behavior.