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(publisher-gcs): add Google Cloud Storage publisher #2100

Merged
merged 9 commits into from
Nov 16, 2023
Prev Previous commit
Next Next commit
fix(publisher-gcs): ported changes from publisher-s3
  • Loading branch information
mahnunchik committed Sep 22, 2023
commit e56b9f07549f17a62bc64492a38d4ae530213042
3 changes: 2 additions & 1 deletion packages/publisher/gcs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"@electron-forge/publisher-base": "6.4.2",
"@electron-forge/shared-types": "6.4.2",
"@google-cloud/storage": "^7.1.0",
"debug": "^4.3.1"
"debug": "^4.3.1",
"google-auth-library": "^9.0.0"
mahnunchik marked this conversation as resolved.
Show resolved Hide resolved
},
"publishConfig": {
"access": "public"
Expand Down
14 changes: 12 additions & 2 deletions packages/publisher/gcs/src/Config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PredefinedAcl } from '@google-cloud/storage';

export interface PublisherGCSConfig {
/**
* The path to the file that is either:
Expand Down Expand Up @@ -36,12 +38,20 @@ export interface PublisherGCSConfig {
* Defaults to the application `version` specified in the app's `package.json`.
*/
folder?: string;
/**
* Apply a predefined set of access controls to this object.
*/
predefinedAcl?: PredefinedAcl;
/**
* Whether to make uploaded artifacts public to the internet.
*
* Defaults to `false`.
* Alias for config.predefinedAcl = 'publicRead'.
*/
public?: boolean;
/**
* Whether to make uploaded artifacts private.
* Alias for config.predefinedAcl = 'private'.
*/
private?: boolean;
/**
* Custom function to provide the key to upload a given file to
*/
Expand Down
52 changes: 30 additions & 22 deletions packages/publisher/gcs/src/PublisherGCS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'path';
import { PublisherBase, PublisherOptions } from '@electron-forge/publisher-base';
import { Storage } from '@google-cloud/storage';
import debug from 'debug';
import { CredentialBody } from 'google-auth-library';

import { PublisherGCSConfig } from './Config';

Expand All @@ -18,45 +19,37 @@ type GCSArtifact = {
export default class PublisherGCS extends PublisherBase<PublisherGCSConfig> {
name = 'gcs';

private GCSKeySafe = (key: string) => {
return key.replace(/@/g, '_').replace(/\//g, '_');
};

async publish({ makeResults, setStatusLine }: PublisherOptions): Promise<void> {
const { config } = this;
const artifacts: GCSArtifact[] = [];

if (!config.bucket) {
throw new Error('In order to publish to Google Cloud Storage you must set the "gcs.bucket" property in your Forge config.');
if (!this.config.bucket) {
throw new Error('In order to publish to Google Cloud Storage you must set the "bucket" property in your Forge config.');
}

for (const makeResult of makeResults) {
artifacts.push(
...makeResult.artifacts.map((artifact) => ({
path: artifact,
keyPrefix: config.folder || makeResult.packageJSON.version,
keyPrefix: this.config.folder || this.GCSKeySafe(makeResult.packageJSON.name),
platform: makeResult.platform,
arch: makeResult.arch,
}))
);
}

const clientEmail = config.clientEmail || process.env.GOOGLE_CLOUD_CLIENT_EMAIL;
const privateKey = config.privateKey || process.env.GOOGLE_CLOUD_PRIVATE_KEY;

let credentials;
if (clientEmail && privateKey) {
credentials = {
client_email: clientEmail,
private_key: privateKey,
};
}

const storage = new Storage({
erickzhao marked this conversation as resolved.
Show resolved Hide resolved
keyFilename: config.keyFilename || process.env.GOOGLE_APPLICATION_CREDENTIALS,
credentials,
projectId: config.projectId || process.env.GOOGLE_CLOUD_PROJECT,
keyFilename: this.config.keyFilename,
credentials: this.generateCredentials(),
projectId: this.config.projectId,
});

const bucket = storage.bucket(config.bucket);
const bucket = storage.bucket(this.config.bucket);

d('creating Google Cloud Storage client with options:', config);
d('creating Google Cloud Storage client with options:', this.config);

let uploaded = 0;
const updateStatusLine = () => setStatusLine(`Uploading distributable (${uploaded}/${artifacts.length})`);
Expand All @@ -69,7 +62,9 @@ export default class PublisherGCS extends PublisherBase<PublisherGCSConfig> {
await bucket.upload(artifact.path, {
gzip: true,
erickzhao marked this conversation as resolved.
Show resolved Hide resolved
destination: this.keyForArtifact(artifact),
public: config.public,
predefinedAcl: this.config.predefinedAcl,
public: this.config.public,
private: this.config.private,
});

uploaded += 1;
Expand All @@ -83,7 +78,20 @@ export default class PublisherGCS extends PublisherBase<PublisherGCSConfig> {
return this.config.keyResolver(path.basename(artifact.path), artifact.platform, artifact.arch);
}

return `${artifact.keyPrefix}/${path.basename(artifact.path)}`;
return `${artifact.keyPrefix}/${artifact.platform}/${artifact.arch}/${path.basename(artifact.path)}`;
}

generateCredentials(): CredentialBody | undefined {
const clientEmail = this.config.clientEmail;
const privateKey = this.config.privateKey;

if (clientEmail && privateKey) {
return {
client_email: clientEmail,
private_key: privateKey,
};
}
return undefined;
}
}

Expand Down