Skip to content
Merged
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
14 changes: 13 additions & 1 deletion src/cli/domain/ocConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ export interface OpenComponentsConfig {
registries?: string[];
/** Development-specific configuration settings */
development?: {
/** Additional Express routes to mount on the registry application */
routes?: Array<{
route: string;
method: string;
handler: string;
}>;
/** JavaScript code to be included in the preview HTML's <head> section.
* Can be either a filepath to a JS script or inline JavaScript code.
*/
Expand Down Expand Up @@ -45,6 +51,11 @@ type ParsedConfig = {
sourcePath?: string;
registries: string[];
development: {
routes?: Array<{
route: string;
method: string;
handler: string;
}>;
preload?: string;
plugins: {
dynamic?: Record<string, string>;
Expand Down Expand Up @@ -91,7 +102,8 @@ function parseConfig(config: OpenComponentsConfig): ParsedConfig {
development: {
preload: config.development?.preload,
plugins,
fallback: config.development?.fallback
fallback: config.development?.fallback,
routes: config.development?.routes
}
};

Expand Down
47 changes: 27 additions & 20 deletions src/cli/facade/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,26 +190,33 @@ const dev = ({ local, logger }: { logger: Logger; local: Local }) =>
liveReload = { refresher, port: otherPort };
}

const registry = oc.Registry({
baseUrl,
prefix: opts.prefix || '',
dependencies: dependencies.modules,
compileClient: true,
discovery: true,
env: { name: 'local' },
fallbackRegistryUrl,
fallbackClient,
hotReloading,
liveReloadPort: liveReload.port,
local: true,
postRequestPayloadSize,
components: opts.components,
path: path.resolve(componentsDir),
port,
templates: dependencies.templates,
verbosity: 1,
preload: localConfig.development?.preload
});
let registry: ReturnType<typeof oc.Registry>;
try {
registry = oc.Registry({
baseUrl,
prefix: opts.prefix || '',
dependencies: dependencies.modules,
compileClient: true,
discovery: true,
env: { name: 'local' },
fallbackRegistryUrl,
fallbackClient,
hotReloading,
liveReloadPort: liveReload.port,
local: true,
postRequestPayloadSize,
components: opts.components,
path: path.resolve(componentsDir),
port,
templates: dependencies.templates,
verbosity: 1,
preload: localConfig.development?.preload,
routes: localConfig.development?.routes
});
} catch (err: any) {
logger.err(String(err));
throw err;
}

registerPlugins(registry);

Expand Down
31 changes: 31 additions & 0 deletions src/registry/domain/options-sanitiser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,5 +220,36 @@ export default function optionsSanitiser(input: RegistryOptions): Config {
options.env = {};
}

// Handle routes with string handlers - load the handler files
if (options.routes) {
options.routes = options.routes.map((route) => {
if (typeof route.handler === 'string') {
try {
const fs = require('node:fs');
const path = require('node:path');
const handlerPath = path.isAbsolute(route.handler)
? route.handler
: path.resolve(process.cwd(), route.handler);

if (fs.existsSync(handlerPath)) {
// Clear require cache to ensure fresh module loading
delete require.cache[require.resolve(handlerPath)];
const handlerModule = require(handlerPath);
route.handler = handlerModule.default || handlerModule;
} else {
throw new Error(`Handler file not found: ${handlerPath}`);
}
} catch (error) {
console.warn(
`Warning: Could not load route handler "${route.handler}":`,
(error as Error).message
);
// Keep the original string handler to avoid breaking the application
}
}
return route;
});
}

return options as Config;
}
8 changes: 6 additions & 2 deletions src/registry/domain/validators/registry-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,13 @@ export default function registryConfiguration(
);
}

if (typeof route.handler !== 'function') {
if (
typeof route.handler !== 'function' &&
typeof route.handler !== 'string'
) {
return returnError(
strings.errors.registry.CONFIGURATION_ROUTES_HANDLER_MUST_BE_FUNCTION
strings.errors.registry
.CONFIGURATION_ROUTES_HANDLER_MUST_BE_FUNCTION_OR_FILE_PATH
);
}

Expand Down
4 changes: 3 additions & 1 deletion src/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ export default function registry<T = any>(inputOptions: RegistryOptions<T>) {
eventsHandler.fire('start', {});

if (options.verbosity) {
ok(`Registry started at port http://localhost:${app.get('port')}`);
ok(
`Registry started at port http://localhost:${app.get('port')}${options.prefix}`
);

if (componentsInfo) {
const componentsNumber = Object.keys(
Expand Down
22 changes: 18 additions & 4 deletions src/registry/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export function create(app: Express, conf: Config, repository: Repository) {

const prefix = conf.prefix;

if (prefix !== '/') {
const definedBaseRoute = conf.routes?.find((route) => route.route === '/');

if (prefix !== '/' && !definedBaseRoute) {
app.get('/', (_req, res) => res.redirect(prefix));
app.get(prefix.substring(0, prefix.length - 1), routes.index);
}
Expand Down Expand Up @@ -92,9 +94,21 @@ export function create(app: Express, conf: Config, repository: Repository) {

if (conf.routes) {
for (const route of conf.routes) {
app[
route.method.toLowerCase() as 'get' | 'post' | 'put' | 'patch' | 'head'
](route.route, route.handler);
// Ensure handler is a function (should be converted by options-sanitiser)
if (typeof route.handler === 'function') {
app[
route.method.toLowerCase() as
| 'get'
| 'post'
| 'put'
| 'patch'
| 'head'
](route.route, route.handler);
} else {
console.warn(
`Warning: Route handler for "${route.route}" is not a function. Skipping route.`
);
}
}
}
}
9 changes: 6 additions & 3 deletions src/registry/views/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,11 +280,14 @@ export default function preview(vm: {
});
}

if (window.oc && window.oc.events && window.oc.events.on) {
window.oc.events.on('oc:error', function(ev, error) {
window.oc = window.oc || {};
window.oc.cmd = window.oc.cmd || [];
window.oc.cmd.push(function(oc) {
oc.events.on('oc:error', function(ev, error) {
ensureOverlay(error);
});
}
});

})();
</script>

Expand Down
4 changes: 2 additions & 2 deletions src/resources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ export default {
'Registry configuration is not valid: prefix should end with "/"',
CONFIGURATION_PREFIX_DOES_NOT_START_WITH_SLASH:
'Registry configuration is not valid: prefix should start with "/"',
CONFIGURATION_ROUTES_HANDLER_MUST_BE_FUNCTION:
'Registry configuration is not valid: handler should be a function',
CONFIGURATION_ROUTES_HANDLER_MUST_BE_FUNCTION_OR_FILE_PATH:
'Registry configuration is not valid: handler should be a function or a file path',
CONFIGURATION_ROUTES_NOT_VALID:
'Registry configuration is not valid: each route should contain route, method and handler',
CONFIGURATION_ROUTES_MUST_BE_ARRAY:
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ export interface Config<T = any> {
routes?: Array<{
route: string;
method: string;
handler: (req: Request, res: Response) => void;
handler: string | ((req: Request, res: Response) => void);
}>;
/**
* Convenience S3 configuration – if present the registry will create
Expand Down
6 changes: 3 additions & 3 deletions test/unit/registry-domain-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,16 +368,16 @@ describe('registry : domain : validator', () => {
});
});

describe('when route contains handler that is not a function', () => {
describe('when route contains handler that is not a function or a string', () => {
const conf = {
routes: [{ route: '/hello', method: 'get', handler: 'hello' }],
routes: [{ route: '/hello', method: 'get', handler: 3 }],
s3: baseS3Conf
};

it('should not be valid', () => {
expect(validate(conf).isValid).to.be.false;
expect(validate(conf).message).to.be.eql(
'Registry configuration is not valid: handler should be a function'
'Registry configuration is not valid: handler should be a function or a file path'
);
});
});
Expand Down