Skip to content

Commit 708e106

Browse files
committed
Index ActionState over the types of state used
1 parent b6be81d commit 708e106

5 files changed

Lines changed: 58 additions & 26 deletions

File tree

src/action-common.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,36 @@ import { Env } from "./environment";
22
import { FeatureEnablement } from "./feature-flags";
33
import { Logger } from "./logging";
44

5-
export interface ActionState {
6-
/** The logger that is in use. */
7-
logger: Logger;
5+
/** Describes different state features that an Action may have. */
6+
export interface FeatureState {
7+
Logger: {
8+
/** The logger that is in use. */
9+
logger: Logger;
10+
};
11+
Env: {
12+
/** Information about environment variables. */
13+
env: Env;
14+
};
15+
FeatureFlags: {
16+
/** Information about enabled feature flags. */
17+
features: FeatureEnablement;
18+
};
19+
}
820

9-
/** Information about environment variables. */
10-
env: Env;
21+
/** Identifies a type of state an Action may have. */
22+
export type StateFeature = keyof FeatureState;
1123

12-
/** Information about enabled feature flags. */
13-
features: FeatureEnablement;
14-
}
24+
/** Constructs the union of all state types identifies by `Fs`. */
25+
export type FieldsOf<Fs extends readonly StateFeature[]> = Fs extends [
26+
infer Head extends StateFeature,
27+
]
28+
? FeatureState[Head]
29+
: Fs extends [
30+
infer Head extends StateFeature,
31+
...infer Tail extends StateFeature[],
32+
]
33+
? FeatureState[Head] & FieldsOf<Tail>
34+
: never;
35+
36+
/** Describes the state of an Action that has access to the state corresponding to `Fs`. */
37+
export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs>;

src/config-utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ async function downloadCacheWithTime(
602602
}
603603

604604
async function loadUserConfig(
605-
actionState: ActionState,
605+
actionState: ActionState<["Logger", "Env", "FeatureFlags"]>,
606606
configFile: string,
607607
workspacePath: string,
608608
apiDetails: api.GitHubApiCombinedDetails,
@@ -1160,7 +1160,7 @@ export async function initConfig(
11601160
logger.debug("No configuration file was provided");
11611161
} else {
11621162
logger.debug(`Using configuration file: ${inputs.configFile}`);
1163-
const actionState: ActionState = { logger, features, env: getEnv() };
1163+
const actionState = { logger, features, env: getEnv() };
11641164
userConfig = await loadUserConfig(
11651165
actionState,
11661166
inputs.configFile,

src/config/file.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function getConfigFileInput(
5151
* @returns The `UserConfig`, if it could be fetched and parsed successfully.
5252
*/
5353
export async function getRemoteConfig(
54-
actionState: ActionState,
54+
actionState: ActionState<["Logger", "Env", "FeatureFlags"]>,
5555
configFile: string,
5656
apiDetails: api.GitHubApiCombinedDetails,
5757
): Promise<UserConfig> {

src/config/remote-file.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ function parseOldRemoteFileAddress(
8080
* @throws `ConfigurationError` if the format of `configFile` is not valid.
8181
*/
8282
export async function parseRemoteFileAddress(
83-
actionState: ActionState,
83+
actionState: ActionState<["FeatureFlags", "Env"]>,
8484
configFile: string,
8585
): Promise<RemoteFileAddress> {
8686
// Try to parse the input using the old format. If successful, return the

src/testing-utils.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import test, {
1010
import nock from "nock";
1111
import * as sinon from "sinon";
1212

13-
import { ActionState } from "./action-common";
13+
import { ActionState, StateFeature } from "./action-common";
1414
import { ActionsEnv, ActionsEnvVars, getActionVersion } from "./actions-util";
1515
import { AnalysisKind } from "./analyses";
1616
import * as apiClient from "./api-client";
@@ -189,18 +189,25 @@ export function getTestActionsEnv(): ActionsEnv {
189189
};
190190
}
191191

192+
/** For testing purposes, we make all available state features accessible in `TestEnv`. */
193+
type AllState = ["Logger", "Env", "FeatureFlags"];
194+
192195
/**
193196
* Wraps a function that accepts an `ActionEnv` for testing in different environments.
194197
*/
195-
export class TestEnv<Args extends readonly any[], R> {
196-
private readonly fn: (state: ActionState, ...args: Args) => R;
198+
export class TestEnv<
199+
Args extends readonly any[],
200+
R,
201+
Fs extends ReadonlyArray<AllState[number]>,
202+
> {
203+
private readonly fn: (state: ActionState<Fs>, ...args: Args) => R;
197204
private args?: Args;
198-
private state: ActionState;
205+
private state: ActionState<AllState>;
199206

200207
constructor(
201-
fn: (state: ActionState, ...args: Args) => R,
208+
fn: (state: ActionState<Fs>, ...args: Args) => R,
202209
args?: Args,
203-
initialState?: ActionState,
210+
initialState?: ActionState<AllState>,
204211
) {
205212
this.fn = fn;
206213
this.args = args;
@@ -211,11 +218,11 @@ export class TestEnv<Args extends readonly any[], R> {
211218
};
212219
}
213220

214-
private clone(): TestEnv<Args, R> {
221+
private clone(): TestEnv<Args, R, Fs> {
215222
return new TestEnv(this.fn, this.args, { ...this.state });
216223
}
217224

218-
public getState(): ActionState {
225+
public getState(): ActionState<AllState> {
219226
return this.state;
220227
}
221228

@@ -229,13 +236,13 @@ export class TestEnv<Args extends readonly any[], R> {
229236
return result;
230237
}
231238

232-
public withFeatures(enabled: Feature[]): TestEnv<Args, R> {
239+
public withFeatures(enabled: Feature[]): TestEnv<Args, R, Fs> {
233240
const result = this.clone();
234241
result.state.features = createFeatures(enabled);
235242
return result;
236243
}
237244

238-
public withEnv(env: Env): TestEnv<Args, R> {
245+
public withEnv(env: Env): TestEnv<Args, R, Fs> {
239246
const result = this.clone();
240247
result.state.env = env;
241248
return result;
@@ -245,7 +252,7 @@ export class TestEnv<Args extends readonly any[], R> {
245252
if (!this.args) {
246253
throw new Error("Trying to call function in TestEnv without arguments.");
247254
}
248-
return this.fn(this.state, ...this.args);
255+
return this.fn(this.state as ActionState<Fs>, ...this.args);
249256
}
250257

251258
public passes<T>(
@@ -259,9 +266,11 @@ export class TestEnv<Args extends readonly any[], R> {
259266
}
260267

261268
/** Utility function to construct a `TestEnv`. */
262-
export function callee<Args extends readonly any[], R>(
263-
fn: (state: ActionState, ...args: Args) => R,
264-
): TestEnv<Args, R> {
269+
export function callee<
270+
Args extends readonly any[],
271+
R,
272+
Fs extends readonly StateFeature[],
273+
>(fn: (state: ActionState<Fs>, ...args: Args) => R): TestEnv<Args, R, Fs> {
265274
return new TestEnv(fn);
266275
}
267276

0 commit comments

Comments
 (0)