From f28384e7eaa21416ee4774a7c3ae6f79a439c0e3 Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Mon, 6 Feb 2023 15:39:44 -0500 Subject: [PATCH] Add a type to represent local and remote files --- actions-workflow-parser/src/model/convert.ts | 3 +- .../src/workflows/file-provider.ts | 3 +- .../src/workflows/file-reference.test.ts | 55 +++++++++++++++++++ .../src/workflows/file-reference.ts | 42 ++++++++++++++ actions-workflow-parser/src/xlang.test.ts | 5 +- 5 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 actions-workflow-parser/src/workflows/file-reference.test.ts create mode 100644 actions-workflow-parser/src/workflows/file-reference.ts diff --git a/actions-workflow-parser/src/model/convert.ts b/actions-workflow-parser/src/model/convert.ts index 197926b..944ba75 100644 --- a/actions-workflow-parser/src/model/convert.ts +++ b/actions-workflow-parser/src/model/convert.ts @@ -1,6 +1,7 @@ import {TemplateContext} from "../templates/template-context"; import {TemplateToken, TemplateTokenError} from "../templates/tokens/template-token"; import {FileProvider} from "../workflows/file-provider"; +import {parseFileReference} from "../workflows/file-reference"; import {parseWorkflow} from "../workflows/workflow-parser"; import {convertConcurrency} from "./converter/concurrency"; import {convertOn} from "./converter/events"; @@ -97,7 +98,7 @@ export async function convertWorkflowTemplate( } try { - const file = await fileProvider.getFileContent(job.ref.value); + const file = await fileProvider.getFileContent(parseFileReference(job.ref.value)); const workflow = parseWorkflow(file, context); if (!workflow.value) { continue; diff --git a/actions-workflow-parser/src/workflows/file-provider.ts b/actions-workflow-parser/src/workflows/file-provider.ts index 468fd34..097d464 100644 --- a/actions-workflow-parser/src/workflows/file-provider.ts +++ b/actions-workflow-parser/src/workflows/file-provider.ts @@ -1,5 +1,6 @@ import {File} from "./file"; +import {FileReference} from "./file-reference"; export interface FileProvider { - getFileContent(path: string): Promise; + getFileContent(ref: FileReference): Promise; } diff --git a/actions-workflow-parser/src/workflows/file-reference.test.ts b/actions-workflow-parser/src/workflows/file-reference.test.ts new file mode 100644 index 0000000..f1cff01 --- /dev/null +++ b/actions-workflow-parser/src/workflows/file-reference.test.ts @@ -0,0 +1,55 @@ +import {parseFileReference} from "./file-reference"; + +describe("parseFileReference", () => { + it("parses local file reference", () => { + const ref = parseFileReference("./workflow/path"); + expect(ref).toEqual({ + path: "workflow/path" + }); + }); + + it("parses local file references with an empty path", () => { + const ref = parseFileReference("./"); + expect(ref).toEqual({ + path: "" + }); + }); + + it("parses remote file reference", () => { + const ref = parseFileReference("owner/repo/path@version"); + expect(ref).toEqual({ + owner: "owner", + repository: "repo", + path: "path", + version: "version" + }); + }); + + it("parses remote file reference with an empty path", () => { + const ref = parseFileReference("owner/repo@version"); + expect(ref).toEqual({ + owner: "owner", + repository: "repo", + path: "", + version: "version" + }); + }); + + it("parses remote file reference with slashes in the version", () => { + const ref = parseFileReference("owner/repo@feature-branch/dev"); + expect(ref).toEqual({ + owner: "owner", + repository: "repo", + path: "", + version: "feature-branch/dev" + }); + }); + + it("throws for malformed remote file references", () => { + expect(() => parseFileReference("owner/repo/path")).toThrowError("Invalid file reference: owner/repo/path"); + + expect(() => parseFileReference("owner/repo/path@")).toThrowError("Invalid file reference: owner/repo/path@"); + + expect(() => parseFileReference("owner@")).toThrowError("Invalid file reference: owner@"); + }); +}); diff --git a/actions-workflow-parser/src/workflows/file-reference.ts b/actions-workflow-parser/src/workflows/file-reference.ts new file mode 100644 index 0000000..3b51ca0 --- /dev/null +++ b/actions-workflow-parser/src/workflows/file-reference.ts @@ -0,0 +1,42 @@ +export type FileReference = LocalFileReference | RemoteFileReference; + +export type LocalFileReference = { + path: string; +}; + +export type RemoteFileReference = { + repository: string; + owner: string; + path: string; + version: string; +}; + +export function parseFileReference(ref: string): FileReference { + if (ref.startsWith("./")) { + return { + path: ref.substring(2) + }; + } + + const [remotePath, version] = ref.split("@"); + const [owner, repository, ...pathSegments] = remotePath.split("/").filter(s => s.length > 0); + + if (!owner || !repository || !version) { + throw new Error(`Invalid file reference: ${ref}`); + } + + return { + repository, + owner, + path: pathSegments.join("/"), + version + }; +} + +export function fileIdentifier(ref: FileReference): string { + if (!("repository" in ref)) { + return "./" + ref.path; + } + + return `${ref.owner}/${ref.repository}/${ref.path}@${ref.version}`; +} diff --git a/actions-workflow-parser/src/xlang.test.ts b/actions-workflow-parser/src/xlang.test.ts index 4b2fee9..e9e2ab1 100644 --- a/actions-workflow-parser/src/xlang.test.ts +++ b/actions-workflow-parser/src/xlang.test.ts @@ -5,6 +5,7 @@ import {convertWorkflowTemplate} from "./model/convert"; import {TraceWriter} from "./templates/trace-writer"; import {File} from "./workflows/file"; import {FileProvider} from "./workflows/file-provider"; +import {fileIdentifier, FileReference} from "./workflows/file-reference"; import {parseWorkflow} from "./workflows/workflow-parser"; interface TestOptions { @@ -61,8 +62,8 @@ describe("x-lang tests", () => { } const testFileProvider: FileProvider = { - getFileContent: async (fileName: string) => { - const file = reusableWorkflows[fileName]; + getFileContent: async (ref: FileReference) => { + const file = reusableWorkflows[fileIdentifier(ref)]; if (file) { return file; }