-
Notifications
You must be signed in to change notification settings - Fork 15
feat(prepare-sources): scaffold pipeline CLI and GitHub Action #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
davidfestal
merged 17 commits into
redhat-developer:main
from
davidfestal:add-downstream-prepare-source-cli
Aug 25, 2026
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
2ec5aaf
feat(prepare-rhdh-supported-sources): scaffold pipeline CLI and GitHu…
davidfestal 6891ffd
chore(prepare-rhdh-supported-sources): add check:fix script and refor…
davidfestal e443db0
chore(prepare-rhdh-supported-sources): use npm ci --ignore-scripts fo…
davidfestal da22413
fix(prepare-rhdh-supported-sources): use locale-aware sort in test ut…
davidfestal 1a1759c
refactor(prepare-sources): rename from prepare-rhdh-supported-sources
jonkoops 33f9856
refactor(prepare-sources): update deps and tighten tooling config
jonkoops 9cd9a2a
chore(add readme)
davidfestal d7289df
fix: review comments
davidfestal fcabb69
docs(prepare-sources): trim README based on review feedback
jonkoops 34ef894
refactor(prepare-sources): consolidate args into cli and improve UX
jonkoops bfe2ecb
refactor(prepare-sources): use explicit resource management for temp …
jonkoops fad7893
refactor(prepare-sources): simplify main() control flow
jonkoops 30c211a
refactor(prepare-sources): extract source.json parsing into dedicated…
jonkoops 3443067
refactor(prepare-sources): use named interfaces for CliArgs union
jonkoops a6e5cf2
refactor(prepare-sources): move PipelineInputs and ModuleContext into…
jonkoops 380ab62
chore(prepare-sources): enable suspicious lint category
jonkoops 571fa5a
refactor(prepare-sources): improve pipeline error handling and logging
jonkoops File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| name: Check prepare-sources | ||
| on: | ||
| push: | ||
| paths: | ||
| - prepare-sources/** | ||
| pull_request: | ||
| paths: | ||
| - prepare-sources/** | ||
|
|
||
| jobs: | ||
| check: | ||
| runs-on: ubuntu-latest | ||
| defaults: | ||
| run: | ||
| working-directory: prepare-sources | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
|
|
||
| - name: Setup Vite+ | ||
| uses: voidzero-dev/setup-vp@313600b80b104eadebb9111787d37a2e83e014ca # v1.17.0 | ||
| with: | ||
| working-directory: prepare-sources | ||
| cache: true | ||
|
|
||
| - name: Lint and typecheck | ||
| run: vp check | ||
|
|
||
| - name: Run tests | ||
| run: vp test |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "recommendations": ["VoidZero.vite-plus-extension-pack"] | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "npm.scriptRunner": "vp", | ||
| "editor.defaultFormatter": "oxc.oxc-vscode", | ||
| "[javascript]": { | ||
| "editor.defaultFormatter": "oxc.oxc-vscode" | ||
| }, | ||
| "[javascriptreact]": { | ||
| "editor.defaultFormatter": "oxc.oxc-vscode" | ||
| }, | ||
| "[typescript]": { | ||
| "editor.defaultFormatter": "oxc.oxc-vscode" | ||
| }, | ||
| "[typescriptreact]": { | ||
| "editor.defaultFormatter": "oxc.oxc-vscode" | ||
| }, | ||
| "oxc.disableNestedConfig": true, | ||
| "oxc.fmt.disableNestedConfig": true, | ||
| "editor.formatOnSave": true, | ||
| "editor.formatOnSaveMode": "file", | ||
| "editor.codeActionsOnSave": { | ||
| "source.fixAll.oxc": "explicit" | ||
| } | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| <!--VITE PLUS START--> | ||
|
|
||
| # Using Vite+, the Unified Toolchain for the Web | ||
|
|
||
| This project uses Vite+ for tooling (Vitest, Oxlint, Oxfmt). Vite+ wraps these tools in a single global CLI called `vp`. Run `vp help` to print a list of commands and `vp <command> --help` for information about a specific command. | ||
|
|
||
| Docs are local at `node_modules/vite-plus/docs` or online at https://viteplus.dev/guide/. | ||
|
|
||
| ## Built-in Commands vs Scripts | ||
|
|
||
| `vp <name>` runs a built-in command. `vp run <name>` runs a `package.json` script or a `vite.config.ts` task. Scripts cannot overwrite built-ins, so `vp dev` and `vp run dev` may do different things. Check `package.json` and `vite.config.ts` first, and run `vp run <name>` when the project defines a script or task with that name. | ||
|
|
||
| ## Tool Versions | ||
|
|
||
| Run `vp toolchain` to show versions and relationships in the active Vite+ | ||
| release. Add a tool name to select part of the graph. For example, run | ||
| `vp toolchain vite`. Use `--global` to ignore the local `vite-plus` package. Use | ||
| `vp why <package>` to show the package-manager dependency graph. | ||
|
|
||
| ## Review Checklist | ||
|
|
||
| - [ ] Run `vp install` after pulling remote changes and before getting started. | ||
| - [ ] Run `vp check` and `vp test` to format, lint, type check and test changes. | ||
| - [ ] Check if there are `vite.config.ts` tasks or `package.json` scripts necessary for validation, run via `vp run <script>`. | ||
| - [ ] If setup, runtime, or package-manager behavior looks wrong, run `vp env doctor` and include its output when asking for help. | ||
|
|
||
| <!--VITE PLUS END--> |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| # prepare-sources | ||
|
|
||
| A TypeScript CLI that transforms a workspace — after the overlay `export-dynamic` workflow has performed the initial `rhdh-cli plugin export` — into a prepared source OCI artifact suitable for downstream Konflux builds. | ||
|
|
||
| ## Usage | ||
|
|
||
| ```bash | ||
| node lib/cli.ts \ | ||
| --workspace-path <path-to-source-workspace> \ | ||
| --overlay-path <path-to-overlay-workspace> | ||
| ``` | ||
|
|
||
| `source.json` is read from `<overlay-path>/source.json` automatically. | ||
|
|
||
| **Optional flags:** | ||
|
|
||
| | Flag | Purpose | | ||
| | ----------------------- | ---------------------------------------- | | ||
| | `-h`, `--help` | Print usage information and exit | | ||
| | `--list-modules` | Print ordered module names and exit | | ||
| | `--start-from=<module>` | Skip all modules before the named module | | ||
| | `--stop-after=<module>` | Halt after completing the named module | | ||
|
|
||
| ## Pipeline Modules | ||
|
|
||
| | # | Module | Purpose | Status | | ||
| | --- | ------------------------- | ------------------------------------------------------------------------------ | --------------- | | ||
| | 1 | `seed-frontend-lockfiles` | Seed `dist-dynamic/yarn.lock` for frontend plugins after the initial export | Not implemented | | ||
| | 2 | `make-self-contained` | Merge repo-root `.yarn/` and `.yarnrc.yml` into the workspace (non-flat repos) | Not implemented | | ||
| | 3 | `generate-manifests` | Produce `manifest.json` and `backstage-manifest.json` for protocol resolution | Not implemented | | ||
| | 4 | `plugin-removal` | Remove unsupported/community plugins and update `plugins-list.yaml` | Not implemented | | ||
| | 5 | `file-cleanup` | Strip test files, mocks, stories, and dev-only artifacts | Not implemented | | ||
| | 6 | `protocol-resolution` | Resolve `workspace:^` and `backstage:^` protocols; generate `type-shims` | Not implemented | | ||
| | 7 | `package-cleanup` | Remove scrubbed entries from `yarn.lock`, clean `package.json` workspaces list | Not implemented | | ||
| | 8 | `hermetic-prep` | Remove `packageManager` and monorepo `postinstall` scripts for Konflux | Not implemented | | ||
| | 9 | `inject-build-tools` | Add `rhdh-cli` as a `file:` devDependency for offline export | Not implemented | | ||
| | 10 | `build` | Run `yarn install` + `tsc` + build to validate the transformation | Not implemented | | ||
| | 11 | `re-export` | Re-export plugins, seed frontend lockfiles, extract OCI annotations | Not implemented | | ||
| | 12 | `validate` | Compare initial-export vs. re-export to detect dependency drift | Not implemented | | ||
| | 13 | `construct-artifact` | Bundle the validated workspace into an OCI artifact with annotations | Not implemented | | ||
|
|
||
| Each module performs an isolated transformation on the workspace directory in sequence. The only shared context (`ModuleContext`) carries the workspace path, overlay path, parsed `source.json`, and a name-prefixed logger. This makes each module independently testable and executable with fixture directories. | ||
|
|
||
| Each module lives in `lib/modules/<name>/` with co-located tests and `__fixtures__/`. See the `template` module (`lib/modules/template/`) for the canonical structure. | ||
|
|
||
| ## Test Strategy | ||
|
|
||
| Every module test follows the file-system-in, file-system-out pattern: | ||
|
|
||
| 1. Create a fixture directory with the module's expected input files | ||
| 2. Invoke the module's `run` function, passing a `ModuleContext` pointing at the fixture | ||
| 3. Assert on the output files (contents, presence/absence, structure) | ||
|
|
||
| ### Fixture conventions | ||
|
|
||
| Each fixture case is a subdirectory under `__fixtures__/`: | ||
|
|
||
| ``` | ||
| __fixtures__/<case>/ | ||
| ├── input/ | ||
| │ ├── workspace/ → copied into a temp workspace dir | ||
| │ └── overlay/ → copied into a temp overlay dir | ||
| │ └── source.json → parsed into ctx.source | ||
| ├── output/ → (optional) | ||
| │ ├── workspace/ → asserted against workspace after run | ||
| │ └── overlay/ → asserted against overlay after run | ||
| └── error → (optional) expected error message or /regex/ | ||
| ``` | ||
|
|
||
| - If `output/<side>/` is present, the test asserts the result matches it exactly. | ||
| - If `output/<side>/` is absent, the test asserts no changes from `input/<side>/` (immutability). | ||
| - If an `error` file is present, the test asserts the module throws with the given message (or regex pattern if wrapped in `/slashes/`). | ||
|
|
||
| The `testInputOutputExpectations()` helper in `lib/test-utils.ts` auto-generates test cases from all fixture subdirectories. See `lib/modules/template/index.test.ts` for a complete example. | ||
|
|
||
| ## Development | ||
|
jonkoops marked this conversation as resolved.
|
||
|
|
||
| Install [Vite+](https://viteplus.dev/guide/) globally, then: | ||
|
|
||
| ```bash | ||
| vp install | ||
| ``` | ||
|
|
||
| Lint, format, and typecheck: | ||
|
|
||
| ```bash | ||
| vp check | ||
| ``` | ||
|
|
||
| Run tests: | ||
|
|
||
| ```bash | ||
| vp test | ||
| ``` | ||
|
davidfestal marked this conversation as resolved.
|
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| name: Prepare Sources | ||
| description: > | ||
| Runs the prepare-sources pipeline against a workspace after the overlay | ||
| export-dynamic run (initial rhdh-cli plugin export). Invokes the CLI via | ||
| Node type stripping (no npm install). | ||
| inputs: | ||
| workspace-path: | ||
| description: Absolute path to the source workspace directory being transformed | ||
| required: true | ||
| overlay-path: | ||
| description: Absolute path to the overlay workspace directory (contains source.json) | ||
| required: true | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| - name: Prepare sources | ||
| shell: bash | ||
| working-directory: ${{ github.action_path }} | ||
| run: > | ||
| node lib/cli.ts | ||
| --workspace-path "${{ inputs.workspace-path }}" | ||
| --overlay-path "${{ inputs.overlay-path }}" |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { describe, expect, it } from "vite-plus/test"; | ||
|
|
||
| import { parseArgs } from "./cli.ts"; | ||
|
|
||
| describe("parseArgs", () => { | ||
| it("parses --help", () => { | ||
| expect(parseArgs(["--help"])).toEqual({ command: "help" }); | ||
| }); | ||
|
|
||
| it("parses -h", () => { | ||
| expect(parseArgs(["-h"])).toEqual({ command: "help" }); | ||
| }); | ||
|
|
||
| it("parses --list-modules", () => { | ||
| expect(parseArgs(["--list-modules"])).toEqual({ command: "list-modules" }); | ||
| }); | ||
|
|
||
| it("parses required run paths", () => { | ||
| expect(parseArgs(["--workspace-path", "/tmp/ws", "--overlay-path", "/tmp/overlay"])).toEqual({ | ||
| command: "run", | ||
| workspacePath: "/tmp/ws", | ||
| overlayPath: "/tmp/overlay", | ||
| startFrom: undefined, | ||
| stopAfter: undefined, | ||
| }); | ||
| }); | ||
|
|
||
| it("parses optional slice bounds", () => { | ||
| expect( | ||
| parseArgs([ | ||
| "--workspace-path=/tmp/ws", | ||
| "--overlay-path=/tmp/overlay", | ||
| "--start-from=plugin-removal", | ||
| "--stop-after=validate", | ||
| ]), | ||
| ).toEqual({ | ||
| command: "run", | ||
| workspacePath: "/tmp/ws", | ||
| overlayPath: "/tmp/overlay", | ||
| startFrom: "plugin-removal", | ||
| stopAfter: "validate", | ||
| }); | ||
| }); | ||
|
|
||
| it("requires workspace and overlay paths for run mode", () => { | ||
| expect(() => parseArgs([])).toThrow("Missing required --workspace-path and --overlay-path"); | ||
| expect(() => parseArgs(["--workspace-path", "/tmp/ws"])).toThrow( | ||
| "Missing required --overlay-path", | ||
| ); | ||
| expect(() => parseArgs(["--overlay-path", "/tmp/ov"])).toThrow( | ||
| "Missing required --workspace-path", | ||
| ); | ||
| }); | ||
|
|
||
| it("lets --list-modules short-circuit other flags", () => { | ||
| expect( | ||
| parseArgs([ | ||
| "--list-modules", | ||
| "--workspace-path", | ||
| "/tmp/ws", | ||
| "--overlay-path", | ||
| "/tmp/overlay", | ||
| ]), | ||
| ).toEqual({ command: "list-modules" }); | ||
| }); | ||
|
|
||
| it("rejects unknown flags", () => { | ||
| expect(() => parseArgs(["--nope"])).toThrow(); | ||
| }); | ||
| }); |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import { parseArgs as parse } from "node:util"; | ||
| import { MODULES } from "./modules.ts"; | ||
| import { loadPipelineInputs, runPipeline } from "./pipeline.ts"; | ||
|
|
||
| interface HelpArgs { | ||
| command: "help"; | ||
| } | ||
|
|
||
| interface ListModulesArgs { | ||
| command: "list-modules"; | ||
| } | ||
|
|
||
| interface RunArgs { | ||
| command: "run"; | ||
| workspacePath: string; | ||
| overlayPath: string; | ||
| startFrom?: string; | ||
| stopAfter?: string; | ||
| } | ||
|
|
||
| export type CliArgs = HelpArgs | ListModulesArgs | RunArgs; | ||
|
|
||
| const USAGE = `Usage: prepare-sources --workspace-path=PATH --overlay-path=PATH [OPTION]... | ||
| or: prepare-sources --list-modules | ||
|
|
||
| Options: | ||
| --workspace-path=PATH path to the workspace directory | ||
| --overlay-path=PATH path to the overlay directory | ||
| --start-from=MODULE start pipeline from MODULE | ||
| --stop-after=MODULE stop pipeline after MODULE | ||
| --list-modules list available pipeline modules and exit | ||
| -h, --help display this help and exit`; | ||
|
|
||
| export function parseArgs(argv: string[]): CliArgs { | ||
| const { values } = parse({ | ||
| args: argv, | ||
| options: { | ||
| "list-modules": { type: "boolean", default: false }, | ||
| "workspace-path": { type: "string" }, | ||
| "overlay-path": { type: "string" }, | ||
| "start-from": { type: "string" }, | ||
| "stop-after": { type: "string" }, | ||
| help: { type: "boolean", short: "h", default: false }, | ||
| }, | ||
| strict: true, | ||
| allowPositionals: false, | ||
| }); | ||
|
|
||
| if (values.help) { | ||
| return { command: "help" }; | ||
| } | ||
|
|
||
| if (values["list-modules"]) { | ||
| return { command: "list-modules" }; | ||
| } | ||
|
|
||
| const workspacePath = values["workspace-path"]; | ||
| const overlayPath = values["overlay-path"]; | ||
|
|
||
| if (!workspacePath || !overlayPath) { | ||
| const missing = [ | ||
| ...(!workspacePath ? ["--workspace-path"] : []), | ||
| ...(!overlayPath ? ["--overlay-path"] : []), | ||
| ]; | ||
| const list = new Intl.ListFormat("en", { type: "conjunction" }); | ||
| throw new Error(`Missing required ${list.format(missing)}`); | ||
| } | ||
|
|
||
| return { | ||
| command: "run", | ||
| workspacePath, | ||
| overlayPath, | ||
| startFrom: values["start-from"], | ||
| stopAfter: values["stop-after"], | ||
| }; | ||
| } | ||
|
|
||
| export async function main(): Promise<void> { | ||
| const args = parseArgs(process.argv.slice(2)); | ||
|
|
||
| if (args.command === "help") { | ||
| console.log(USAGE); | ||
| } else if (args.command === "list-modules") { | ||
| for (const { name } of MODULES) { | ||
| console.log(name); | ||
| } | ||
| } else if (args.command === "run") { | ||
| const inputs = loadPipelineInputs(args.workspacePath, args.overlayPath); | ||
| await runPipeline(MODULES, inputs, args.startFrom, args.stopAfter); | ||
| } | ||
| } | ||
|
|
||
| if (import.meta.main) { | ||
| await main(); | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import type { ModuleContext, PipelineModule } from "./pipeline.ts"; | ||
|
|
||
| async function notImplemented(ctx: ModuleContext): Promise<void> { | ||
| ctx.log("not yet implemented"); | ||
| } | ||
|
|
||
| /** Order is the contract. Replace `notImplemented` with a real module import when implementing. */ | ||
| export const MODULES: readonly PipelineModule[] = [ | ||
| { name: "seed-frontend-lockfiles", run: notImplemented }, | ||
| { name: "make-self-contained", run: notImplemented }, | ||
| { name: "generate-manifests", run: notImplemented }, | ||
| { name: "plugin-removal", run: notImplemented }, | ||
| { name: "file-cleanup", run: notImplemented }, | ||
| { name: "protocol-resolution", run: notImplemented }, | ||
| { name: "package-cleanup", run: notImplemented }, | ||
| { name: "hermetic-prep", run: notImplemented }, | ||
| { name: "inject-build-tools", run: notImplemented }, | ||
| { name: "build", run: notImplemented }, | ||
| { name: "re-export", run: notImplemented }, | ||
| { name: "validate", run: notImplemented }, | ||
| { name: "construct-artifact", run: notImplemented }, | ||
| ]; |
1 change: 1 addition & 0 deletions
1
prepare-sources/lib/modules/template/__fixtures__/error-regex/error
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| /precondition failed.*plugins-list/ |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.