Skip to content
Permalink
Browse files

feat(publisher-bitbucket): initial publish publisher-bitbucket (#571)

* feat(publisher-bitbucket): initial publish of @electron-forge/publisher-bitbucket

ISSUES CLOSED: #487

* feat(publisher-bitbucket): adds \`replaceExistingFiles\` config for publisher-bitbucket
  • Loading branch information
lukebatchelor authored and MarshallOfSound committed Sep 12, 2018
1 parent ca46ea5 commit 82e8c85ebbaeccfd428077bd2ade60b65c775998
@@ -0,0 +1,24 @@
{
"name": "@electron-forge/publisher-bitbucket",
"version": "6.0.0-beta.28",
"description": "Bitbucket publisher for Electron Forge",
"repository": "https://github.com/electron-userland/electron-forge",
"author": "Luke Batchelor",
"license": "MIT",
"main": "dist/PublisherBitbucket.js",
"typings": "dist/PublisherBitbucket.d.ts",
"devDependencies": {
"chai": "^4.0.0",
"mocha": "^5.0.0"
},
"engines": {
"node": ">= 6.0"
},
"dependencies": {
"@electron-forge/async-ora": "6.0.0-beta.28",
"@electron-forge/publisher-base": "6.0.0-beta.28",
"form-data": "^2.1.4",
"fs-extra": "^7.0.0",
"node-fetch": "^2.0.0"
}
}
@@ -0,0 +1,44 @@
export interface BitbucketRepository {
/**
* The name of your repository
*/
name: string;
/**
* The owner of your repository, this is either your username or the name of
* the organization that owns the repository.
*/
owner: string;
}

export interface BitbucketAuth {
/**
* The username to use when uploading.
*
* You can set the BITBUCKET_USERNAME environment variable if you don't want to hard
* code this into your config.
*/
username?: string;
/**
* An authorization token with permission to upload downloads to this
* repository.
*
* You can set the BITBUCKET_APP_PASSWORD environment variable if you don't want to hard
* code this into your config.
*/
appPassword?: string;
}

export interface PublisherBitbucketConfig {
/**
* Details that identify your repository (name and owner)
*/
repository: BitbucketRepository;
/**
* User details for uploading releases
*/
auth: BitbucketAuth;
/**
* If true, will replace an existing files of the same name (will throw an error otherwise).
*/
replaceExistingFiles?: boolean;
}
@@ -0,0 +1,77 @@
import PublisherBase, { PublisherOptions } from '@electron-forge/publisher-base';
import { asyncOra } from '@electron-forge/async-ora';

import fetch from 'node-fetch';
import FormData from 'form-data';
import fs from 'fs-extra';
import path from 'path';

import { PublisherBitbucketConfig } from './Config';

export default class PublisherBitbucket extends PublisherBase<PublisherBitbucketConfig> {
name = 'bitbucket';

async publish({ makeResults }: PublisherOptions) {
const { config } = this;
const hasRepositoryConfig = config.repository && typeof config.repository;
const replaceExistingFiles = Boolean(config.replaceExistingFiles);
const appPassword = process.env.BITBUCKET_APP_PASSWORD;
const username = process.env.BITBUCKET_USERNAME;
const auth = Object.assign({}, { appPassword, username }, config.auth || {});
const apiUrl = `https://api.bitbucket.org/2.0/repositories/${config.repository.owner}/${config.repository.name}/downloads`;
const encodedUserAndPass = Buffer.from(`${auth.username}:${auth.appPassword}`).toString('base64');

if (!(hasRepositoryConfig && config.repository.owner && config.repository.name)) {
throw 'In order to publish to Bitbucket you must set the "repository.owner" and "repository.name" properties in your forge config. See the docs for more info'; // eslint-disable-line max-len
}

if (!auth.appPassword || !auth.username) {
throw 'In order to publish to Bitbucket provide credentials, either through "auth.appPassword" and "auth.username" properties in your forge config or using BITBUCKET_APP_PASSWORD and BITBUCKET_USERNAME environment variables'; // eslint-disable-line max-len
}

for (const [index, makeResult] of makeResults.entries()) {
const data = new FormData();

for (const artifactPath of makeResult.artifacts) {
data.append('files', fs.createReadStream(artifactPath));
}

// If we are not supposed to override an existing version, we'll check check if any of the files exist first
if (!replaceExistingFiles) {
await asyncOra('Checking if artifacts have been published previously', async () => {
for (const artifactPath of makeResult.artifacts) {
const fileName = path.basename(artifactPath);

const response = await fetch(`${apiUrl}/${fileName}`, {
headers: {
Authorization: `Basic ${encodedUserAndPass}`,
},
method: 'HEAD',
// We set redirect to 'manual' so that we get the 302 redirects if the file already exists
redirect: 'manual',
});

if (response.status === 302) {
throw `Unable to publish "${fileName}" as it has been published previously. Use the "replaceExistingFiles" property in your forge config to override this.`; // eslint-disable-line max-len
}
}
});
}

await asyncOra(`Uploading result (${index + 1}/${makeResults.length})`, async () => {
const response = await fetch(apiUrl, {
headers: {
Authorization: `Basic ${encodedUserAndPass}`,
},
method: 'POST',
body: data,
});

// We will get a 200 on the inital upload and a 201 if publishing over the same version
if (response.status !== 200 && response.status !== 201) {
throw `Unexpected response code from Bitbucket: ${response.status} ${response.statusText}\n\nBody:\n${await response.text()}`;
}
});
}
}
}

0 comments on commit 82e8c85

Please sign in to comment.
You can’t perform that action at this time.