Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions modules/common/src/main/ts/api/TestError.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import * as Reporter from './Reporter';

export interface JsError extends Error {
toString?: () => string;
}
Expand Down Expand Up @@ -38,7 +36,8 @@ export const pprintAssertionError = (message: string, expected: string, actual:
expected
};
e.toString = (): string => {
return Reporter.pprintAssertionError(e as PprintAssertionError);
// Break circular dependency by inlining basic formatting
return `${message}\nExpected: ${expected}\nActual: ${actual}`;
};
return e as PprintAssertionError;
};
Expand Down
2 changes: 1 addition & 1 deletion modules/server/src/main/ts/BedrockAuto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const go = (bedrockAutoSettings: BedrockAutoSettings): void => {
const username = settings.username ?? process.env.LT_USERNAME;
const accesskey = settings.accesskey ?? process.env.LT_ACCESS_KEY;

const routes = RunnerRoutes.generate('auto', settings.projectdir, settings.basedir, settings.config, settings.bundler, settings.testfiles, settings.chunk, settings.retries, settings.singleTimeout, settings.stopOnFailure, basePage, settings.coverage, settings.polyfills);
const routes = RunnerRoutes.generate('auto', settings.projectdir, settings.basedir, settings.config, settings.bundler, settings.testfiles, settings.chunk, settings.retries, settings.singleTimeout, settings.stopOnFailure, basePage, settings.coverage, settings.polyfills, settings.turbo);

const shutdownServices: ((immediate?: boolean) => Promise<void>)[] = [];
const shutdown = (services: ((immediate?: boolean) => Promise<void>)[]) => (immediate?: boolean) => Promise.allSettled(services.map((fn) => fn(immediate)));
Expand Down
38 changes: 29 additions & 9 deletions modules/server/src/main/ts/BedrockManual.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Attempt } from './bedrock/core/Attempt';
import * as Version from './bedrock/core/Version';
import * as RunnerRoutes from './bedrock/server/RunnerRoutes';
import * as Webpack from './bedrock/compiler/Webpack';
import * as BunDevServer from './bedrock/server/BunDevServer';
import { BedrockManualSettings } from './bedrock/core/Settings';
import { ExitCodes } from './bedrock/util/ExitCodes';
import * as SettingsResolver from './bedrock/core/SettingsResolver';
Expand All @@ -11,25 +11,45 @@ export const go = (bedrockManualSettings: BedrockManualSettings): void => {

const settings = SettingsResolver.resolveAndLog(bedrockManualSettings);
const basePage = 'src/resources/html/bedrock.html';
const routes = RunnerRoutes.generate('manual', settings.projectdir, settings.basedir, settings.config, settings.bundler, settings.testfiles, settings.chunk, 0, settings.singleTimeout, true, basePage, settings.coverage, settings.polyfills);
const routes = RunnerRoutes.generate('manual', settings.projectdir, settings.basedir, settings.config, settings.bundler, settings.testfiles, settings.chunk, 0, settings.singleTimeout, true, basePage, settings.coverage, settings.polyfills, settings.turbo);

routes.then(async (runner) => {
const serveSettings: Webpack.WebpackServeSettings = {
const serveSettings: BunDevServer.BunDevServerSettings = {
...settings,
// There is no driver for manual mode.
driver: Attempt.failed('There is no webdriver for manual mode'),
master: null, // there is no need for master,
master: null,
runner,
// sticky session is used by auto mode only
stickyFirstSession: false,
// reset mouse position will never work on manual
skipResetMousePosition: true
skipResetMousePosition: true,
enableHotReload: true,
watchFiles: settings.testfiles
};

try {
const service = await Webpack.devserver(serveSettings);
const service = await BunDevServer.startBunDevServer(serveSettings);
service.enableHud();
console.log('bedrock-manual ' + Version.get() + ' available at: http://localhost:' + service.port);

// Setup graceful shutdown handlers
const shutdown = async (signal: string) => {
console.log(`\n${signal} received, shutting down gracefully...`);
try {
await service.shutdown();
console.log('Server closed successfully');
process.exit(0);
} catch (error) {
console.error('Error during shutdown:', error);
process.exit(1);
}
};

process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGQUIT', () => shutdown('SIGQUIT'));

// Prevent the process from exiting
process.stdin.resume();

} catch (err) {
console.error(err);
process.exit(ExitCodes.failures.error);
Expand Down
14 changes: 11 additions & 3 deletions modules/server/src/main/ts/bedrock/cli/ClOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,9 @@ export const polyfills: ClOption = {
name: 'polyfills',
type: String,
multiple: true,
defaultValue: [ 'Symbol' ],
description: 'CoreJS polyfills to apply when running tests',
validate: Extraction.inSet([ 'ArrayBuffer', 'Map', 'Object', 'Promise', 'Set', 'Symbol', 'TypedArray', 'WeakMap', 'WeakSet' ]),
defaultValue: [ 'Symbol', 'tinymce' ],
description: 'CoreJS polyfills and globals to apply when running tests',
validate: Extraction.inSet([ 'ArrayBuffer', 'Map', 'Object', 'Promise', 'Set', 'Symbol', 'TypedArray', 'WeakMap', 'WeakSet', 'tinymce' ]),
uncommon: true
};

Expand Down Expand Up @@ -395,3 +395,11 @@ export const webdriverPort: ClOption = {
defaultValue: 4444,
uncommon: true
};

export const turbo: ClOption = {
name: 'turbo',
type: Boolean,
defaultValue: false,
description: 'Use Bun for ultra-fast TypeScript compilation and nx affected testing',
validate: Extraction.any
};
3 changes: 2 additions & 1 deletion modules/server/src/main/ts/bedrock/cli/Clis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ const commonOptions = (directories: Directories) => {
ClOptions.bucket,
ClOptions.buckets,
ClOptions.stopOnFailure,
ClOptions.verbose
ClOptions.verbose,
ClOptions.turbo
];
};

Expand Down
53 changes: 52 additions & 1 deletion modules/server/src/main/ts/bedrock/cli/Extraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,49 @@ import readdirSyncRec = require('recursive-readdir-sync');
import { Attempt } from '../core/Attempt';
import * as Qstring from '../util/Qstring';

/**
* Constants for file deduplication
*/
const SOURCE_DIR_INDICATOR = '/src/';
const COMPILED_DIR_INDICATOR = '/lib/';

/**
* Removes duplicate test files, preferring compiled JavaScript over TypeScript sources
*/
const deduplicateTestFiles = (files: string[]): string[] => {
// Create a map of base names to their file paths for quick lookups
const filesByBaseName = new Map<string, string[]>();

files.forEach(file => {
const baseName = file.split('/').pop()?.replace(/\.(ts|js)$/, '') || '';
if (!filesByBaseName.has(baseName)) {
filesByBaseName.set(baseName, []);
}
filesByBaseName.get(baseName)!.push(file);
});

const result: string[] = [];

filesByBaseName.forEach((filePaths) => {
if (filePaths.length === 1) {
// No duplicates, include the single file
result.push(filePaths[0]);
} else {
// Multiple files with same base name - prefer compiled JS over TS source
const compiledJs = filePaths.find(f => f.includes(COMPILED_DIR_INDICATOR) && f.endsWith('.js'));
if (compiledJs) {
result.push(compiledJs);
} else {
// No compiled version found, use the first TypeScript source
const tsSource = filePaths.find(f => f.includes(SOURCE_DIR_INDICATOR) && f.endsWith('.ts'));
result.push(tsSource || filePaths[0]);
}
}
});

return result;
};

export const file = (name: string, rawValue: string): Attempt<string[], string> => {
// Ignore any query strings when checking if a file exists
const parsed = Qstring.parse(rawValue);
Expand Down Expand Up @@ -71,13 +114,21 @@ export const files = (patterns: string[]) => {
const scanned = dirs.reduce((result, d) => result.concat(readdirSyncRec(d)), [] as string[]);

const filtered = scanned.filter((f) => {
// Skip source map files
if (f.endsWith('.js.map')) {
return false;
}

const matches = patterns.filter((p) => {
return f.indexOf(p) > -1;
});

return matches.length > 0 && fs.lstatSync(f).isFile();
});
return Attempt.passed(filtered);

// Remove duplicates: prefer compiled JavaScript files over TypeScript source files for performance
const deduped = deduplicateTestFiles(filtered);
return Attempt.passed(deduped);
} catch (err) {
return Attempt.failed([
`Error scanning [${value}] for files matching pattern: [${patterns.join(', ')}]`
Expand Down
Loading