Skip to content

Commit 9698daf

Browse files
Copilotfgreinacher
andcommitted
Fix template expansion for asset globbing patterns with wildcards
Template variables in asset paths like "release/my-project-v${nextRelease.version}*.zip" are now expanded before globbing, allowing wildcard patterns to work correctly. Only applies template expansion when template variables are detected to avoid unnecessary processing. Co-authored-by: fgreinacher <[email protected]>
1 parent 7014068 commit 9698daf

File tree

4 files changed

+112
-9
lines changed

4 files changed

+112
-9
lines changed

lib/publish.js

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,26 +56,66 @@ export default async (pluginConfig, context) => {
5656
debug("milestones: %o", milestones);
5757

5858
if (assets && assets.length > 0) {
59+
// Apply template expansion to assets BEFORE globbing, but only when templates are present
60+
const needsTemplateExpansion = (str) => str && typeof str === 'string' && str.includes('${');
61+
62+
const templatedAssets = assets.map((asset) => {
63+
if (isPlainObject(asset)) {
64+
const templatedAsset = { ...asset };
65+
if (asset.path && needsTemplateExpansion(asset.path)) {
66+
templatedAsset.path = template(asset.path)(context);
67+
}
68+
if (asset.url && needsTemplateExpansion(asset.url)) {
69+
templatedAsset.url = template(asset.url)(context);
70+
}
71+
if (asset.label && needsTemplateExpansion(asset.label)) {
72+
templatedAsset.label = template(asset.label)(context);
73+
}
74+
if (asset.type && needsTemplateExpansion(asset.type)) {
75+
templatedAsset.type = template(asset.type)(context);
76+
}
77+
if (asset.filepath && needsTemplateExpansion(asset.filepath)) {
78+
templatedAsset.filepath = template(asset.filepath)(context);
79+
}
80+
if (asset.target && needsTemplateExpansion(asset.target)) {
81+
templatedAsset.target = template(asset.target)(context);
82+
}
83+
if (asset.status && needsTemplateExpansion(asset.status)) {
84+
templatedAsset.status = template(asset.status)(context);
85+
}
86+
return templatedAsset;
87+
} else if (Array.isArray(asset)) {
88+
// Handle array of glob patterns - only apply templates if needed
89+
return asset.map(pattern => needsTemplateExpansion(pattern) ? template(pattern)(context) : pattern);
90+
} else {
91+
// String asset path - only apply template if needed
92+
return needsTemplateExpansion(asset) ? template(asset)(context) : asset;
93+
}
94+
});
95+
5996
// Skip glob if url is provided
60-
const urlAssets = assets.filter((asset) => asset.url);
97+
const urlAssets = templatedAssets.filter((asset) => asset.url);
6198
debug("url assets: %o", urlAssets);
6299
const globbedAssets = await getAssets(
63100
context,
64-
assets.filter((asset) => !asset.url)
101+
templatedAssets.filter((asset) => !asset.url)
65102
);
66103
debug("globbed assets: %o", globbedAssets);
67104
const allAssets = [...urlAssets, ...globbedAssets];
68105
debug("all assets: %o", allAssets);
69106

70107
await Promise.all(
71108
allAssets.map(async (asset) => {
72-
const path = template((isPlainObject(asset) ? asset : { path: asset }).path)(context);
73-
const _url = asset.url ? template(asset.url)(context) : undefined;
74-
const label = asset.label ? template(asset.label)(context) : undefined;
75-
const type = asset.type ? template(asset.type)(context) : undefined;
76-
const filepath = asset.filepath ? template(asset.filepath)(context) : undefined;
77-
const target = asset.target ? template(asset.target)(context) : undefined;
78-
const status = asset.status ? template(asset.status)(context) : undefined;
109+
// Templates may have been expanded before globbing, but we need to handle remaining cases
110+
const needsTemplateExpansion = (str) => str && typeof str === 'string' && str.includes('${');
111+
112+
const path = isPlainObject(asset) ? asset.path : asset;
113+
const _url = asset.url ? (needsTemplateExpansion(asset.url) ? template(asset.url)(context) : asset.url) : undefined;
114+
const label = asset.label ? (needsTemplateExpansion(asset.label) ? template(asset.label)(context) : asset.label) : undefined;
115+
const type = asset.type ? (needsTemplateExpansion(asset.type) ? template(asset.type)(context) : asset.type) : undefined;
116+
const filepath = asset.filepath ? (needsTemplateExpansion(asset.filepath) ? template(asset.filepath)(context) : asset.filepath) : undefined;
117+
const target = asset.target ? (needsTemplateExpansion(asset.target) ? template(asset.target)(context) : asset.target) : undefined;
118+
const status = asset.status ? (needsTemplateExpansion(asset.status) ? template(asset.status)(context) : asset.status) : undefined;
79119

80120
if (_url) {
81121
assetsList.push({ label, rawUrl: _url, type, filepath });
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dummy zip content for testing alpha
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dummy zip content for testing

test/publish.test.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,3 +712,64 @@ test.serial("Publish a release with error response", async (t) => {
712712
t.is(error.message, `Response code 499 (Something went wrong)`);
713713
t.true(gitlab.isDone());
714714
});
715+
716+
test.serial("Publish a release with templated wildcard path", async (t) => {
717+
const cwd = "test/fixtures/wildcard-test";
718+
const owner = "test_user";
719+
const repo = "test_repo";
720+
const env = { GITLAB_TOKEN: "gitlab_token" };
721+
const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body", version: "1.0.0" };
722+
const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
723+
const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
724+
const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
725+
726+
// This pattern should match both "my-project-v1.0.0.zip" and "my-project-v1.0.0-alpha.zip"
727+
const assets = ["release/my-project-v${nextRelease.version}*.zip"];
728+
729+
const uploaded1 = {
730+
url: "/uploads/my-project-v1.0.0.zip",
731+
alt: "my-project-v1.0.0.zip",
732+
full_path: "/-/project/4/66dbcd21ec5d24ed6ea225176098d52b/my-project-v1.0.0.zip",
733+
};
734+
const uploaded2 = {
735+
url: "/uploads/my-project-v1.0.0-alpha.zip",
736+
alt: "my-project-v1.0.0-alpha.zip",
737+
full_path: "/-/project/4/66dbcd21ec5d24ed6ea225176098d52b/my-project-v1.0.0-alpha.zip",
738+
};
739+
740+
const gitlab = authenticate(env)
741+
.post(`/projects/${encodedProjectPath}/releases`, {
742+
tag_name: nextRelease.gitTag,
743+
description: nextRelease.notes,
744+
assets: {
745+
links: [
746+
{
747+
name: "my-project-v1.0.0-alpha.zip",
748+
url: `https://gitlab.com${uploaded2.full_path}`,
749+
},
750+
{
751+
name: "my-project-v1.0.0.zip",
752+
url: `https://gitlab.com${uploaded1.full_path}`,
753+
},
754+
],
755+
},
756+
})
757+
.reply(200);
758+
const gitlabUpload1 = authenticate(env)
759+
.post(`/projects/${encodedProjectPath}/uploads`, /Content-Disposition.*my-project-v1\.0\.0\.zip/g)
760+
.reply(200, uploaded1);
761+
const gitlabUpload2 = authenticate(env)
762+
.post(`/projects/${encodedProjectPath}/uploads`, /Content-Disposition.*my-project-v1\.0\.0-alpha\.zip/g)
763+
.reply(200, uploaded2);
764+
765+
const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger });
766+
767+
t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`);
768+
// Should upload both files that match the wildcard pattern (in alphabetical order)
769+
t.deepEqual(t.context.log.args[0], ["Uploaded file: %s", `https://gitlab.com${uploaded2.full_path}`]);
770+
t.deepEqual(t.context.log.args[1], ["Uploaded file: %s", `https://gitlab.com${uploaded1.full_path}`]);
771+
t.deepEqual(t.context.log.args[2], ["Published GitLab release: %s", nextRelease.gitTag]);
772+
t.true(gitlab.isDone());
773+
t.true(gitlabUpload1.isDone());
774+
t.true(gitlabUpload2.isDone());
775+
});

0 commit comments

Comments
 (0)