cli-node, cli: move yarnPlugin and SuccessCache to cli-node

Move `getHasYarnPlugin` and `SuccessCache` from `@backstage/cli` internal
modules to `@backstage/cli-node` as public exports, making them available
for reuse by other CLI tooling.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-02-25 11:52:36 +01:00
parent dd6408d682
commit 3c811bf8a9
15 changed files with 77 additions and 8 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Migrated `getHasYarnPlugin` and `SuccessCache` to use the implementations from `@backstage/cli-node`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli-node': patch
---
Added `getHasYarnPlugin` and `SuccessCache` exports, moved from `@backstage/cli`.
+1
View File
@@ -39,6 +39,7 @@
"@yarnpkg/parsers": "^3.0.0",
"fs-extra": "^11.2.0",
"semver": "^7.5.3",
"yaml": "^2.0.0",
"zod": "^3.25.76"
},
"devDependencies": {
+13
View File
@@ -93,6 +93,9 @@ export type ConcurrentTasksOptions<TItem> = {
worker: (item: TItem) => Promise<void>;
};
// @public
export function getHasYarnPlugin(): Promise<boolean>;
// @public
export class GitUtils {
static listChangedFiles(ref: string): Promise<string[]>;
@@ -221,6 +224,16 @@ export function runWorkerQueueThreads<TItem, TResult, TContext>(
results: TResult[];
}>;
// @public
export class SuccessCache {
constructor(name: string, basePath?: string);
// (undocumented)
read(): Promise<Set<string>>;
static trimPaths(input: string): string;
// (undocumented)
write(newEntries: Iterable<string>): Promise<void>;
}
// @public
export type WorkerQueueThreadsOptions<TItem, TResult, TContext> = {
items: Iterable<TItem>;
@@ -22,6 +22,12 @@ const DEFAULT_CACHE_BASE_PATH = 'node_modules/.cache/backstage-cli';
const CACHE_MAX_AGE_MS = 7 * 24 * 3600_000;
/**
* A file-system-based cache that tracks successful operations by storing
* timestamped marker files.
*
* @public
*/
export class SuccessCache {
readonly #path: string;
@@ -89,7 +95,6 @@ export class SuccessCache {
const empty = Buffer.alloc(0);
for (const key of newEntries) {
// Remove any existing items with the key we're about to add
const trimmedItems = existingItems.filter(item =>
item.endsWith(`_${key}`),
);
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { SuccessCache } from './SuccessCache';
+3 -1
View File
@@ -20,7 +20,9 @@
* @packageDocumentation
*/
export * from './cache';
export * from './concurrency';
export * from './git';
export * from './monorepo';
export * from './concurrency';
export * from './roles';
export * from './yarn';
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { getHasYarnPlugin } from './yarnPlugin';
@@ -33,12 +33,12 @@ const yarnRcSchema = z.object({
* Detects whether the Backstage Yarn plugin is installed in the target repository.
*
* @returns Promise<boolean> - true if the plugin is installed, false otherwise
* @public
*/
export async function getHasYarnPlugin(): Promise<boolean> {
const yarnRcPath = targetPaths.resolveRoot('.yarnrc.yml');
const yarnRcContent = await fs.readFile(yarnRcPath, 'utf-8').catch(e => {
if (e.code === 'ENOENT') {
// gracefully continue in case the file doesn't exist
return '';
}
throw e;
@@ -28,7 +28,7 @@ import {
import { targetPaths } from '@backstage/cli-common';
import { createScriptOptionsParser } from '../../lib/optionsParser';
import { SuccessCache } from '../../../../lib/cache/SuccessCache';
import { SuccessCache } from '@backstage/cli-node';
function depCount(pkg: BackstagePackageJson) {
const deps = pkg.dependencies ? Object.keys(pkg.dependencies).length : 0;
@@ -30,8 +30,11 @@ import { OptionValues } from 'commander';
import { isError, NotFoundError } from '@backstage/errors';
import { resolve as resolvePath } from 'node:path';
import { getHasYarnPlugin } from '../../../../lib/yarnPlugin';
import { Lockfile, runConcurrentTasks } from '@backstage/cli-node';
import {
getHasYarnPlugin,
Lockfile,
runConcurrentTasks,
} from '@backstage/cli-node';
import {
fetchPackageInfo,
mapDependencies,
@@ -28,7 +28,7 @@ import { Lockfile } from '@backstage/cli-node';
import { targetPaths } from '@backstage/cli-common';
import { createPackageVersionProvider } from '../../../../lib/version';
import { getHasYarnPlugin } from '../../../../lib/yarnPlugin';
import { getHasYarnPlugin } from '@backstage/cli-node';
const builtInHelpers = {
camelCase,
@@ -31,7 +31,7 @@ import {
findOwnPaths,
isChildPath,
} from '@backstage/cli-common';
import { SuccessCache } from '../../../../lib/cache/SuccessCache';
import { SuccessCache } from '@backstage/cli-node';
type JestProject = {
displayName: string;
+1
View File
@@ -3277,6 +3277,7 @@ __metadata:
"@yarnpkg/parsers": "npm:^3.0.0"
fs-extra: "npm:^11.2.0"
semver: "npm:^7.5.3"
yaml: "npm:^2.0.0"
zod: "npm:^3.25.76"
languageName: unknown
linkType: soft