Skip to content

Commit 732911a

Browse files
committed
Turn Env into a class, and add ReadOnlyEnv
1 parent 1d0e1b2 commit 732911a

4 files changed

Lines changed: 91 additions & 51 deletions

File tree

lib/entry-points.js

Lines changed: 30 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api-client.test.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as sinon from "sinon";
66
import * as actionsUtil from "./actions-util";
77
import * as api from "./api-client";
88
import { DO_NOT_RETRY_STATUSES } from "./api-client";
9-
import { setupTests } from "./testing-utils";
9+
import { getTestEnv, setupTests } from "./testing-utils";
1010
import * as util from "./util";
1111

1212
setupTests(test);
@@ -20,16 +20,19 @@ test.serial("getApiClient", async (t) => {
2020
const githubStub: sinon.SinonStub = sinon.stub();
2121
pluginStub.returns(githubStub);
2222

23+
const env = getTestEnv();
24+
env.set(
25+
actionsUtil.ActionsEnvVars.GITHUB_SERVER_URL,
26+
"http://github.localhost",
27+
);
28+
env.set(
29+
actionsUtil.ActionsEnvVars.GITHUB_API_URL,
30+
"http://api.github.localhost",
31+
);
32+
2333
sinon.stub(actionsUtil, "getRequiredInput").withArgs("token").returns("xyz");
24-
const requiredEnvParamStub = sinon.stub(util, "getRequiredEnvParam");
25-
requiredEnvParamStub
26-
.withArgs("GITHUB_SERVER_URL")
27-
.returns("http://github.localhost");
28-
requiredEnvParamStub
29-
.withArgs("GITHUB_API_URL")
30-
.returns("http://api.github.localhost");
31-
32-
api.getApiClient();
34+
35+
api.getApiClient(env);
3336

3437
t.assert(
3538
githubStub.calledOnceWithExactly({

src/api-client.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
getActionVersion,
88
getRequiredInput,
99
} from "./actions-util";
10-
import { EnvVar } from "./environment";
10+
import { EnvVar, ReadOnlyEnv, getEnv } from "./environment";
1111
import { Logger } from "./logging";
1212
import { getRepositoryNwo, RepositoryNwo } from "./repository";
1313
import {
@@ -71,16 +71,16 @@ function createApiClientWithDetails(
7171
);
7272
}
7373

74-
export function getApiDetails(): GitHubApiDetails {
74+
export function getApiDetails(env: ReadOnlyEnv = getEnv()): GitHubApiDetails {
7575
return {
7676
auth: getRequiredInput("token"),
77-
url: getRequiredEnvParam(ActionsEnvVars.GITHUB_SERVER_URL),
78-
apiURL: getRequiredEnvParam(ActionsEnvVars.GITHUB_API_URL),
77+
url: env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL),
78+
apiURL: env.getRequired(ActionsEnvVars.GITHUB_API_URL),
7979
};
8080
}
8181

82-
export function getApiClient() {
83-
return createApiClientWithDetails(getApiDetails());
82+
export function getApiClient(env: ReadOnlyEnv = getEnv()) {
83+
return createApiClientWithDetails(getApiDetails(env));
8484
}
8585

8686
export function getApiClientWithExternalAuth(

src/environment.ts

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,7 @@ export enum EnvVar {
167167
/**
168168
* Gets an environment variable, but throws an error if it is not set.
169169
*/
170-
export function getRequiredEnvVar(
171-
env: NodeJS.ProcessEnv,
172-
paramName: string,
173-
): string {
170+
function getRequiredEnvVar(env: NodeJS.ProcessEnv, paramName: string): string {
174171
const value = env[paramName];
175172
if (value === undefined || value.length === 0) {
176173
throw new Error(`${paramName} environment variable must be set`);
@@ -180,6 +177,8 @@ export function getRequiredEnvVar(
180177

181178
/**
182179
* Get an environment parameter, but throw an error if it is not set.
180+
*
181+
* @deprecated Use `getRequired` of a `ReadOnlyEnv` or `Env` instance instead.
183182
*/
184183
export function getRequiredEnvParam(paramName: string): string {
185184
return getRequiredEnvVar(process.env, paramName);
@@ -188,7 +187,7 @@ export function getRequiredEnvParam(paramName: string): string {
188187
/**
189188
* Gets an environment variable, but returns `undefined` if it is not set or empty.
190189
*/
191-
export function getOptionalEnvVarFrom(
190+
function getOptionalEnvVarFrom(
192191
env: NodeJS.ProcessEnv,
193192
paramName: string,
194193
): string | undefined {
@@ -201,31 +200,52 @@ export function getOptionalEnvVarFrom(
201200

202201
/**
203202
* Get an environment variable, but return `undefined` if it is not set or empty.
203+
*
204+
* @deprecated Use `getOptional` of a `ReadOnlyEnv` or `Env` instance instead.
204205
*/
205206
export function getOptionalEnvVar(paramName: string): string | undefined {
206207
return getOptionalEnvVarFrom(process.env, paramName);
207208
}
208209

209-
/** Gets an `Env` instance for `env`, which is `process.env` by default. */
210-
export function getEnv(env: NodeJS.ProcessEnv = process.env): Env {
211-
return {
212-
getRequired: (name) => getRequiredEnvVar(env, name),
213-
getOptional: (name) => getOptionalEnvVarFrom(env, name),
214-
entries: () => Object.entries(env),
215-
set: (name, value) => {
216-
env[name] = value;
217-
},
218-
};
219-
}
210+
/**
211+
* An abstraction around read-only environment variables, to allow abstracting away from `process.env`
212+
* in tests, while clearly signalling in regular code that the consumer of the `ReadOnlyEnv` instance
213+
* will only read from it.
214+
*/
215+
export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
216+
constructor(protected readonly vars: Record<string, T>) {}
220217

221-
/** A wrapper around an environment, to allow abstracting away from `process.env` in tests. */
222-
export interface Env {
223218
/** Tries to get the value for `name` and throws if there isn't one. */
224-
getRequired(name: string): string;
219+
public getRequired(name: string): string {
220+
return getRequiredEnvVar(this.vars, name);
221+
}
222+
225223
/** Gets the value for `name`, or `undefined` if it isn't set or empty. */
226-
getOptional(name: string): string | undefined;
224+
public getOptional(name: string): string | undefined {
225+
return getOptionalEnvVarFrom(this.vars, name);
226+
}
227+
227228
/** Gets the entries of the underlying `ProcessEnv`. */
228-
entries(): Array<[string, string | undefined]>;
229+
public entries(): Array<[string, T]> {
230+
return Object.entries(this.vars);
231+
}
232+
}
233+
234+
/**
235+
* A wrapper around an environment, to allow abstracting away from `process.env` in tests.
236+
* Use `ReadOnlyEnv` instead if you only plan to read from the environment.
237+
* This type allows writing to the environment.
238+
*/
239+
export class Env<
240+
T extends string | undefined = string | undefined,
241+
> extends ReadOnlyEnv<T> {
229242
/** Sets an environment variable. */
230-
set(name: string, value: string): void;
243+
public set(name: string, value: T): void {
244+
this.vars[name] = value;
245+
}
246+
}
247+
248+
/** Gets an `Env` instance for `env`, which is `process.env` by default. */
249+
export function getEnv(env: NodeJS.ProcessEnv = process.env): Env {
250+
return new Env(env);
231251
}

0 commit comments

Comments
 (0)