Env variable($PATH) for theia terminal to be different from the underlying shell

Hi theia experts,

We would like to have env variable($PATH) for theia terminal to be different from the underlying shell.

At this point, we understand that Theia inherits the value of the underlying shell. Reference thread - Theia process environment variables.

Is it possible to do ? Is there any flags that we need to pass to yarn start command while starting the back-end server to achieve this use case?

Or, should I change this piece of code here by overriding to modify the $PATH for theia terminal?

Regards,
Chid

I am trying to override the EnvVariablesServerImpl class but facing the following error when I started theia:

CustomEnvVariableServerImpl.ts

import { EnvVariable } from "@theia/core/lib/common/env-variables";
import { EnvVariablesServerImpl } from "@theia/core/lib/node/env-variables/env-variables-server";
import { injectable } from "@theia/core/shared/inversify";

@injectable()
export class CustomEnvVariableServerImpl extends EnvVariablesServerImpl {

    protected readonly ENV_PATH: string = 'PATH';
    protected readonly envs: { [key: string]: EnvVariable } = {};

    constructor() {
        super();
        const prEnv = process.env;
        Object.keys(prEnv).forEach((key: string) => {
            if (key === this.ENV_PATH) {
                const JDK17_PATH = 'test-path/bin:'
                this.envs[key] = { "name": key, "value": ENV_PATH + prEnv[key] };

            }
            else {
                this.envs[key] = { "name": key, "value": prEnv[key] };
            }
        });
    }

    async getVariables(): Promise<EnvVariable[]> {
        return Object.keys(this.envs).map(key =>
            this.envs[key]);
    }

}

backend-application-module.ts

    bind(CustomEnvVariableServerImpl).toSelf().inSingletonScope();
    // unbind(EnvVariablesServerImpl);
    rebind(EnvVariablesServerImpl).toService(CustomEnvVariableServerImpl);

But having the following issue when starting theia now:

Failed to start the backend application:
Error: Could not unbind serviceIdentifier: EnvVariablesServerImpl

@chid.crushev That’s because EnvVariablesServerImpl isn’t bound, but EnvVariablesServer is. You should instead do:

rebind(EnvVariablesServer).to(CustomEnvVariableServerImpl).inSingletonScope();

@msujew ,

Thanks for the help. Yup, it fixed the unbind issue. However, the env variable($PATH) in theia front-end terminal is still the same as the underlying shell. Any thoughts on this on how to make this work properly?

CustomEnvVariableServerImpl.ts

    constructor() {
        super();
        const prEnv = process.env;
        Object.keys(prEnv).forEach((key: string) => {
            if (key === this.ENV_PATH) {
                const JDK17_PATH = 'test-path/bin:'
                this.envs[key] = { "name": key, "value": ENV_PATH + prEnv[key] };

            }
            else {
                this.envs[key] = { "name": key, "value": prEnv[key] };
            }
        });
    }

Ah, I didn’t notice you were talking about terminals. You’ll have to look here and here to see how to change that. The terminal server doesn’t use the EnvVariablesServer at all.

@msujew Thanks for all your help and continued support. I’ve created CustomEnvironmentUtils class to extend the EnvironmentUtils to modify the path.

CustomEnvironmentUtils.ts

export class CustomEnvironmentUtils extends EnvironmentUtils {

    protected readonly ENV_PATH: string = 'PATH';
    protected readonly JDK17_PATH: string = '/usr/lib64/jdk-17/bin:';


    mergeProcessEnv(env: Record<string, string | null> = {}): Record<string, string> {
        env = this.normalizeEnv(env);
        // eslint-disable-next-line no-null/no-null
        const mergedEnv: Record<string, string> = Object.create(null);
        for (const [key, value] of Object.entries(this.normalizeEnv(process.env))) {
            // Ignore keys from `process.env` that are overridden in `env`. Accept only non-empty strings.
            if (!(key in env) && value) {
                mergedEnv[key] = value;
            }
        }
        for (const [key, value] of Object.entries(env)) {
            // Accept only non-empty strings from the `env` object.
            if (value) {
                if (key === this.ENV_PATH) {
                    mergedEnv[key] = this.JDK17_PATH;
                }
                else {
                    mergedEnv[key] = value;
                }
            }
        }
        return mergedEnv;
    }
}

However, this JDK17_PATH is not getting prepended to the path. It adds JDK17_PATH to the end of the path. How do I prepend JDK17_PATH to the env variable PATH?

Regards,
Chid.