feat: provide --no-node-snapshot by default

Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com>
This commit is contained in:
aramissennyeydd
2025-12-08 17:01:39 -05:00
parent cecaa124bb
commit ed2ca0efc7
3 changed files with 205 additions and 0 deletions
@@ -0,0 +1,189 @@
/*
* 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.
*/
import { runBackend } from './runBackend';
// Mock external dependencies
jest.mock('chokidar', () => ({
watch: jest.fn(() => ({
on: jest.fn().mockReturnThis(),
add: jest.fn(),
})),
}));
jest.mock('cross-spawn', () =>
jest.fn(() => ({
on: jest.fn().mockReturnThis(),
once: jest.fn().mockReturnThis(),
kill: jest.fn(),
killed: false,
exitCode: null,
pid: 12345,
})),
);
jest.mock('../ipc', () => ({
IpcServer: jest.fn().mockImplementation(() => ({
addChild: jest.fn(),
})),
ServerDataStore: {
bind: jest.fn(),
},
}));
jest.mock('ctrlc-windows', () => ({
ctrlc: jest.fn(),
}));
describe('runBackend', () => {
let originalEnv: NodeJS.ProcessEnv;
let originalPlatform: string;
beforeEach(() => {
// Save original environment
originalEnv = { ...process.env };
originalPlatform = process.platform;
// Clear environment variables that we're testing
delete process.env.NODE_ENV;
delete process.env.NODE_OPTIONS;
// Mock process.stdin.on to prevent actual stdin reading
jest.spyOn(process.stdin, 'on').mockReturnValue(process.stdin);
// Mock process.once to prevent actual signal handling
jest.spyOn(process, 'once').mockReturnValue(process);
});
afterEach(() => {
// Restore original environment
process.env = originalEnv;
Object.defineProperty(process, 'platform', {
value: originalPlatform,
});
jest.clearAllMocks();
});
describe('NODE_OPTIONS environment variable', () => {
it('should add --no-node-snapshot when NODE_OPTIONS is not set', async () => {
delete process.env.NODE_OPTIONS;
runBackend({
entry: 'src/index',
});
expect(process.env.NODE_OPTIONS).toBe('--no-node-snapshot');
});
it('should append --no-node-snapshot when NODE_OPTIONS exists without it', async () => {
process.env.NODE_OPTIONS = '--max-old-space-size=4096';
runBackend({
entry: 'src/index',
});
expect(process.env.NODE_OPTIONS).toBe(
'--max-old-space-size=4096 --no-node-snapshot',
);
});
it('should not add --no-node-snapshot when --node-snapshot already exists', async () => {
process.env.NODE_OPTIONS = '--node-snapshot --max-old-space-size=4096';
runBackend({
entry: 'src/index',
});
expect(process.env.NODE_OPTIONS).toBe(
'--node-snapshot --max-old-space-size=4096',
);
});
it('should not add --no-node-snapshot when --node-snapshot exists in the middle of NODE_OPTIONS', async () => {
process.env.NODE_OPTIONS =
'--max-old-space-size=4096 --node-snapshot --inspect';
runBackend({
entry: 'src/index',
});
expect(process.env.NODE_OPTIONS).toBe(
'--max-old-space-size=4096 --node-snapshot --inspect',
);
});
it('should handle NODE_OPTIONS with trailing spaces', async () => {
process.env.NODE_OPTIONS = '--max-old-space-size=4096 ';
runBackend({
entry: 'src/index',
});
expect(process.env.NODE_OPTIONS).toBe(
'--max-old-space-size=4096 --no-node-snapshot',
);
});
});
describe('NODE_ENV environment variable', () => {
it('should set NODE_ENV to development when not set', async () => {
delete process.env.NODE_ENV;
runBackend({
entry: 'src/index',
});
expect(process.env.NODE_ENV).toBe('development');
});
it('should not override existing NODE_ENV', async () => {
process.env.NODE_ENV = 'production';
runBackend({
entry: 'src/index',
});
expect(process.env.NODE_ENV).toBe('production');
});
});
describe('combined environment setup', () => {
it('should set both NODE_ENV and NODE_OPTIONS when neither is set', async () => {
delete process.env.NODE_ENV;
delete process.env.NODE_OPTIONS;
runBackend({
entry: 'src/index',
});
expect(process.env.NODE_ENV).toBe('development');
expect(process.env.NODE_OPTIONS).toBe('--no-node-snapshot');
});
it('should handle both environment variables independently', async () => {
process.env.NODE_ENV = 'test';
process.env.NODE_OPTIONS = '--inspect';
runBackend({
entry: 'src/index',
});
expect(process.env.NODE_ENV).toBe('test');
expect(process.env.NODE_OPTIONS).toBe('--inspect --no-node-snapshot');
});
});
});
@@ -52,6 +52,14 @@ export async function runBackend(options: RunBackendOptions) {
envEnv.NODE_ENV = 'development';
}
// Unless the user explicitly toggles node-snapshot, default to provide --no-node-snapshot to reduce number of steps to run scaffolder
// on Node LTS.
if (!envEnv.NODE_OPTIONS?.includes('--node-snapshot')) {
envEnv.NODE_OPTIONS =
(envEnv.NODE_OPTIONS ? envEnv.NODE_OPTIONS + ' ' : '') +
'--no-node-snapshot';
}
// Set up the parent IPC server and bind the available services
const server = new IpcServer();
ServerDataStore.bind(server);
@@ -78,6 +78,14 @@ export default async (_opts: OptionValues, cmd: Command) => {
process.env.TZ = 'UTC';
}
// Unless the user explicitly toggles node-snapshot, default to provide --no-node-snapshot to reduce number of steps to run scaffolder
// on Node LTS.
if (!process.env.NODE_OPTIONS?.includes('--node-snapshot')) {
process.env.NODE_OPTIONS = `${
process.env.NODE_OPTIONS ? `${process.env.NODE_OPTIONS} ` : ''
}--no-node-snapshot`;
}
// This ensures that the process doesn't exit too early before stdout is flushed
if (args.includes('--help')) {
(process.stdout as any)._handle.setBlocking(true);