Skip to content

Commit f77cf55

Browse files
committed
Support old and new formats
1 parent 812b882 commit f77cf55

4 files changed

Lines changed: 189 additions & 13 deletions

File tree

lib/entry-points.js

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

src/config/remote-file.test.ts

Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
test("parseRemoteFileAddress accepts full remote addresses", async (t) => {
1616
const env = getTestEnv();
1717

18+
// Old format.
1819
t.deepEqual(parseRemoteFileAddress(env, "owner/repo/path@ref"), {
1920
owner: "owner",
2021
repo: "repo",
@@ -86,6 +87,79 @@ test("parseRemoteFileAddress accepts full remote addresses", async (t) => {
8687
ref: "ref/feature",
8788
} satisfies RemoteFileAddress,
8889
);
90+
91+
// New format.
92+
t.deepEqual(parseRemoteFileAddress(env, "owner/repo@ref:path"), {
93+
owner: "owner",
94+
repo: "repo",
95+
path: "path",
96+
ref: "ref",
97+
} satisfies RemoteFileAddress);
98+
99+
t.deepEqual(parseRemoteFileAddress(env, "owner /repo@ref:path"), {
100+
owner: "owner",
101+
repo: "repo",
102+
path: "path",
103+
ref: "ref",
104+
} satisfies RemoteFileAddress);
105+
106+
t.deepEqual(parseRemoteFileAddress(env, "owner/ repo@ref:path"), {
107+
owner: "owner",
108+
repo: "repo",
109+
path: "path",
110+
ref: "ref",
111+
} satisfies RemoteFileAddress);
112+
113+
t.deepEqual(parseRemoteFileAddress(env, "owner/repo @ref:path"), {
114+
owner: "owner",
115+
repo: "repo",
116+
path: "path",
117+
ref: "ref",
118+
} satisfies RemoteFileAddress);
119+
120+
t.deepEqual(parseRemoteFileAddress(env, "owner/repo@ ref:path"), {
121+
owner: "owner",
122+
repo: "repo",
123+
path: "path",
124+
ref: "ref",
125+
} satisfies RemoteFileAddress);
126+
127+
t.deepEqual(parseRemoteFileAddress(env, "owner/repo@ref :path"), {
128+
owner: "owner",
129+
repo: "repo",
130+
path: "path",
131+
ref: "ref",
132+
} satisfies RemoteFileAddress);
133+
134+
t.deepEqual(parseRemoteFileAddress(env, "owner/repo@ref: path"), {
135+
owner: "owner",
136+
repo: "repo",
137+
path: "path",
138+
ref: "ref",
139+
} satisfies RemoteFileAddress);
140+
141+
t.deepEqual(
142+
parseRemoteFileAddress(env, "owner/repo@ref/feature:path/to/codeql.yml"),
143+
{
144+
owner: "owner",
145+
repo: "repo",
146+
path: "path/to/codeql.yml",
147+
ref: "ref/feature",
148+
} satisfies RemoteFileAddress,
149+
);
150+
151+
t.deepEqual(
152+
parseRemoteFileAddress(
153+
env,
154+
" owner/repo@ref/feature:path/to/codeql.yml ",
155+
),
156+
{
157+
owner: "owner",
158+
repo: "repo",
159+
path: "path/to/codeql.yml",
160+
ref: "ref/feature",
161+
} satisfies RemoteFileAddress,
162+
);
89163
});
90164

91165
test("parseRemoteFileAddress accepts remote address without an owner", async (t) => {
@@ -96,13 +170,27 @@ test("parseRemoteFileAddress accepts remote address without an owner", async (t)
96170
.withArgs(ActionsEnvVars.GITHUB_REPOSITORY)
97171
.returns(`${owner}/current-repo`);
98172

173+
t.deepEqual(parseRemoteFileAddress(env, "repo@ref:path.yml"), {
174+
owner,
175+
repo: "repo",
176+
path: "path.yml",
177+
ref: "ref",
178+
} satisfies RemoteFileAddress);
179+
99180
t.deepEqual(parseRemoteFileAddress(env, "repo@ref"), {
100181
owner,
101182
repo: "repo",
102183
path: DEFAULT_CONFIG_FILE_NAME,
103184
ref: "ref",
104185
} satisfies RemoteFileAddress);
105186

187+
t.deepEqual(parseRemoteFileAddress(env, "repo:path.yml"), {
188+
owner,
189+
repo: "repo",
190+
path: "path.yml",
191+
ref: DEFAULT_CONFIG_FILE_REF,
192+
} satisfies RemoteFileAddress);
193+
106194
t.deepEqual(parseRemoteFileAddress(env, "repo"), {
107195
owner,
108196
repo: "repo",
@@ -142,14 +230,7 @@ test("parseRemoteFileAddress accepts remote address without a path", async (t) =
142230
test("parseRemoteFileAddress accepts remote address without a ref", async (t) => {
143231
const env = getTestEnv();
144232

145-
t.deepEqual(parseRemoteFileAddress(env, "owner/repo/path"), {
146-
owner: "owner",
147-
repo: "repo",
148-
path: "path",
149-
ref: DEFAULT_CONFIG_FILE_REF,
150-
} satisfies RemoteFileAddress);
151-
152-
t.deepEqual(parseRemoteFileAddress(env, "owner/repo/path@"), {
233+
t.deepEqual(parseRemoteFileAddress(env, "owner/repo:path"), {
153234
owner: "owner",
154235
repo: "repo",
155236
path: "path",
@@ -171,10 +252,43 @@ test("parseRemoteFileAddress rejects invalid values", async (t) => {
171252
t.throws(() => parseRemoteFileAddress(env, "repo//absolute"), {
172253
instanceOf: ConfigurationError,
173254
});
255+
t.throws(() => parseRemoteFileAddress(env, "repo:/absolute"), {
256+
instanceOf: ConfigurationError,
257+
});
174258
t.throws(() => parseRemoteFileAddress(env, "/repo@ref"), {
175259
instanceOf: ConfigurationError,
176260
});
177261
t.throws(() => parseRemoteFileAddress(env, " /repo@ref"), {
178262
instanceOf: ConfigurationError,
179263
});
264+
t.throws(() => parseRemoteFileAddress(env, "repo@"), {
265+
instanceOf: ConfigurationError,
266+
});
267+
t.throws(() => parseRemoteFileAddress(env, "repo:"), {
268+
instanceOf: ConfigurationError,
269+
});
270+
t.throws(() => parseRemoteFileAddress(env, "repo/"), {
271+
instanceOf: ConfigurationError,
272+
});
273+
t.throws(() => parseRemoteFileAddress(env, "/repo"), {
274+
instanceOf: ConfigurationError,
275+
});
276+
t.throws(() => parseRemoteFileAddress(env, ":path"), {
277+
instanceOf: ConfigurationError,
278+
});
279+
t.throws(() => parseRemoteFileAddress(env, "@ref"), {
280+
instanceOf: ConfigurationError,
281+
});
282+
t.throws(() => parseRemoteFileAddress(env, "@ref:path"), {
283+
instanceOf: ConfigurationError,
284+
});
285+
t.throws(() => parseRemoteFileAddress(env, "owner/@ref:path"), {
286+
instanceOf: ConfigurationError,
287+
});
288+
t.throws(() => parseRemoteFileAddress(env, "owner/@ref"), {
289+
instanceOf: ConfigurationError,
290+
});
291+
t.throws(() => parseRemoteFileAddress(env, "owner/:path"), {
292+
instanceOf: ConfigurationError,
293+
});
180294
});

src/config/remote-file.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ActionsEnvVars } from "../actions-util";
22
import { Env } from "../environment";
33
import * as errorMessages from "../error-messages";
4-
import { ConfigurationError } from "../util";
4+
import { ConfigurationError, Failure, Result, Success } from "../util";
55

66
/** Represents remote file addresses. */
77
export interface RemoteFileAddress {
@@ -37,6 +37,38 @@ function getDefaultOwner(env: Env): string {
3737
return nwoParts[0].trim();
3838
}
3939

40+
/**
41+
* The old remote address format that's always been supported for the `config-file` input.
42+
* All the components are required. Unchanged from the previous implementation.
43+
*/
44+
const OLD_REMOTE_ADDRESS_FORMAT = new RegExp(
45+
"(?<owner>[^/]+)/(?<repo>[^/]+)/(?<path>[^@]+)@(?<ref>.*)",
46+
);
47+
48+
/**
49+
* Attempts to parse `input` as a `RemoteFileAddress` using the old format.
50+
*
51+
* @param input The input to try and parse.
52+
* @returns A `RemoteFileAddress` value if successful or `undefined` otherwise.
53+
*/
54+
function parseOldRemoteFileAddress(
55+
input: string,
56+
): Result<RemoteFileAddress, undefined> {
57+
const pieces = OLD_REMOTE_ADDRESS_FORMAT.exec(input);
58+
59+
// 5 = 4 groups + the whole expression
60+
if (pieces?.groups === undefined || pieces.length < 5) {
61+
return new Failure(undefined);
62+
}
63+
64+
return new Success({
65+
owner: pieces.groups.owner.trim(),
66+
repo: pieces.groups.repo.trim(),
67+
path: pieces.groups.path.trim(),
68+
ref: pieces.groups.ref.trim(),
69+
});
70+
}
71+
4072
/**
4173
* Attempts to parse `configFile` into an array of `RemoteFileAddress` components.
4274
*
@@ -49,16 +81,27 @@ export function parseRemoteFileAddress(
4981
env: Env,
5082
configFile: string,
5183
): RemoteFileAddress {
84+
// Try to parse the input using the old format. If successful, return the
85+
// resulting `RemoteFileAddress`. Otherwise, continue using the new format.
86+
const oldFormatAddressResult = parseOldRemoteFileAddress(configFile);
87+
88+
if (oldFormatAddressResult.isSuccess()) {
89+
return oldFormatAddressResult.value;
90+
}
91+
5292
// retrieve the various parts of the config location, and ensure they're present
5393
const format = new RegExp(
54-
"^((?<owner>[^/]+)/)?(?<repo>[^/@]+)(/(?<path>[^@]+))?(@(?<ref>.*))?$",
94+
"^((?<owner>[^:@/]+)/)?(?<repo>[^:@/]+)(@(?<ref>[^:]+))?(:(?<path>.+))?$",
5595
);
5696
const pieces = format.exec(configFile.trim());
5797

5898
const repo: string | undefined = pieces?.groups?.repo?.trim();
5999

60100
// Check that the regular expression matched and that we have at least the repo name.
61101
if (!pieces?.groups || !repo || repo.length === 0) {
102+
// Neither the old format nor the new format worked. Throw an error that
103+
// explains the format we accept. We only mention the new format, since that's
104+
// what we want to be used going forward.
62105
throw new ConfigurationError(
63106
errorMessages.getConfigFileRepoFormatInvalidMessage(configFile),
64107
);

src/error-messages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function getConfigFileRepoFormatInvalidMessage(
3434
configFile: string,
3535
): string {
3636
let error = `The configuration file "${configFile}" is not a supported remote file reference.`;
37-
error += " Expected format [<owner>/]<repository>[/<file-path>][@<ref>]";
37+
error += " Expected format [<owner>/]<repository>[@<ref>][:<file-path>]";
3838

3939
return error;
4040
}

0 commit comments

Comments
 (0)