Skip to content

Commit 1f6e4ad

Browse files
committed
fix: support wildcard file paths in project includes
The glob expanded to several files but cp targeted a literal destination path, and the later load used that same literal path instead of the matches.
1 parent 41eea11 commit 1f6e4ad

3 files changed

Lines changed: 45 additions & 8 deletions

File tree

src/parser-includes.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import path from "node:path";
1111
import prettyHrtime from "pretty-hrtime";
1212
import semver from "semver";
1313
import {RE2JS} from "re2js";
14+
import {globbySync} from "globby";
1415

1516
type ParserIncludesInitOptions = {
1617
argv: Argv;
@@ -147,13 +148,16 @@ export class ParserIncludes {
147148
} else if (value["project"]) {
148149
for (const fileValue of Array.isArray(value["file"]) ? value["file"] : [value["file"]]) {
149150
const mergedInputs = {...(value.inputs ?? {}), ...globalInputs};
150-
const fileDoc = await Parser.loadYaml(
151-
`${cwd}/${stateDir}/includes/${gitData.remote.host}/${value["project"]}/${value["ref"] || "HEAD"}/${fileValue}`
152-
, {inputs: mergedInputs}
153-
, expandVariables, writeStreams);
154-
// Expand local includes inside a "project"-like include
155-
fileDoc["include"] = this.expandInnerLocalIncludes(fileDoc["include"], value["project"], value["ref"], opts);
156-
includeDatas = includeDatas.concat(await this.init(fileDoc, opts));
151+
const includeDir = `${cwd}/${stateDir}/includes/${gitData.remote.host}/${value["project"]}/${value["ref"] || "HEAD"}`;
152+
const normalizedFile = fileValue.replace(/^\/+/, "");
153+
const matches = globbySync(normalizedFile, {cwd: includeDir, absolute: true}).sort();
154+
const filePaths = matches.length > 0 ? matches : [`${includeDir}/${normalizedFile}`];
155+
for (const filePath of filePaths) {
156+
const fileDoc = await Parser.loadYaml(filePath, {inputs: mergedInputs}, expandVariables, writeStreams);
157+
// Expand local includes inside a "project"-like include
158+
fileDoc["include"] = this.expandInnerLocalIncludes(fileDoc["include"], value["project"], value["ref"], opts);
159+
includeDatas = includeDatas.concat(await this.init(fileDoc, opts));
160+
}
157161
}
158162
} else if (value["component"]) {
159163
const component = componentParseCache.get(index);
@@ -387,7 +391,7 @@ export class ParserIncludes {
387391
`git sparse-checkout set --no-cone ${normalizedFile}`,
388392
isCommitSha ? "git checkout FETCH_HEAD" : "git checkout",
389393
`cd ${cwd}/${stateDir}`,
390-
`cp ${tmpDir}/${normalizedFile} ${cwd}/${target}/${normalizedFile}`,
394+
`cp ${tmpDir}/${normalizedFile} ${path.dirname(`${cwd}/${target}/${normalizedFile}`)}/`,
391395
], cwd);
392396
} else {
393397
await fs.mkdirp(`${cwd}/${target}`);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
# templates/[bt]*.yml matches both templates/build.yml and templates/test.yml,
3+
# so the glob expands to more than one file.
4+
include:
5+
- project: components/go
6+
ref: main
7+
file: templates/[bt]*.yml
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {WriteStreamsMock} from "../../../src/write-streams.js";
2+
import {handler} from "../../../src/handler.js";
3+
import {initSpawnSpy} from "../../mocks/utils.mock.js";
4+
import {WhenStatics} from "../../mocks/when-statics.js";
5+
6+
beforeAll(() => {
7+
initSpawnSpy([...WhenStatics.all, WhenStatics.mockGitRemoteHttp]);
8+
});
9+
10+
test.concurrent("include:project with a wildcard file path includes every match", async () => {
11+
const writeStreams = new WriteStreamsMock();
12+
13+
await handler({
14+
cwd: "tests/test-cases/include-project-file-wildcard",
15+
noColor: true,
16+
list: true,
17+
stateDir: ".gitlab-ci-local-include-project-file-wildcard",
18+
}, writeStreams);
19+
20+
const jobNames = writeStreams.stdoutLines
21+
.filter(l => /^(build|test)\s/.test(l))
22+
.map(l => l.split(/\s+/)[0])
23+
.sort();
24+
25+
expect(jobNames).toEqual(["build", "test"]);
26+
});

0 commit comments

Comments
 (0)