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(generics): allow upload of build assets as generic packages #174 #343

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Expand Up @@ -35,7 +35,7 @@ The plugin can be configured in the [**semantic-release** configuration file](ht
"gitlabUrl": "https://custom.gitlab.com",
"assets": [
{ "path": "dist/asset.min.css", "label": "CSS distribution" },
{ "path": "dist/asset.min.js", "label": "JS distribution" },
{ "path": "dist/asset.min.js", "label": "JS distribution", "type": "generic_package" },
{ "path": "dist/asset.min.js", "label": "v${nextRelease.version}.js" },
{ "url": "https://gitlab.com/gitlab-org/gitlab/-/blob/master/README.md" }
]
Expand Down Expand Up @@ -106,6 +106,8 @@ Can be a [glob](https://github.com/isaacs/node-glob#glob-primer) or and `Array`
| `label` | Short description of the file displayed on the GitLab release. Ignored if `path` matches more than one file. Supports [Lodash templating](https://lodash.com/docs#template). | File name extracted from the `path`. |
| `type` | Asset type displayed on the GitLab release. Can be `runbook`, `package`, `image` and `other` (see official documents on [release assets](https://docs.gitlab.com/ee/user/project/releases/#release-assets)). Supports [Lodash templating](https://lodash.com/docs#template). | `other` |
| `filepath` | A filepath for creating a permalink pointing to the asset (requires GitLab 12.9+, see official documents on [permanent links](https://docs.gitlab.com/ee/user/project/releases/#permanent-links-to-release-assets)). Ignored if `path` matches more than one file. Supports [Lodash templating](https://lodash.com/docs#template). | - |
| `target` | Controls where the file is uploaded to. Can be set to `project_upload` for storing the file as [project upload](https://docs.gitlab.com/ee/api/projects.html#upload-a-file) or `generic_package` for storing the file as [generic package](https://docs.gitlab.com/ee/user/packages/generic_packages/). | `project_upload` |
| `status` | This is only applied, if `target` is set to `generic_package`. The generic package status. Can be `default` and `hidden` (see official documents on [generic packages](https://docs.gitlab.com/ee/user/packages/generic_packages/)). | `default` |

Each entry in the `assets` `Array` is globbed individually. A [glob](https://github.com/isaacs/node-glob#glob-primer)
can be a `String` (`"dist/**/*.js"` or `"dist/mylib.js"`) or an `Array` of `String`s that will be globbed together
Expand Down
58 changes: 46 additions & 12 deletions lib/publish.js
Expand Up @@ -50,6 +50,9 @@ export default async (pluginConfig, context) => {
const label = asset.label ? template(asset.label)(context) : undefined;
const type = asset.type ? template(asset.type)(context) : undefined;
const filepath = asset.filepath ? template(asset.filepath)(context) : undefined;
const target = asset.target ? template(asset.target)(context) : undefined;
const status = asset.status ? template(asset.status)(context) : undefined;

if (_url) {
assetsList.push({ label, rawUrl: _url, type, filepath });
debug("use link from release setting: %s", _url);
Expand All @@ -74,28 +77,59 @@ export default async (pluginConfig, context) => {
debug("file label: %o", label);
debug("file type: %o", type);
debug("file filepath: %o", filepath);
debug("file target: %o", target);
debug("file status: %o", status);

// Uploaded assets to the project
const form = new FormData();
form.append("file", createReadStream(file));

const uploadEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedRepoId}/uploads`);
let uploadEndpoint;
let response;

debug("POST-ing the file %s to %s", file, uploadEndpoint);
if (target === "generic_package") {
// Upload generic packages
const encodedLabel = encodeURIComponent(label);
uploadEndpoint = urlJoin(
gitlabApiUrl,
`/projects/${encodedRepoId}/packages/generic/release/${encodedGitTag}/${encodedLabel}?${
status ? `status=${status}&` : ""
}select=package_file`
);

let response;
try {
response = await got.post(uploadEndpoint, { ...apiOptions, ...proxy, body: form }).json();
} catch (error) {
logger.error("An error occurred while uploading %s to the GitLab project uploads API:\n%O", file, error);
throw error;
}
debug("PUT-ing the file %s to %s", file, uploadEndpoint);

try {
response = await got.put(uploadEndpoint, { ...apiOptions, body: form }).json();
} catch (error) {
logger.error("An error occurred while uploading %s to the GitLab generics package API:\n%O", file, error);
throw error;
}

const { url, alt } = response;
const { url } = response.file;

assetsList.push({ label, alt, url, type, filepath });
assetsList.push({ label, alt: "release", url, type: "package", filepath });

logger.log("Uploaded file: %s", url);
logger.log("Uploaded file: %s", url);
} else {
// Handle normal assets
uploadEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedRepoId}/uploads`);

debug("POST-ing the file %s to %s", file, uploadEndpoint);

try {
response = await got.post(uploadEndpoint, { ...apiOptions, ...proxy, body: form }).json();
} catch (error) {
logger.error("An error occurred while uploading %s to the GitLab project uploads API:\n%O", file, error);
throw error;
}

const { url, alt } = response;

assetsList.push({ label, alt, url, type, filepath });

logger.log("Uploaded file: %s", url);
}
}
})
);
Expand Down
44 changes: 44 additions & 0 deletions test/publish.test.js
Expand Up @@ -83,6 +83,50 @@ test.serial("Publish a release with assets", async (t) => {
t.true(gitlab.isDone());
});

test.serial("Publish a release with generics", async (t) => {
const cwd = "test/fixtures/files";
const owner = "test_user";
const repo = "test_repo";
const env = { GITLAB_TOKEN: "gitlab_token" };
const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
const uploaded = { file: { url: "/uploads/file.css" } };
const generic = { path: "file.css", label: "Style package", target: "generic_package", status: "hidden" };
const assets = [generic];
const encodedLabel = encodeURIComponent(generic.label);
const gitlab = authenticate(env)
.post(`/projects/${encodedRepoId}/releases`, {
tag_name: nextRelease.gitTag,
description: nextRelease.notes,
assets: {
links: [
{
name: "Style package",
url: `https://gitlab.com/${owner}/${repo}${uploaded.file.url}`,
link_type: "package",
},
],
},
})
.reply(200);
const gitlabUpload = authenticate(env)
.put(
`/projects/${encodedRepoId}/packages/generic/release/${encodedGitTag}/${encodedLabel}?status=${generic.status}&select=package_file`,
/filename="file.css"/gm
)
.reply(200, uploaded);

const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger });

t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`);
t.deepEqual(t.context.log.args[0], ["Uploaded file: %s", uploaded.file.url]);
t.deepEqual(t.context.log.args[1], ["Published GitLab release: %s", nextRelease.gitTag]);
t.true(gitlabUpload.isDone());
t.true(gitlab.isDone());
});

test.serial("Publish a release with asset type and permalink", async (t) => {
const cwd = "test/fixtures/files";
const owner = "test_user";
Expand Down