Skip to content
Merged
Show file tree
Hide file tree
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 Aug 3, 2026
6891ffd
chore(prepare-rhdh-supported-sources): add check:fix script and refor…
davidfestal Aug 3, 2026
e443db0
chore(prepare-rhdh-supported-sources): use npm ci --ignore-scripts fo…
davidfestal Aug 11, 2026
da22413
fix(prepare-rhdh-supported-sources): use locale-aware sort in test ut…
davidfestal Aug 11, 2026
1a1759c
refactor(prepare-sources): rename from prepare-rhdh-supported-sources
jonkoops Aug 18, 2026
33f9856
refactor(prepare-sources): update deps and tighten tooling config
jonkoops Aug 18, 2026
9cd9a2a
chore(add readme)
davidfestal Aug 20, 2026
d7289df
fix: review comments
davidfestal Aug 20, 2026
fcabb69
docs(prepare-sources): trim README based on review feedback
jonkoops Aug 24, 2026
34ef894
refactor(prepare-sources): consolidate args into cli and improve UX
jonkoops Aug 24, 2026
bfe2ecb
refactor(prepare-sources): use explicit resource management for temp …
jonkoops Aug 25, 2026
fad7893
refactor(prepare-sources): simplify main() control flow
jonkoops Aug 25, 2026
30c211a
refactor(prepare-sources): extract source.json parsing into dedicated…
jonkoops Aug 25, 2026
3443067
refactor(prepare-sources): use named interfaces for CliArgs union
jonkoops Aug 25, 2026
a6e5cf2
refactor(prepare-sources): move PipelineInputs and ModuleContext into…
jonkoops Aug 25, 2026
380ab62
chore(prepare-sources): enable suspicious lint category
jonkoops Aug 25, 2026
571fa5a
refactor(prepare-sources): improve pipeline error handling and logging
jonkoops Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/check-prepare-sources.yaml
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
3 changes: 3 additions & 0 deletions prepare-sources/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["VoidZero.vite-plus-extension-pack"]
}
23 changes: 23 additions & 0 deletions prepare-sources/.vscode/settings.json
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"
}
}
27 changes: 27 additions & 0 deletions prepare-sources/AGENTS.md
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-->
94 changes: 94 additions & 0 deletions prepare-sources/README.md
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
Comment thread
jonkoops marked this conversation as resolved.

| # | 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
Comment thread
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
```
23 changes: 23 additions & 0 deletions prepare-sources/action.yaml
Comment thread
davidfestal marked this conversation as resolved.
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 }}"
70 changes: 70 additions & 0 deletions prepare-sources/lib/cli.test.ts
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();
});
});
95 changes: 95 additions & 0 deletions prepare-sources/lib/cli.ts
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();
}
22 changes: 22 additions & 0 deletions prepare-sources/lib/modules.ts
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 },
];
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/precondition failed.*plugins-list/
Loading