diff --git a/.changeset/tough-lies-float.md b/.changeset/tough-lies-float.md
new file mode 100644
index 0000000000..2ad3b8623f
--- /dev/null
+++ b/.changeset/tough-lies-float.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+'@backstage/plugin-scaffolder-node': patch
+---
+
+Deprecated `executeShellCommand`, `RunCommandOptions`, and `fetchContents` from `@backstage/plugin-scaffolder-backend`, since they are useful for Scaffolder modules (who should not be importing from the plugin package itself). You should now import these from `@backstage/plugin-scaffolder-backend-node` instead. `RunCommandOptions` was renamed in the Node package as `ExecuteShellCommandOptions`, for consistency.
diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json
index b8ce9ea8d9..676eddfb12 100644
--- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json
+++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json
@@ -34,7 +34,6 @@
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/integration": "workspace:^",
- "@backstage/plugin-scaffolder-backend": "workspace:^",
"@backstage/plugin-scaffolder-node": "workspace:^",
"@backstage/types": "workspace:^",
"fs-extra": "10.1.0",
diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts
index 428f392555..71a3af11ae 100644
--- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts
+++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts
@@ -13,11 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import { Config } from '@backstage/config';
import { UrlReader } from '@backstage/backend-common';
import { ScmIntegrations } from '@backstage/integration';
-import { fetchContents } from '@backstage/plugin-scaffolder-backend';
-import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
+import {
+ createTemplateAction,
+ fetchContents,
+} from '@backstage/plugin-scaffolder-node';
import { InputError, ConflictError } from '@backstage/errors';
import { NodeHtmlMarkdown } from 'node-html-markdown';
import fs from 'fs-extra';
diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json
index f95771822b..446d79e06c 100644
--- a/plugins/scaffolder-backend-module-cookiecutter/package.json
+++ b/plugins/scaffolder-backend-module-cookiecutter/package.json
@@ -33,7 +33,6 @@
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/integration": "workspace:^",
- "@backstage/plugin-scaffolder-backend": "workspace:^",
"@backstage/plugin-scaffolder-node": "workspace:^",
"@backstage/types": "workspace:^",
"command-exists": "^1.2.9",
diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts
index b63a044a20..8b084e6377 100644
--- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts
+++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts
@@ -27,10 +27,10 @@ import fs from 'fs-extra';
import path, { resolve as resolvePath } from 'path';
import { Writable } from 'stream';
import {
+ createTemplateAction,
fetchContents,
executeShellCommand,
-} from '@backstage/plugin-scaffolder-backend';
-import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
+} from '@backstage/plugin-scaffolder-node';
export class CookiecutterRunner {
private readonly containerRunner?: ContainerRunner;
diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json
index 9bc7bbf06f..3697529c31 100644
--- a/plugins/scaffolder-backend-module-rails/package.json
+++ b/plugins/scaffolder-backend-module-rails/package.json
@@ -33,7 +33,6 @@
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/integration": "workspace:^",
- "@backstage/plugin-scaffolder-backend": "workspace:^",
"@backstage/plugin-scaffolder-node": "workspace:^",
"@backstage/types": "workspace:^",
"command-exists": "^1.2.9",
diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts
index 2a8a2554a2..2b34f978e3 100644
--- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts
+++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts
@@ -19,8 +19,10 @@ import { JsonObject } from '@backstage/types';
import { InputError } from '@backstage/errors';
import { ScmIntegrations } from '@backstage/integration';
import fs from 'fs-extra';
-import { fetchContents } from '@backstage/plugin-scaffolder-backend';
-import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
+import {
+ createTemplateAction,
+ fetchContents,
+} from '@backstage/plugin-scaffolder-node';
import { resolve as resolvePath } from 'path';
import { RailsNewRunner } from './railsNewRunner';
diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts
index 0ed02e293c..7e78c25d62 100644
--- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts
+++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts
@@ -17,7 +17,7 @@
import { ContainerRunner } from '@backstage/backend-common';
import fs from 'fs-extra';
import path from 'path';
-import { executeShellCommand } from '@backstage/plugin-scaffolder-backend';
+import { executeShellCommand } from '@backstage/plugin-scaffolder-node';
import commandExists from 'command-exists';
import {
railsArgumentResolver,
diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md
index 1957c3b3d2..4fe961056f 100644
--- a/plugins/scaffolder-backend/api-report.md
+++ b/plugins/scaffolder-backend/api-report.md
@@ -3,8 +3,6 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
-///
-
import { ActionContext as ActionContext_2 } from '@backstage/plugin-scaffolder-node';
import { CatalogApi } from '@backstage/catalog-client';
import { CatalogProcessor } from '@backstage/plugin-catalog-node';
@@ -13,7 +11,10 @@ import { Config } from '@backstage/config';
import { createPullRequest } from 'octokit-plugin-create-pull-request';
import { Duration } from 'luxon';
import { Entity } from '@backstage/catalog-model';
+import { executeShellCommand as executeShellCommand_2 } from '@backstage/plugin-scaffolder-node';
+import { ExecuteShellCommandOptions } from '@backstage/plugin-scaffolder-node';
import express from 'express';
+import { fetchContents as fetchContents_2 } from '@backstage/plugin-scaffolder-node';
import { GithubCredentialsProvider } from '@backstage/integration';
import { HumanDuration } from '@backstage/types';
import { IdentityApi } from '@backstage/plugin-auth-node';
@@ -34,7 +35,6 @@ import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-
import { Schema } from 'jsonschema';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { ScmIntegrations } from '@backstage/integration';
-import { SpawnOptionsWithoutStdio } from 'child_process';
import { TaskSecrets as TaskSecrets_2 } from '@backstage/plugin-scaffolder-node';
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common';
@@ -43,7 +43,6 @@ import { TemplateActionOptions } from '@backstage/plugin-scaffolder-node';
import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common';
import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common';
import { UrlReader } from '@backstage/backend-common';
-import { Writable } from 'stream';
import { ZodType } from 'zod';
import { ZodTypeDef } from 'zod';
@@ -816,17 +815,11 @@ export type DatabaseTaskStoreOptions = {
database: PluginDatabaseManager | Knex;
};
-// @public
-export const executeShellCommand: (options: RunCommandOptions) => Promise;
+// @public @deprecated
+export const executeShellCommand: typeof executeShellCommand_2;
-// @public
-export function fetchContents(options: {
- reader: UrlReader;
- integrations: ScmIntegrations;
- baseUrl?: string;
- fetchUrl?: string;
- outputPath: string;
-}): Promise;
+// @public @deprecated
+export const fetchContents: typeof fetchContents_2;
// @public (undocumented)
export type OctokitWithPullRequestPluginClient = Octokit & {
@@ -876,13 +869,8 @@ export interface RouterOptions {
taskWorkers?: number;
}
-// @public (undocumented)
-export type RunCommandOptions = {
- command: string;
- args: string[];
- options?: SpawnOptionsWithoutStdio;
- logStream?: Writable;
-};
+// @public @deprecated
+export type RunCommandOptions = ExecuteShellCommandOptions;
// @public (undocumented)
export class ScaffolderEntitiesProcessor implements CatalogProcessor {
diff --git a/plugins/scaffolder-backend/src/deprecated.ts b/plugins/scaffolder-backend/src/deprecated.ts
index f8358b9071..d1a0f9ad5b 100644
--- a/plugins/scaffolder-backend/src/deprecated.ts
+++ b/plugins/scaffolder-backend/src/deprecated.ts
@@ -19,6 +19,9 @@ import {
createTemplateAction as createTemplateActionNode,
TaskSecrets as TaskSecretsNode,
TemplateAction as TemplateActionNode,
+ executeShellCommand as executeShellCommandNode,
+ ExecuteShellCommandOptions as ExecuteShellCommandOptionsNode,
+ fetchContents as fetchContentsNode,
} from '@backstage/plugin-scaffolder-node';
import { JsonObject } from '@backstage/types';
@@ -47,3 +50,28 @@ export type TaskSecrets = TaskSecretsNode;
*/
export type TemplateAction =
TemplateActionNode;
+
+/**
+ * Options for {@link executeShellCommand}.
+ *
+ * @public
+ * @deprecated Use `ExecuteShellCommandOptions` from `@backstage/plugin-scaffolder-node` instead
+ */
+export type RunCommandOptions = ExecuteShellCommandOptionsNode;
+
+/**
+ * Run a command in a sub-process, normally a shell command.
+ *
+ * @public
+ * @deprecated Use `executeShellCommand` from `@backstage/plugin-scaffolder-node` instead
+ */
+export const executeShellCommand = executeShellCommandNode;
+
+/**
+ * A helper function that reads the contents of a directory from the given URL.
+ * Can be used in your own actions, and also used behind fetch:template and fetch:plain
+ *
+ * @public
+ * @deprecated Use `fetchContents` from `@backstage/plugin-scaffolder-node` instead
+ */
+export const fetchContents = fetchContentsNode;
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/index.ts
index 14a40f53c3..67f1bc360f 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/index.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/index.ts
@@ -17,4 +17,3 @@
export { createFetchPlainAction } from './plain';
export { createFetchPlainFileAction } from './plainFile';
export { createFetchTemplateAction } from './template';
-export { fetchContents } from './helpers';
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts
index ed37a39a98..3a67489eca 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts
@@ -21,9 +21,9 @@ import { resolve as resolvePath } from 'path';
import { getVoidLogger, UrlReader } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
+import { fetchContents } from '@backstage/plugin-scaffolder-node';
import { createFetchPlainAction } from './plain';
import { PassThrough } from 'stream';
-import { fetchContents } from './helpers';
describe('fetch:plain', () => {
const integrations = ScmIntegrations.fromConfig(
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts
index c10f696bb9..e102c5bb74 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts
@@ -16,8 +16,10 @@
import { UrlReader, resolveSafeChildPath } from '@backstage/backend-common';
import { ScmIntegrations } from '@backstage/integration';
-import { fetchContents } from './helpers';
-import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
+import {
+ createTemplateAction,
+ fetchContents,
+} from '@backstage/plugin-scaffolder-node';
/**
* Downloads content and places it in the workspace, or optionally
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts
index 5d9e99fc7d..e6ed858b09 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts
@@ -21,9 +21,9 @@ import { resolve as resolvePath } from 'path';
import { getVoidLogger, UrlReader } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
+import { fetchFile } from '@backstage/plugin-scaffolder-node';
import { createFetchPlainFileAction } from './plainFile';
import { PassThrough } from 'stream';
-import { fetchFile } from './helpers';
describe('fetch:plain:file', () => {
const integrations = ScmIntegrations.fromConfig(
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts
index 7b33ad93b4..96abe773fd 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts
@@ -16,8 +16,10 @@
import { UrlReader, resolveSafeChildPath } from '@backstage/backend-common';
import { ScmIntegrations } from '@backstage/integration';
-import { fetchFile } from './helpers';
-import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
+import {
+ createTemplateAction,
+ fetchFile,
+} from '@backstage/plugin-scaffolder-node';
/**
* Downloads content and places it in the workspace, or optionally
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts
index 15548ffea7..aee1fdcb32 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts
@@ -25,9 +25,9 @@ import {
} from '@backstage/backend-common';
import { ScmIntegrations } from '@backstage/integration';
import { PassThrough } from 'stream';
-import { fetchContents } from './helpers';
import { createFetchTemplateAction } from './template';
import {
+ fetchContents,
ActionContext,
TemplateAction,
} from '@backstage/plugin-scaffolder-node';
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts
index be7b47521c..7f03cb2514 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts
@@ -18,8 +18,10 @@ import { extname } from 'path';
import { resolveSafeChildPath, UrlReader } from '@backstage/backend-common';
import { InputError } from '@backstage/errors';
import { ScmIntegrations } from '@backstage/integration';
-import { fetchContents } from './helpers';
-import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
+import {
+ createTemplateAction,
+ fetchContents,
+} from '@backstage/plugin-scaffolder-node';
import globby from 'globby';
import fs from 'fs-extra';
import { isBinaryFile } from 'isbinaryfile';
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts
index f51ab47f16..5ac92128ca 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts
@@ -17,61 +17,9 @@
import { Git } from '@backstage/backend-common';
import { Config } from '@backstage/config';
import { assertError } from '@backstage/errors';
-import { spawn, SpawnOptionsWithoutStdio } from 'child_process';
import { Octokit } from 'octokit';
-import { PassThrough, Writable } from 'stream';
import { Logger } from 'winston';
-/** @public */
-export type RunCommandOptions = {
- /** command to run */
- command: string;
- /** arguments to pass the command */
- args: string[];
- /** options to pass to spawn */
- options?: SpawnOptionsWithoutStdio;
- /** stream to capture stdout and stderr output */
- logStream?: Writable;
-};
-
-/**
- * Run a command in a sub-process, normally a shell command.
- *
- * @public
- */
-export const executeShellCommand = async (options: RunCommandOptions) => {
- const {
- command,
- args,
- options: spawnOptions,
- logStream = new PassThrough(),
- } = options;
- await new Promise((resolve, reject) => {
- const process = spawn(command, args, spawnOptions);
-
- process.stdout.on('data', stream => {
- logStream.write(stream);
- });
-
- process.stderr.on('data', stream => {
- logStream.write(stream);
- });
-
- process.on('error', error => {
- return reject(error);
- });
-
- process.on('close', code => {
- if (code !== 0) {
- return reject(
- new Error(`Command ${command} failed, exit code: ${code}`),
- );
- }
- return resolve();
- });
- });
-};
-
export async function initRepoAndPush({
dir,
remoteUrl,
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts
index 7846ff96fc..7ab98482ff 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts
@@ -21,6 +21,3 @@ export * from './fetch';
export * from './filesystem';
export * from './publish';
export * from './github';
-
-export { executeShellCommand } from './helpers';
-export type { RunCommandOptions } from './helpers';
diff --git a/plugins/scaffolder-node/api-report.md b/plugins/scaffolder-node/api-report.md
index 46aba960d8..8f3a7af3e6 100644
--- a/plugins/scaffolder-node/api-report.md
+++ b/plugins/scaffolder-node/api-report.md
@@ -9,7 +9,10 @@ import { ExtensionPoint } from '@backstage/backend-plugin-api';
import { JsonObject } from '@backstage/types';
import { Logger } from 'winston';
import { Schema } from 'jsonschema';
+import { ScmIntegrations } from '@backstage/integration';
+import { SpawnOptionsWithoutStdio } from 'child_process';
import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
+import { UrlReader } from '@backstage/backend-common';
import { UserEntity } from '@backstage/catalog-model';
import { Writable } from 'stream';
import { z } from 'zod';
@@ -67,6 +70,37 @@ export const createTemplateAction: <
>,
) => TemplateAction;
+// @public
+export function executeShellCommand(
+ options: ExecuteShellCommandOptions,
+): Promise;
+
+// @public
+export type ExecuteShellCommandOptions = {
+ command: string;
+ args: string[];
+ options?: SpawnOptionsWithoutStdio;
+ logStream?: Writable;
+};
+
+// @public
+export function fetchContents(options: {
+ reader: UrlReader;
+ integrations: ScmIntegrations;
+ baseUrl?: string;
+ fetchUrl?: string;
+ outputPath: string;
+}): Promise;
+
+// @public
+export function fetchFile(options: {
+ reader: UrlReader;
+ integrations: ScmIntegrations;
+ baseUrl?: string;
+ fetchUrl?: string;
+ outputPath: string;
+}): Promise;
+
// @alpha
export interface ScaffolderActionsExtensionPoint {
// (undocumented)
diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json
index 59e80f1c1c..053e9a1dec 100644
--- a/plugins/scaffolder-node/package.json
+++ b/plugins/scaffolder-node/package.json
@@ -30,17 +30,22 @@
"postpack": "backstage-cli package postpack"
},
"dependencies": {
+ "@backstage/backend-common": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/catalog-model": "workspace:^",
+ "@backstage/errors": "workspace:^",
+ "@backstage/integration": "workspace:^",
"@backstage/plugin-scaffolder-common": "workspace:^",
"@backstage/types": "workspace:^",
+ "fs-extra": "10.1.0",
"jsonschema": "^1.2.6",
"winston": "^3.2.1",
"zod": "^3.21.4",
"zod-to-json-schema": "^3.20.4"
},
"devDependencies": {
- "@backstage/cli": "workspace:^"
+ "@backstage/cli": "workspace:^",
+ "@backstage/config": "workspace:^"
},
"files": [
"alpha",
diff --git a/plugins/scaffolder-node/src/actions/executeShellCommand.ts b/plugins/scaffolder-node/src/actions/executeShellCommand.ts
new file mode 100644
index 0000000000..ce54989589
--- /dev/null
+++ b/plugins/scaffolder-node/src/actions/executeShellCommand.ts
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021 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.
+ */
+
+import { spawn, SpawnOptionsWithoutStdio } from 'child_process';
+import { PassThrough, Writable } from 'stream';
+
+/**
+ * Options for {@link executeShellCommand}.
+ *
+ * @public
+ */
+export type ExecuteShellCommandOptions = {
+ /** command to run */
+ command: string;
+ /** arguments to pass the command */
+ args: string[];
+ /** options to pass to spawn */
+ options?: SpawnOptionsWithoutStdio;
+ /** stream to capture stdout and stderr output */
+ logStream?: Writable;
+};
+
+/**
+ * Run a command in a sub-process, normally a shell command.
+ *
+ * @public
+ */
+export async function executeShellCommand(
+ options: ExecuteShellCommandOptions,
+): Promise {
+ const {
+ command,
+ args,
+ options: spawnOptions,
+ logStream = new PassThrough(),
+ } = options;
+
+ await new Promise((resolve, reject) => {
+ const process = spawn(command, args, spawnOptions);
+
+ process.stdout.on('data', stream => {
+ logStream.write(stream);
+ });
+
+ process.stderr.on('data', stream => {
+ logStream.write(stream);
+ });
+
+ process.on('error', error => {
+ return reject(error);
+ });
+
+ process.on('close', code => {
+ if (code !== 0) {
+ return reject(
+ new Error(`Command ${command} failed, exit code: ${code}`),
+ );
+ }
+ return resolve();
+ });
+ });
+}
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts b/plugins/scaffolder-node/src/actions/fetch.test.ts
similarity index 98%
rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts
rename to plugins/scaffolder-node/src/actions/fetch.test.ts
index 3c4a677d4d..55b949fca7 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts
+++ b/plugins/scaffolder-node/src/actions/fetch.test.ts
@@ -21,10 +21,10 @@ import { resolve as resolvePath } from 'path';
import { UrlReader } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
-import { fetchContents, fetchFile } from './helpers';
+import { fetchContents, fetchFile } from './fetch';
import os from 'os';
-describe('fetchContent helper', () => {
+describe('fetchContents helper', () => {
beforeEach(() => {
jest.clearAllMocks();
});
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts b/plugins/scaffolder-node/src/actions/fetch.ts
similarity index 99%
rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts
rename to plugins/scaffolder-node/src/actions/fetch.ts
index 301c9991c4..66bbc0a842 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts
+++ b/plugins/scaffolder-node/src/actions/fetch.ts
@@ -54,6 +54,8 @@ export async function fetchContents(options: {
/**
* A helper function that reads the content of a single file from the given URL.
* Can be used in your own actions, and also used behind `fetch:plain:file`
+ *
+ * @public
*/
export async function fetchFile(options: {
reader: UrlReader;
diff --git a/plugins/scaffolder-node/src/actions/index.ts b/plugins/scaffolder-node/src/actions/index.ts
index e4af098356..751173384b 100644
--- a/plugins/scaffolder-node/src/actions/index.ts
+++ b/plugins/scaffolder-node/src/actions/index.ts
@@ -18,4 +18,9 @@ export {
createTemplateAction,
type TemplateActionOptions,
} from './createTemplateAction';
+export {
+ executeShellCommand,
+ type ExecuteShellCommandOptions,
+} from './executeShellCommand';
+export { fetchContents, fetchFile } from './fetch';
export { type ActionContext, type TemplateAction } from './types';
diff --git a/yarn.lock b/yarn.lock
index 5d4ddd09d3..45ca2930fe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8173,7 +8173,6 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/integration": "workspace:^"
- "@backstage/plugin-scaffolder-backend": "workspace:^"
"@backstage/plugin-scaffolder-node": "workspace:^"
"@backstage/types": "workspace:^"
fs-extra: 10.1.0
@@ -8195,7 +8194,6 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/integration": "workspace:^"
- "@backstage/plugin-scaffolder-backend": "workspace:^"
"@backstage/plugin-scaffolder-node": "workspace:^"
"@backstage/types": "workspace:^"
"@types/command-exists": ^1.2.0
@@ -8235,7 +8233,6 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/integration": "workspace:^"
- "@backstage/plugin-scaffolder-backend": "workspace:^"
"@backstage/plugin-scaffolder-node": "workspace:^"
"@backstage/types": "workspace:^"
"@types/command-exists": ^1.2.0
@@ -8364,11 +8361,16 @@ __metadata:
version: 0.0.0-use.local
resolution: "@backstage/plugin-scaffolder-node@workspace:plugins/scaffolder-node"
dependencies:
+ "@backstage/backend-common": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/catalog-model": "workspace:^"
"@backstage/cli": "workspace:^"
+ "@backstage/config": "workspace:^"
+ "@backstage/errors": "workspace:^"
+ "@backstage/integration": "workspace:^"
"@backstage/plugin-scaffolder-common": "workspace:^"
"@backstage/types": "workspace:^"
+ fs-extra: 10.1.0
jsonschema: ^1.2.6
winston: ^3.2.1
zod: ^3.21.4