diff --git a/.changeset/unlucky-pens-search.md b/.changeset/unlucky-pens-search.md new file mode 100644 index 0000000000..4786c14691 --- /dev/null +++ b/.changeset/unlucky-pens-search.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +When using node 20+ the `scaffolder-backend` will now throw an error at startup if the `--no-node-snapshot` option was +not provided to node. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd6e2edbc3..71d6b2e56a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -190,7 +190,7 @@ jobs: env: CI: true - NODE_OPTIONS: --max-old-space-size=4096 + NODE_OPTIONS: ${{ matrix.node-version == '20.x' && '--max-old-space-size=4096 --no-node-snapshot' || '--max-old-space-size=4096' }} INTEGRATION_TEST_GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }} INTEGRATION_TEST_GITLAB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITLAB_TOKEN }} INTEGRATION_TEST_BITBUCKET_TOKEN: ${{ secrets.INTEGRATION_TEST_BITBUCKET_TOKEN }} diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index 3f403b2c91..0fe258b740 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -36,7 +36,7 @@ jobs: env: CI: true - NODE_OPTIONS: --max-old-space-size=4096 + NODE_OPTIONS: ${{ matrix.node-version == '20.x' && '--max-old-space-size=8192 --no-node-snapshot' || '--max-old-space-size=8192' }} name: E2E Linux ${{ matrix.node-version }} steps: diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index d549ac2a40..395dbe0b7f 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -26,7 +26,7 @@ jobs: env: CI: true - NODE_OPTIONS: --max-old-space-size=8192 + NODE_OPTIONS: ${{ matrix.node-version == '20.x' && '--max-old-space-size=8192 --no-node-snapshot' || '--max-old-space-size=8192' }} name: E2E Windows ${{ matrix.node-version }} steps: diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index 3412fec2d3..9e271a9d79 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -107,3 +107,42 @@ export async function findTemplate(options: { return template as TemplateEntityV1beta3; } + +/** + * Checks if the '--no-node-snapshot' option is included in the NODE_OPTIONS environment variable + * or not included in the command line arguments. + * + * @remarks + * This function checks whether the '--no-node-snapshot' option is part of the NODE_OPTIONS environment + * variable or is missing from the command line arguments. If either condition is met, the function returns `true`. + * This check is especially important when using the "isolated-vm" package with Node.js version 20.x or later. + * + * According to the "isolated-vm" documentation on GitHub (https://github.com/laverdet/isolated-vm), + * if you are using a version of Node.js 20.x or later and you don't pass the '--no-node-snapshot' option, + * it can cause the process to crash. This function helps prevent such crashes by ensuring that the option + * is correctly provided. + * + * @returns {boolean} Returns `true` if the '--no-node-snapshot' option is included in the NODE_OPTIONS + * environment variable or not included in the command line arguments. Otherwise, it returns `false`. + */ +export function isNoNodeSnapshotOptionProvided(): boolean { + return ( + process.env.NODE_OPTIONS?.includes('--no-node-snapshot') || + process.argv.includes('--no-node-snapshot') + ); +} + +/** + * Gets the major version of the currently running Node.js process. + * + * @remarks + * This function extracts the major version from `process.versions.node` (a string representing the Node.js version), + * which includes the major, minor, and patch versions. It splits this string by the `.` character to get an array + * of these versions, and then parses the first element of this array (the major version) to a number. + * + * @returns {number} The major version of the currently running Node.js process. + */ +export function getMajorNodeVersion(): number { + const version = process.versions.node; + return parseInt(version.split('.')[0], 10); +} diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index c0322af283..dec3dcf69a 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -61,7 +61,13 @@ import { } from '../scaffolder'; import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; -import { findTemplate, getEntityBaseUrl, getWorkingDirectory } from './helpers'; +import { + findTemplate, + getEntityBaseUrl, + getMajorNodeVersion, + getWorkingDirectory, + isNoNodeSnapshotOptionProvided, +} from './helpers'; import { IdentityApi, IdentityApiGetIdentityRequest, @@ -260,6 +266,14 @@ export async function createRouter( const logger = parentLogger.child({ plugin: 'scaffolder' }); + const nodeVersion = getMajorNodeVersion(); + if (nodeVersion >= 20 && !isNoNodeSnapshotOptionProvided()) { + throw new Error( + 'When using node v20+ Scaffolder requires that node be started with the --no-node-snapshot option. Please restart ' + + 'Backstage providing the node --no-node-snapshot option.', + ); + } + const identity: IdentityApi = options.identity || buildDefaultIdentityClient(options); const workingDirectory = await getWorkingDirectory(config, logger);