Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow adding of steps #72

Merged
merged 4 commits into from
Feb 1, 2024
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
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,12 +404,25 @@ Schema for `mockSteps`
{
run: "locates the step using the run field"
mockWith: "command or a new step as JSON to replace the given step with"
} |
{
index: "locates the step using the index (0 indexed) in the steps array of the workflow"
mockWith: "command or a new step as JSON to replace the given step with"
} |
{
before: "index of the step or the id/name/run/uses of the step before which you want to insert a step"
mockWith: "a new step as JSON to be added before the given step"
} |
{
after: "index of the step or the id/name/run/uses of the step after which you want to insert a step"
mockWith: "a new step as JSON to be added after the given step"
}
)[]
}
```

NOTE: Please use `MockGithub` to run the workflow in a clean safe github repository so that any changes made to the Workflow file are done in the test environment and not to the actual file.
**Important Notes**:
- Please use `MockGithub` to run the workflow in a clean safe github repository so that any changes made to the Workflow file are done in the test environment and not to the actual file.

#### Run result

Expand Down
75 changes: 63 additions & 12 deletions src/step-mocker/step-mocker.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import {
GithubWorkflow,
GithubWorkflowStep,
isStepIdentifierUsingAfter,
isStepIdentifierUsingBefore,
isStepIdentifierUsingBeforeOrAfter,
isStepIdentifierUsingId,
isStepIdentifierUsingIndex,
isStepIdentifierUsingName,
isStepIdentifierUsingRun,
isStepIdentifierUsingUses,
MockStep,
StepIdentifier,
StepIdentifierUsingAfter,
StepIdentifierUsingBefore,
} from "@aj/step-mocker/step-mocker.types";
import { existsSync, readFileSync, writeFileSync } from "fs";
import path from "path";
Expand All @@ -23,35 +29,64 @@ export class StepMocker {
async mock(mockSteps: MockStep) {
const filePath = this.getWorkflowPath();
const workflow = await this.readWorkflowFile(filePath);
for (const job of Object.keys(mockSteps)) {
for (const mockStep of mockSteps[job]) {
const { step, stepIndex } = this.locateStep(workflow, job, mockStep);
for (const jobId of Object.keys(mockSteps)) {
const stepsToAdd = [];
for (const mockStep of mockSteps[jobId]) {
const { step, stepIndex } = this.locateStep(workflow, jobId, mockStep);
if (step) {
if (typeof mockStep.mockWith === "string") {
this.updateStep(workflow, job, stepIndex, {
...step,
run: mockStep.mockWith,
uses: undefined,
});
if (isStepIdentifierUsingBeforeOrAfter(mockStep)) {
// need to adjust the step index if there were elements added previously
const adjustIndex: number = stepsToAdd.filter(s => s.stepIndex < stepIndex).length;
// we will only actually add the steps at the end so as to avoid indexing errors in subsequent add steps
stepsToAdd.push({jobId, stepIndex: stepIndex + adjustIndex, mockStep});
} else {
this.updateStep(workflow, job, stepIndex, mockStep.mockWith);
this.updateStep(workflow, jobId, stepIndex, mockStep);
}
} else {
throw new Error("Could not find step");
}
}
stepsToAdd.forEach(s => this.addStep(workflow, s.jobId, s.stepIndex, s.mockStep));
}
return this.writeWorkflowFile(filePath, workflow);
}

private addStep(
workflow: GithubWorkflow,
jobId: string,
stepIndex: number,
mockStep: StepIdentifierUsingAfter | StepIdentifierUsingBefore
) {
if (workflow.jobs[jobId]) {
let indexToInsertAt = stepIndex;
if (isStepIdentifierUsingBefore(mockStep)) {
indexToInsertAt = stepIndex <= 0 ? 0 : indexToInsertAt - 1;
} else {
indexToInsertAt =
stepIndex >= workflow.jobs[jobId].steps.length - 1
? workflow.jobs[jobId].steps.length
: indexToInsertAt + 1;
}
workflow.jobs[jobId].steps.splice(indexToInsertAt, 0, {...mockStep.mockWith});
}
}

private updateStep(
workflow: GithubWorkflow,
jobId: string,
stepIndex: number,
newStep: GithubWorkflowStep
mockStep: StepIdentifier
) {
if (workflow.jobs[jobId]) {
const oldStep = workflow.jobs[jobId].steps[stepIndex];
const newStep =
typeof mockStep.mockWith === "string"
? {
...oldStep,
run: mockStep.mockWith,
uses: undefined,
}
: mockStep.mockWith;
const updatedStep = { ...oldStep, ...newStep };

for (const key of Object.keys(oldStep)) {
Expand All @@ -72,7 +107,7 @@ export class StepMocker {
jobId: string,
step: StepIdentifier
): { stepIndex: number; step: GithubWorkflowStep | undefined } {
const index = workflow.jobs[jobId]?.steps.findIndex(s => {
const index = workflow.jobs[jobId]?.steps.findIndex((s, index) => {
if (isStepIdentifierUsingId(step)) {
return step.id === s.id;
}
Expand All @@ -88,6 +123,22 @@ export class StepMocker {
if (isStepIdentifierUsingRun(step)) {
return step.run === s.run;
}

if (isStepIdentifierUsingIndex(step)) {
return step.index === index;
}

if (isStepIdentifierUsingBefore(step)) {
return typeof step.before === "string"
? [s.id, s.name, s.uses, s.run].includes(step.before)
: step.before === index;
}

if (isStepIdentifierUsingAfter(step)) {
return typeof step.after === "string"
? [s.id, s.name, s.uses, s.run].includes(step.after)
: step.after === index;
}
return false;
});

Expand Down
35 changes: 33 additions & 2 deletions src/step-mocker/step-mocker.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,18 @@ export type StepIdentifierUsingName = { name: string; mockWith: GithubWorkflowSt
export type StepIdentifierUsingId = { id: string; mockWith: GithubWorkflowStep | string };
export type StepIdentifierUsingUses = { uses: string; mockWith: GithubWorkflowStep | string };
export type StepIdentifierUsingRun = { run: string; mockWith: GithubWorkflowStep | string };
export type StepIdentifierUsingIndex = { index: number; mockWith: GithubWorkflowStep | string };
export type StepIdentifierUsingBefore = { before: number | string; mockWith: GithubWorkflowStep };
export type StepIdentifierUsingAfter = { after: number | string; mockWith: GithubWorkflowStep };

export type StepIdentifier =
| StepIdentifierUsingName
| StepIdentifierUsingId
| StepIdentifierUsingUses
| StepIdentifierUsingRun;
| StepIdentifierUsingRun
| StepIdentifierUsingIndex
| StepIdentifierUsingBefore
| StepIdentifierUsingAfter;

export function isStepIdentifierUsingName(
step: StepIdentifier
Expand All @@ -118,6 +125,30 @@ export function isStepIdentifierUsingUses(

export function isStepIdentifierUsingRun(
step: StepIdentifier
): step is StepIdentifierUsingUses {
): step is StepIdentifierUsingRun {
return Object.prototype.hasOwnProperty.call(step, "run");
}

export function isStepIdentifierUsingIndex(
step: StepIdentifier
): step is StepIdentifierUsingIndex {
return Object.prototype.hasOwnProperty.call(step, "index");
}

export function isStepIdentifierUsingBefore(
step: StepIdentifier
): step is StepIdentifierUsingBefore {
return Object.prototype.hasOwnProperty.call(step, "before");
}

export function isStepIdentifierUsingAfter(
step: StepIdentifier
): step is StepIdentifierUsingAfter {
return Object.prototype.hasOwnProperty.call(step, "after");
}

export function isStepIdentifierUsingBeforeOrAfter(
step: StepIdentifier
): step is StepIdentifierUsingBefore | StepIdentifierUsingAfter {
return isStepIdentifierUsingBefore(step) || isStepIdentifierUsingAfter(step);
}
Loading
Loading