Skip to content

Commit f0e5eeb

Browse files
committed
feat(export): export consistent yarn project
This change updates the CLI's frontend script to create a yarn.lock based on the exported plugin package in a similar fashion to backend plugins. This makes the exported plugin package for frontend plugins more consistent with backend plugins and allows another means for security scanners to inspect the plugin's dependencies. This change also moves functions that are shared between the backend and frontend commands into a shared utils file so it's more obvious which functions are common to each command. A new .yarnrc.yml is created in the exported plugin package to ensure consistency when running "yarn install" during plugin export. Also workspace resolutions are taken from the adjacent package.json file when the yarn lockfile is discovered and applied to the derived exported package. This change also adds some documents that summarize the plugin export process. Finally this change adds a prepare script so that it's possible to run this command with changes from a pull request without having to perform a release for testing purposes. Assisted-By: Cursor Desktop rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED
1 parent 2f84c04 commit f0e5eeb

19 files changed

Lines changed: 1451 additions & 606 deletions

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
2-
ignorePatterns: ['templates/**'],
2+
ignorePatterns: ['templates/**', 'coverage/**', '**/lcov-report/**'],
33
rules: {
44
'no-console': 0,
55
},

CHANGELOG.md

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,90 @@
1-
# @red-hat-developer-hub/cli
1+
# Changelog
22

3-
## 0.0.2
3+
<!-- markdownlint-disable MD013 MD024 -->
44

5-
### Patch Changes
5+
All notable changes to `@red-hat-developer-hub/cli` are documented here. This project no longer uses Changesets; releases are versioned in `package.json` and described here (and in conventional commit messages / PR titles).
66

7-
- 66e629b: fix missing node-stdlib-browser update in scalprumConfig.ts
7+
## 1.10.3
8+
9+
### Added
10+
11+
- **`prepare` lifecycle**: when `dist/` is missing (for example after cloning or installing from a git ref), runs `yarn build` so colleagues can try a PR via `npx` / `yarn dlx` without a separate build step. Skipped when `dist/` already exists (for example the published npm tarball).
12+
- **Plugin export — workspace `resolutions`**: `plugin export` merges `resolutions` from the `package.json` next to the same `yarn.lock` used for the export (plugin package or monorepo root). Non-portable values (`workspace:`, `portal:`, `link:` at value start, and non-string shapes) are omitted with a warning. Merge order: built-in AWS workarounds, then packed manifest, then workspace, then backend `additionalResolutions` (embedded `file:` still wins on conflicts).
13+
- **Documentation**: maintainer guide for the export pipeline under [`doc/plugin-export/`](doc/plugin-export/README.md).
14+
15+
### Changed
16+
17+
- **Frontend dynamic plugin export**: Yarn project setup and install behavior aligned with backend export (lockfile, **generated minimal** Berry **`.yarnrc.yml`**, logging where applicable).
18+
- **Dependencies**: `fast-xml-parser` 5.5.7 (#87).
19+
20+
### Fixed
21+
22+
- **Yarn Berry `dist-dynamic/.yarnrc.yml`**: no longer a copy of the monorepo file; the exporter writes a **minimal generated** config (**`httpTimeout`** + **`nodeLinker: node-modules`**) so **`yarn install`** does not fail when the parent repo lists Berry **plugins** (e.g. under **`.yarn/plugins/`**) that are not present in the export tree.
23+
- **Yarn lockfile** for frontend plugin export installs (`yarn.lock` consistency).
24+
25+
## 1.10.2
26+
27+
### Changed
28+
29+
- **@backstage/cli** updated to **0.35.4** (#86).
30+
31+
### Added
32+
33+
- **jest-environment-jsdom** (dev) for the test toolchain.
34+
35+
## 1.10.1
36+
37+
### Changed — 1.10.1
38+
39+
- **Node.js**: CI and tooling moved to **Node 22** (#54).
40+
- **Dependencies**: broad updates including **ESLint 9**, **webpack** / **@backstage/cli-common**, **axios**, **lodash** / **lodash-es**, **tar** (dev), **fast-xml-parser**, **ajv**, **undici**, **diff**, **jsonpath**, **node-forge**, **jws**, and others (Dependabot and manual bumps through #57#62, #65#66, #68#69, #81, #83).
41+
42+
### Fixed
43+
44+
- **`plugin export`**: clearer handling when **`yarn install`** fails under `dist-dynamic` (log path, fail fast) — RHDHBUGS-2819 (#70, #73; see also #77).
45+
46+
## 1.10.0
47+
48+
### Changed
49+
50+
- **Version** aligned with RHDH **1.10** line.
51+
52+
### Fixed
53+
54+
- **E2E tests** for community plugin build (#52).
55+
- **Webpack** dependency alignment across the tree (#39).
56+
57+
## 1.9.1
58+
59+
### Fixed — 1.9.1
60+
61+
- **Webpack** / **webpack-dev-server** version inconsistency in published metadata (#35 follow-up).
62+
63+
## 1.9.0
64+
65+
### Added
66+
67+
- **`--generate-module-federation-assets`** (and inverse) for **frontend** `plugin export`, emitting standard Module Federation assets alongside Scalprum (#31).
68+
- **`generate-types`** script and **Backstage CLI** dependency bump for the build.
69+
70+
### Changed
71+
72+
- **Version** aligned with RHDH **1.9** line.
73+
74+
### Fixed
75+
76+
- **CLI binary** name / packaging (`rhdh-cli`) (#26).
77+
78+
## 1.8.0 and earlier
79+
80+
Releases **1.8.x** and below used the same tagging process (`tagRelease.sh` / automated bump PRs). Notable older changes include:
81+
82+
- **`plugin package`**: correct export command in user-facing text (#22).
83+
- **README**: versioning notes; move away from Changesets toward conventional commits (#20).
84+
- **E2E**: optional archive download instead of git clone for community plugin build tests.
85+
86+
## Historic (0.x)
87+
88+
### 0.0.2
89+
90+
- Fix missing **node-stdlib-browser** update in `scalprumConfig.ts` (66e629b; Changesets-era entry).

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ This repository hosts the source code for the rhdh-cli utility, a new command-li
44

55
This new CLI aims to offer more flexibility and ease of use compared to the previous @janus-idp/cli.
66

7+
## Documentation
8+
9+
- **[Plugin export pipeline](doc/plugin-export/README.md)** — how `plugin export` works for backend and frontend plugins (`dist-dynamic`, packaging, and related CLI flags).
10+
711
<!-- prettier breaks the formating for GitHub Markdown callout, this is why this whole block is ignored -->
812
<!-- prettier-ignore-start -->
913
> [!TIP]

doc/plugin-export/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# `plugin export` — documentation index
2+
3+
This folder describes how **`rhdh-cli plugin export`** repackages a Backstage plugin into **`./dist-dynamic`** for use as a dynamic plugin. It is aimed at maintainers and contributors who need to understand or explain the pipeline.
4+
5+
## Quick links
6+
7+
| Document | Contents |
8+
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
9+
| [command-flow.md](./command-flow.md) | Shared steps after backend vs frontend dispatch: config schema, `supported-versions`, `--dev` |
10+
| [backend-export.md](./backend-export.md) | Backend / backend-module export phases (`backend.ts`) |
11+
| [frontend-export.md](./frontend-export.md) | Frontend / frontend-module export phases (`frontend.ts`) |
12+
| [shared-packaging.md](./shared-packaging.md) | `productionPack`, `customizeForDynamicUse`, workspace `resolutions` inheritance, `initializeYarnProject` |
13+
14+
## Command and entrypoints
15+
16+
- **CLI registration**: [`src/commands/index.ts`](../../src/commands/index.ts) (`plugin export` subcommand and flags).
17+
- **Orchestrator**: [`src/commands/export-dynamic-plugin/command.ts`](../../src/commands/export-dynamic-plugin/command.ts) — reads `backstage.role`, calls `backend()` or `frontend()`, then writes config schema, checks `supported-versions`, applies `--dev`.
18+
19+
**Example** (from the plugin package directory):
20+
21+
```bash
22+
npx @red-hat-developer-hub/cli plugin export
23+
```
24+
25+
## Prerequisites
26+
27+
- The **current working directory** (or Backstage CLI target) must be a package whose **`package.json`** defines **`backstage.role`**.
28+
- **Supported roles** (see `command.ts`): `backend-plugin`, `backend-plugin-module`, `frontend-plugin`, `frontend-plugin-module`. Other roles are rejected.
29+
30+
## Output
31+
32+
- Primary artifact: **`dist-dynamic/`** under the plugin package, containing a derived **`package.json`**, optional **`yarn.lock`** / **`.yarnrc.yml`**, **`packageManager`**, and built assets (layout differs by role; see backend vs frontend docs).
33+
- **Config schema** JSON is written under paths that depend on role (see [command-flow.md](./command-flow.md)).
34+
35+
## CLI options matrix
36+
37+
Flags are registered on `plugin export` for all roles; **only some are honored** depending on implementation.
38+
39+
| Option | Backend | Frontend | Notes |
40+
| -------------------------------------------------------------------------------- | :-----: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
41+
| `--embed-package` | Yes || |
42+
| `--shared-package` | Yes || Moves matching deps to `peerDependencies` (plus default `@backstage/*`). |
43+
| `--allow-native-package` | Yes || |
44+
| `--suppress-native-package` | Yes || |
45+
| `--ignore-version-check` | Yes || Relaxes semver checks when embedding / hoisting peers. |
46+
| `--no-install` / `--install` | Yes | Yes | Commander exposes `--no-install`. Backend help says “backend only” but **frontend** [`handlePackageInstall`](../../src/commands/export-dynamic-plugin/frontend.ts) also respects it. |
47+
| `--no-build` / `--build` | Yes || |
48+
| `--clean` | Yes | Yes | |
49+
| `--dev` | Yes | Yes | Symlink `src` for **node** platform; frontend gets symlink/copy without `src` link. See [command-flow.md](./command-flow.md). |
50+
| `--dynamic-plugins-root` | Yes | Yes | Used with `--dev` when copying instead of symlinking. |
51+
| `--scalprum-config` || Yes | |
52+
| `--track-dynamic-manifest-and-lock-file` | Yes | Yes | Whitelists `package.json`, `yarn.lock`, `.yarnrc.yml` inside `dist-dynamic/.gitignore`. |
53+
| `--generate-scalprum-assets` / `--no-generate-scalprum-assets` || Yes | |
54+
| `--generate-module-federation-assets` / `--no-generate-module-federation-assets` || Yes | At least one of Scalprum or MF must stay enabled. |
55+
56+
For exact strings, see [`src/commands/index.ts`](../../src/commands/index.ts).
57+
58+
## Source layout (reference)
59+
60+
```text
61+
src/commands/export-dynamic-plugin/
62+
command.ts # role dispatch + post-export steps
63+
backend.ts # backend export implementation
64+
frontend.ts # frontend export implementation
65+
common-utils.ts # customizeForDynamicUse, initializeYarnProject
66+
dev.ts # --dev symlink / install into dynamic plugins root
67+
backend-utils.ts
68+
types.ts
69+
```
70+
71+
## Related commands
72+
73+
- **`plugin package`** — builds a container image / registry workflow from exported plugins ([`src/commands/index.ts`](../../src/commands/index.ts)); not covered here.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Backend dynamic plugin export
2+
3+
Walkthrough of [`backend.ts`](../../src/commands/export-dynamic-plugin/backend.ts): **`backend-plugin`** and **`backend-plugin-module`** packages. The result is a self-contained tree under **`dist-dynamic/`** suitable for loading as a Node dynamic plugin.
4+
5+
[Back to index](./README.md) · Shared packaging: [shared-packaging.md](./shared-packaging.md)
6+
7+
## 1. Inputs and guards
8+
9+
- Parse the plugin **`package.json`**; reject if **`bundled: true`** (dynamic backend plugins must not be bundled in this sense).
10+
- Resolve CLI lists: **`--embed-package`**, **`--shared-package`**, **`--allow-native-package`**, **`--suppress-native-package`**, **`--ignore-version-check`**.
11+
- Load monorepo layout with **`@manypkg/get-packages`** from **`paths.targetDir`**.
12+
- **`searchEmbedded`** ([`backend.ts`](../../src/commands/export-dynamic-plugin/backend.ts) + [`backend-utils`](../../src/commands/export-dynamic-plugin/backend-utils.ts)) resolves which packages to embed from the dependency graph and optional **`--embed-package`** names; may auto-include related **`-common`** / **`-node`** packages depending on role.
13+
14+
## 2. Shared dependency rules
15+
16+
- **`sharedPackagesRules`**: by default, **`@backstage/*`** dependencies are treated as **shared** (moved to **`peerDependencies`** during [`customizeForDynamicUse`](./shared-packaging.md)).
17+
- **`--shared-package`** adds patterns (string or `/regex/`); entries prefixed with **`!`** go to **`exclude`** (do not force to peer).
18+
- **Embedded package names** are always excluded from the “move to peer” rule so they stay private/embedded as intended.
19+
20+
## 3. Prepare `dist-dynamic`
21+
22+
- Optional **`--clean`**: remove **`dist-dynamic`** entirely.
23+
- Recreate directory and write **`.gitignore`** (ignore all by default).
24+
- With **`--track-dynamic-manifest-and-lock-file`**, whitelist **`package.json`**, **`yarn.lock`**, **`.yarnrc.yml`** so they can be committed (productization).
25+
26+
## 4. Suppress native (`--suppress-native-package`)
27+
28+
For each suppressed name, materialize a stub under **`embedded/<name>/`** (minimal **`package.json`** + **`index.js`** that throws) so resolutions can point at a non-native placeholder.
29+
30+
## 5. Embedded packages loop
31+
32+
For each resolved embedded package:
33+
34+
- Optional **`yarn build`** in the embedded package dir when **`--build`** (default install path uses `opts.build`).
35+
- **`productionPack`** into **`dist-dynamic/embedded/<normalized-name>/`**, or **recursive copy** if already packed.
36+
- Remove **`node_modules`** under the embedded copy if present.
37+
- **`customizeForDynamicUse`** on the embedded **`package.json`** (private, version suffix `+embedded`, shared/peer rules, workspace resolution, Yarn v1 **`file:`** for embedded deps when applicable), including [lockfile-adjacent workspace **`resolutions`**](./shared-packaging.md#workspace-resolutions-inheritance) merged before backend-only **`additionalResolutions`**.
38+
- Collect **peer dependencies** from embedded packages for later hoisting onto the main package.
39+
40+
## 6. Main package
41+
42+
- Optional **`yarn build`** at **`paths.targetDir`** when **`--build`**.
43+
- **`productionPack`** with **`packageDir: ''`** so file resolution uses the **Backstage CLI target directory** (plugin root) as the pack root, output into **`dist-dynamic`**.
44+
- Remove nested **`dist-dynamic/dist-dynamic`** if **`files`** accidentally included it.
45+
- **`customizeForDynamicUse`** on the main **`package.json`** (same [workspace \*\*`resolutions` inheritance](./shared-packaging.md#workspace-resolutions-inheritance); embedded **`file:`** resolutions still win on conflicts):
46+
- Rename to **`<original-name>-dynamic`**
47+
- **`bundleDependencies: true`**
48+
- Clear **`scripts`**
49+
- **`resolutions`** / **`yarn`** resolutions wiring **`file:./embedded/...`** for embedded packages (and suppressed native stubs)
50+
- Hoist collected embedded **peer** requirements onto the main **`peerDependencies`** when non-empty
51+
52+
Details of manifest rewriting: [shared-packaging.md](./shared-packaging.md).
53+
54+
## 7. Yarn project metadata (`initializeYarnProject`)
55+
56+
See [shared-packaging.md](./shared-packaging.md). Summaries: copy **`yarn.lock`** if missing, **generate** a minimal Berry **`.yarnrc.yml`** in **`dist-dynamic`** (**`httpTimeout`** + **`nodeLinker: node-modules`**; see shared doc), set **`packageManager`**.
57+
58+
## 8. `yarn install`
59+
60+
- Skipped when **`--no-install`**; user is warned the lockfile may be stale until they install manually.
61+
- Otherwise run **`yarn install`** in **`dist-dynamic`** with Yarn **1.x** vs **Berry**-appropriate flags; log to a temp file on backend, then remove **`.yarn`** under **`dist-dynamic`** after success.
62+
63+
## 9. Post-install validation (install path only)
64+
65+
1. **Shared vs private**: scan **`yarn.lock`** so no **shared** package (per rules) appears as a private dependency; on failure, suggest **`--shared-package !...`** or **`--embed-package`** with a derived hint list.
66+
2. **Native modules**: **`gatherNativeModules`**; fail unless listed in **`--allow-native-package`**.
67+
3. **Entry points**: **`validatePluginEntryPoints`** ([`backend-utils.ts`](../../src/commands/export-dynamic-plugin/backend-utils.ts)) — ensures expected backend plugin entry surface.
68+
69+
On success, the yarn install log is removed (see **`logFile`** / **`fs.remove`** usage at the end of the install block in **`backend.ts`**).
70+
71+
## 10. Return value
72+
73+
Returns the absolute **`dist-dynamic`** path for **`command.ts`** to append config schema and dev steps.
74+
75+
[Back to index](./README.md)

0 commit comments

Comments
 (0)