Initial commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
import { Arch } from "builder-util";
|
||||
import { BitbucketOptions } from "builder-util-runtime/out/publishOptions";
|
||||
import { ClientRequest } from "http";
|
||||
import { PublishContext } from "./";
|
||||
import { HttpPublisher } from "./httpPublisher";
|
||||
export declare class BitbucketPublisher extends HttpPublisher {
|
||||
readonly providerName = "bitbucket";
|
||||
readonly hostname = "api.bitbucket.org";
|
||||
private readonly info;
|
||||
private readonly auth;
|
||||
private readonly basePath;
|
||||
constructor(context: PublishContext, info: BitbucketOptions);
|
||||
protected doUpload(fileName: string, _arch: Arch, _dataLength: number, _requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void, file: string): Promise<any>;
|
||||
deleteRelease(filename: string): Promise<void>;
|
||||
toString(): string;
|
||||
static convertAppPassword(username: string, token: string): string;
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BitbucketPublisher = void 0;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const builder_util_runtime_1 = require("builder-util-runtime");
|
||||
const FormData = require("form-data");
|
||||
const fs_extra_1 = require("fs-extra");
|
||||
const httpPublisher_1 = require("./httpPublisher");
|
||||
class BitbucketPublisher extends httpPublisher_1.HttpPublisher {
|
||||
constructor(context, info) {
|
||||
super(context);
|
||||
this.providerName = "bitbucket";
|
||||
this.hostname = "api.bitbucket.org";
|
||||
const token = info.token || process.env.BITBUCKET_TOKEN || null;
|
||||
const username = info.username || process.env.BITBUCKET_USERNAME || null;
|
||||
if ((0, builder_util_1.isEmptyOrSpaces)(token)) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`Bitbucket token is not set using env "BITBUCKET_TOKEN" (see https://www.electron.build/publish#BitbucketOptions)`);
|
||||
}
|
||||
if ((0, builder_util_1.isEmptyOrSpaces)(username)) {
|
||||
builder_util_1.log.warn('No Bitbucket username provided via "BITBUCKET_USERNAME". Defaulting to use repo owner.');
|
||||
}
|
||||
this.info = info;
|
||||
this.auth = BitbucketPublisher.convertAppPassword(username !== null && username !== void 0 ? username : this.info.owner, token);
|
||||
this.basePath = `/2.0/repositories/${this.info.owner}/${this.info.slug}/downloads`;
|
||||
}
|
||||
doUpload(fileName, _arch, _dataLength, _requestProcessor, file) {
|
||||
return builder_util_runtime_1.HttpExecutor.retryOnServerError(async () => {
|
||||
const fileContent = await (0, fs_extra_1.readFile)(file);
|
||||
const form = new FormData();
|
||||
form.append("files", fileContent, fileName);
|
||||
const upload = {
|
||||
hostname: this.hostname,
|
||||
path: this.basePath,
|
||||
headers: form.getHeaders(),
|
||||
timeout: this.info.timeout || undefined,
|
||||
};
|
||||
await builder_util_1.httpExecutor.doApiRequest((0, builder_util_runtime_1.configureRequestOptions)(upload, this.auth, "POST"), this.context.cancellationToken, it => form.pipe(it));
|
||||
return fileName;
|
||||
});
|
||||
}
|
||||
async deleteRelease(filename) {
|
||||
const req = {
|
||||
hostname: this.hostname,
|
||||
path: `${this.basePath}/${filename}`,
|
||||
timeout: this.info.timeout || undefined,
|
||||
};
|
||||
await builder_util_1.httpExecutor.request((0, builder_util_runtime_1.configureRequestOptions)(req, this.auth, "DELETE"), this.context.cancellationToken);
|
||||
}
|
||||
toString() {
|
||||
const { owner, slug, channel } = this.info;
|
||||
return `Bitbucket (owner: ${owner}, slug: ${slug}, channel: ${channel})`;
|
||||
}
|
||||
static convertAppPassword(username, token) {
|
||||
const base64encodedData = Buffer.from(`${username}:${token.trim()}`).toString("base64");
|
||||
return `Basic ${base64encodedData}`;
|
||||
}
|
||||
}
|
||||
exports.BitbucketPublisher = BitbucketPublisher;
|
||||
//# sourceMappingURL=bitbucketPublisher.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+36
@@ -0,0 +1,36 @@
|
||||
import { Arch } from "builder-util";
|
||||
import { GithubOptions } from "builder-util-runtime";
|
||||
import { ClientRequest } from "http";
|
||||
import { Lazy } from "lazy-val";
|
||||
import { HttpPublisher } from "./httpPublisher";
|
||||
import { PublishContext, PublishOptions } from "./index";
|
||||
export interface Release {
|
||||
id: number;
|
||||
tag_name: string;
|
||||
draft: boolean;
|
||||
prerelease: boolean;
|
||||
published_at: string;
|
||||
upload_url: string;
|
||||
}
|
||||
export declare class GitHubPublisher extends HttpPublisher {
|
||||
private readonly info;
|
||||
private readonly version;
|
||||
private readonly options;
|
||||
private readonly tag;
|
||||
readonly _release: Lazy<any>;
|
||||
private readonly token;
|
||||
readonly providerName = "github";
|
||||
private readonly releaseType;
|
||||
private releaseLogFields;
|
||||
constructor(context: PublishContext, info: GithubOptions, version: string, options?: PublishOptions);
|
||||
private getOrCreateRelease;
|
||||
private overwriteArtifact;
|
||||
protected doUpload(fileName: string, arch: Arch, dataLength: number, requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void): Promise<any>;
|
||||
private doUploadFile;
|
||||
private doesErrorMeanAlreadyExists;
|
||||
private createRelease;
|
||||
getRelease(): Promise<any>;
|
||||
deleteRelease(): Promise<any>;
|
||||
private githubRequest;
|
||||
toString(): string;
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GitHubPublisher = void 0;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const builder_util_runtime_1 = require("builder-util-runtime");
|
||||
const lazy_val_1 = require("lazy-val");
|
||||
const mime = require("mime");
|
||||
const url_1 = require("url");
|
||||
const httpPublisher_1 = require("./httpPublisher");
|
||||
const publisher_1 = require("./publisher");
|
||||
class GitHubPublisher extends httpPublisher_1.HttpPublisher {
|
||||
constructor(context, info, version, options = {}) {
|
||||
super(context, true);
|
||||
this.info = info;
|
||||
this.version = version;
|
||||
this.options = options;
|
||||
this._release = new lazy_val_1.Lazy(() => (this.token === "__test__" ? Promise.resolve(null) : this.getOrCreateRelease()));
|
||||
this.providerName = "github";
|
||||
this.releaseLogFields = null;
|
||||
let token = info.token;
|
||||
if ((0, builder_util_1.isEmptyOrSpaces)(token) || process.env.GITHUB_RELEASE_TOKEN) {
|
||||
token = process.env.GITHUB_RELEASE_TOKEN ? process.env.GITHUB_RELEASE_TOKEN : process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
||||
if ((0, builder_util_1.isEmptyOrSpaces)(token)) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`GitHub Personal Access Token is not set, neither programmatically, nor using env "GH_TOKEN"`);
|
||||
}
|
||||
token = token.trim();
|
||||
if (!(0, builder_util_1.isTokenCharValid)(token)) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`GitHub Personal Access Token (${JSON.stringify(token)}) contains invalid characters, please check env "GH_TOKEN"`);
|
||||
}
|
||||
}
|
||||
this.token = token;
|
||||
if (version.startsWith("v")) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`Version must not start with "v": ${version}`);
|
||||
}
|
||||
this.tag = (0, builder_util_runtime_1.githubTagPrefix)(info) + version;
|
||||
if ((0, builder_util_1.isEnvTrue)(process.env.EP_DRAFT)) {
|
||||
this.releaseType = "draft";
|
||||
builder_util_1.log.info({ reason: "env EP_DRAFT is set to true" }, "GitHub provider release type is set to draft");
|
||||
}
|
||||
else if ((0, builder_util_1.isEnvTrue)(process.env.EP_PRE_RELEASE) || (0, builder_util_1.isEnvTrue)(process.env.EP_PRELEASE) /* https://github.com/electron-userland/electron-builder/issues/2878 */) {
|
||||
this.releaseType = "prerelease";
|
||||
builder_util_1.log.info({ reason: "env EP_PRE_RELEASE is set to true" }, "GitHub provider release type is set to prerelease");
|
||||
}
|
||||
else if (info.releaseType != null) {
|
||||
this.releaseType = info.releaseType;
|
||||
}
|
||||
else if (options.prerelease) {
|
||||
this.releaseType = "prerelease";
|
||||
}
|
||||
else {
|
||||
// noinspection PointlessBooleanExpressionJS
|
||||
this.releaseType = options.draft === false ? "release" : "draft";
|
||||
}
|
||||
}
|
||||
async getOrCreateRelease() {
|
||||
const logFields = {
|
||||
tag: this.tag,
|
||||
version: this.version,
|
||||
};
|
||||
// we don't use "Get a release by tag name" because "tag name" means existing git tag, but we draft release and don't create git tag
|
||||
const releases = await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases`, this.token);
|
||||
for (const release of releases) {
|
||||
if (!(release.tag_name === this.tag || release.tag_name === this.version)) {
|
||||
continue;
|
||||
}
|
||||
if (release.draft) {
|
||||
return release;
|
||||
}
|
||||
// https://github.com/electron-userland/electron-builder/issues/1197
|
||||
// https://github.com/electron-userland/electron-builder/issues/2072
|
||||
if (this.releaseType === "draft") {
|
||||
this.releaseLogFields = {
|
||||
reason: "existing type not compatible with publishing type",
|
||||
...logFields,
|
||||
existingType: release.prerelease ? "pre-release" : "release",
|
||||
publishingType: this.releaseType,
|
||||
};
|
||||
builder_util_1.log.warn(this.releaseLogFields, "GitHub release not created");
|
||||
return null;
|
||||
}
|
||||
// https://github.com/electron-userland/electron-builder/issues/1133
|
||||
// https://github.com/electron-userland/electron-builder/issues/2074
|
||||
// if release created < 2 hours — allow to upload
|
||||
const publishedAt = release.published_at == null ? null : Date.parse(release.published_at);
|
||||
if (!(0, builder_util_1.isEnvTrue)(process.env.EP_GH_IGNORE_TIME) && publishedAt != null && Date.now() - publishedAt > 2 * 3600 * 1000) {
|
||||
// https://github.com/electron-userland/electron-builder/issues/1183#issuecomment-275867187
|
||||
this.releaseLogFields = {
|
||||
reason: "existing release published more than 2 hours ago",
|
||||
...logFields,
|
||||
date: new Date(publishedAt).toString(),
|
||||
};
|
||||
builder_util_1.log.warn(this.releaseLogFields, "GitHub release not created");
|
||||
return null;
|
||||
}
|
||||
return release;
|
||||
}
|
||||
// https://github.com/electron-userland/electron-builder/issues/1835
|
||||
if (this.options.publish === "always" || (0, publisher_1.getCiTag)() != null) {
|
||||
builder_util_1.log.info({
|
||||
reason: "release doesn't exist",
|
||||
...logFields,
|
||||
}, `creating GitHub release`);
|
||||
return this.createRelease();
|
||||
}
|
||||
this.releaseLogFields = {
|
||||
reason: 'release doesn\'t exist and not created because "publish" is not "always" and build is not on tag',
|
||||
...logFields,
|
||||
};
|
||||
return null;
|
||||
}
|
||||
async overwriteArtifact(fileName, release) {
|
||||
// delete old artifact and re-upload
|
||||
builder_util_1.log.warn({ file: fileName, reason: "already exists on GitHub" }, "overwrite published file");
|
||||
const assets = await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/${release.id}/assets`, this.token, null);
|
||||
for (const asset of assets) {
|
||||
if (asset.name === fileName) {
|
||||
await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/assets/${asset.id}`, this.token, null, "DELETE");
|
||||
return;
|
||||
}
|
||||
}
|
||||
builder_util_1.log.debug({ file: fileName, reason: "not found on GitHub" }, "trying to upload again");
|
||||
}
|
||||
async doUpload(fileName, arch, dataLength, requestProcessor) {
|
||||
const release = await this._release.value;
|
||||
if (release == null) {
|
||||
builder_util_1.log.warn({ file: fileName, ...this.releaseLogFields }, "skipped publishing");
|
||||
return;
|
||||
}
|
||||
const parsedUrl = (0, url_1.parse)(`${release.upload_url.substring(0, release.upload_url.indexOf("{"))}?name=${fileName}`);
|
||||
return await this.doUploadFile(0, parsedUrl, fileName, dataLength, requestProcessor, release);
|
||||
}
|
||||
doUploadFile(attemptNumber, parsedUrl, fileName, dataLength, requestProcessor, release) {
|
||||
return builder_util_1.httpExecutor
|
||||
.doApiRequest((0, builder_util_runtime_1.configureRequestOptions)({
|
||||
protocol: parsedUrl.protocol,
|
||||
hostname: parsedUrl.hostname,
|
||||
path: parsedUrl.path,
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
"Content-Type": mime.getType(fileName) || "application/octet-stream",
|
||||
"Content-Length": dataLength,
|
||||
},
|
||||
timeout: this.info.timeout || undefined,
|
||||
}, this.token), this.context.cancellationToken, requestProcessor)
|
||||
.catch((e) => {
|
||||
if (attemptNumber > 3) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
else if (this.doesErrorMeanAlreadyExists(e)) {
|
||||
return this.overwriteArtifact(fileName, release).then(() => this.doUploadFile(attemptNumber + 1, parsedUrl, fileName, dataLength, requestProcessor, release));
|
||||
}
|
||||
else {
|
||||
return new Promise((resolve, reject) => {
|
||||
const newAttemptNumber = attemptNumber + 1;
|
||||
setTimeout(() => {
|
||||
this.doUploadFile(newAttemptNumber, parsedUrl, fileName, dataLength, requestProcessor, release).then(resolve).catch(reject);
|
||||
}, newAttemptNumber * 2000);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
doesErrorMeanAlreadyExists(e) {
|
||||
if (!e.description) {
|
||||
return false;
|
||||
}
|
||||
const desc = e.description;
|
||||
const descIncludesAlreadyExists = (desc.includes("errors") && desc.includes("already_exists")) || (desc.errors && desc.errors.length >= 1 && desc.errors[0].code === "already_exists");
|
||||
return e.statusCode === 422 && descIncludesAlreadyExists;
|
||||
}
|
||||
createRelease() {
|
||||
return this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases`, this.token, {
|
||||
tag_name: this.tag,
|
||||
name: this.version,
|
||||
draft: this.releaseType === "draft",
|
||||
prerelease: this.releaseType === "prerelease",
|
||||
});
|
||||
}
|
||||
// test only
|
||||
//noinspection JSUnusedGlobalSymbols
|
||||
async getRelease() {
|
||||
return this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/${(await this._release.value).id}`, this.token);
|
||||
}
|
||||
//noinspection JSUnusedGlobalSymbols
|
||||
async deleteRelease() {
|
||||
if (!this._release.hasValue) {
|
||||
return;
|
||||
}
|
||||
const release = await this._release.value;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
try {
|
||||
return await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/${release.id}`, this.token, null, "DELETE");
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof builder_util_runtime_1.HttpError) {
|
||||
if (e.statusCode === 404) {
|
||||
builder_util_1.log.warn({ releaseId: release.id, reason: "doesn't exist" }, "cannot delete release");
|
||||
return;
|
||||
}
|
||||
else if (e.statusCode === 405 || e.statusCode === 502) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
builder_util_1.log.warn({ releaseId: release.id }, "cannot delete release");
|
||||
}
|
||||
githubRequest(path, token, data = null, method) {
|
||||
// host can contains port, but node http doesn't support host as url does
|
||||
const baseUrl = (0, url_1.parse)(`https://${this.info.host || "api.github.com"}`);
|
||||
return (0, builder_util_runtime_1.parseJson)(builder_util_1.httpExecutor.request((0, builder_util_runtime_1.configureRequestOptions)({
|
||||
protocol: baseUrl.protocol,
|
||||
hostname: baseUrl.hostname,
|
||||
port: baseUrl.port,
|
||||
path: this.info.host != null && this.info.host !== "github.com" ? `/api/v3${path.startsWith("/") ? path : `/${path}`}` : path,
|
||||
headers: { accept: "application/vnd.github.v3+json" },
|
||||
timeout: this.info.timeout || undefined,
|
||||
}, token, method), this.context.cancellationToken, data));
|
||||
}
|
||||
toString() {
|
||||
return `Github (owner: ${this.info.owner}, project: ${this.info.repo}, version: ${this.version})`;
|
||||
}
|
||||
}
|
||||
exports.GitHubPublisher = GitHubPublisher;
|
||||
//# sourceMappingURL=gitHubPublisher.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+36
@@ -0,0 +1,36 @@
|
||||
import { Arch } from "builder-util";
|
||||
import { GitlabOptions } from "builder-util-runtime";
|
||||
import { ClientRequest } from "http";
|
||||
import { Lazy } from "lazy-val";
|
||||
import { HttpPublisher } from "./httpPublisher";
|
||||
import { PublishContext } from "./index";
|
||||
type RequestProcessor = (request: ClientRequest, reject: (error: Error) => void) => void;
|
||||
export declare class GitlabPublisher extends HttpPublisher {
|
||||
private readonly info;
|
||||
private readonly version;
|
||||
private readonly tag;
|
||||
readonly _release: Lazy<any>;
|
||||
private readonly token;
|
||||
private readonly host;
|
||||
private readonly baseApiPath;
|
||||
private readonly projectId;
|
||||
readonly providerName = "gitlab";
|
||||
private releaseLogFields;
|
||||
constructor(context: PublishContext, info: GitlabOptions, version: string);
|
||||
private getOrCreateRelease;
|
||||
private getExistingRelease;
|
||||
private getDefaultBranch;
|
||||
private createRelease;
|
||||
protected doUpload(fileName: string, arch: Arch, dataLength: number, requestProcessor: RequestProcessor, filePath: string): Promise<any>;
|
||||
private uploadFileAndReturnAssetPath;
|
||||
private addReleaseAssetLink;
|
||||
private uploadToProjectUpload;
|
||||
private uploadToGenericPackages;
|
||||
private buildProjectUrl;
|
||||
private resolveProjectId;
|
||||
private gitlabRequest;
|
||||
private setAuthHeaderForToken;
|
||||
private categorizeGitlabError;
|
||||
toString(): string;
|
||||
}
|
||||
export {};
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GitlabPublisher = void 0;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const fs_1 = require("fs");
|
||||
const promises_1 = require("fs/promises");
|
||||
const promises_2 = require("fs/promises");
|
||||
const builder_util_runtime_1 = require("builder-util-runtime");
|
||||
const lazy_val_1 = require("lazy-val");
|
||||
const mime = require("mime");
|
||||
const FormData = require("form-data");
|
||||
const url_1 = require("url");
|
||||
const httpPublisher_1 = require("./httpPublisher");
|
||||
class GitlabPublisher extends httpPublisher_1.HttpPublisher {
|
||||
constructor(context, info, version) {
|
||||
super(context, true);
|
||||
this.info = info;
|
||||
this.version = version;
|
||||
this._release = new lazy_val_1.Lazy(() => (this.token === "__test__" ? Promise.resolve(null) : this.getOrCreateRelease()));
|
||||
this.providerName = "gitlab";
|
||||
this.releaseLogFields = null;
|
||||
let token = info.token || null;
|
||||
if ((0, builder_util_1.isEmptyOrSpaces)(token)) {
|
||||
token = process.env.GITLAB_TOKEN || null;
|
||||
if ((0, builder_util_1.isEmptyOrSpaces)(token)) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`GitLab Personal Access Token is not set, neither programmatically, nor using env "GITLAB_TOKEN"`);
|
||||
}
|
||||
token = token.trim();
|
||||
if (!(0, builder_util_1.isTokenCharValid)(token)) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`GitLab Personal Access Token (${JSON.stringify(token)}) contains invalid characters, please check env "GITLAB_TOKEN"`);
|
||||
}
|
||||
}
|
||||
this.token = token;
|
||||
this.host = info.host || "gitlab.com";
|
||||
this.projectId = this.resolveProjectId();
|
||||
this.baseApiPath = `https://${this.host}/api/v4`;
|
||||
if (version.startsWith("v")) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`Version must not start with "v": ${version}`);
|
||||
}
|
||||
// By default, we prefix the version with "v"
|
||||
this.tag = info.vPrefixedTagName === false ? version : `v${version}`;
|
||||
}
|
||||
async getOrCreateRelease() {
|
||||
const logFields = {
|
||||
tag: this.tag,
|
||||
version: this.version,
|
||||
};
|
||||
try {
|
||||
const existingRelease = await this.getExistingRelease();
|
||||
if (existingRelease) {
|
||||
return existingRelease;
|
||||
}
|
||||
// Create new release if it doesn't exist
|
||||
return this.createRelease();
|
||||
}
|
||||
catch (error) {
|
||||
const errorInfo = this.categorizeGitlabError(error);
|
||||
builder_util_1.log.error({
|
||||
...logFields,
|
||||
error: error.message,
|
||||
errorType: errorInfo.type,
|
||||
statusCode: errorInfo.statusCode,
|
||||
}, "Failed to get or create GitLab release");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async getExistingRelease() {
|
||||
const url = this.buildProjectUrl("/releases");
|
||||
const releases = await this.gitlabRequest(url);
|
||||
for (const release of releases) {
|
||||
if (release.tag_name === this.tag) {
|
||||
return release;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async getDefaultBranch() {
|
||||
try {
|
||||
const url = this.buildProjectUrl();
|
||||
const project = await this.gitlabRequest(url);
|
||||
return project.default_branch || "main";
|
||||
}
|
||||
catch (error) {
|
||||
builder_util_1.log.warn({ error: error.message }, "Failed to get default branch, using 'main' as fallback");
|
||||
return "main";
|
||||
}
|
||||
}
|
||||
async createRelease() {
|
||||
const releaseName = this.info.vPrefixedTagName === false ? this.version : `v${this.version}`;
|
||||
const branchName = await this.getDefaultBranch();
|
||||
const releaseData = {
|
||||
tag_name: this.tag,
|
||||
name: releaseName,
|
||||
description: `Release ${releaseName}`,
|
||||
ref: branchName,
|
||||
};
|
||||
builder_util_1.log.debug({
|
||||
tag: this.tag,
|
||||
name: releaseName,
|
||||
ref: branchName,
|
||||
projectId: this.projectId,
|
||||
}, "creating GitLab release");
|
||||
const url = this.buildProjectUrl("/releases");
|
||||
return this.gitlabRequest(url, releaseData, "POST");
|
||||
}
|
||||
async doUpload(fileName, arch, dataLength, requestProcessor, filePath) {
|
||||
const release = await this._release.value;
|
||||
if (release == null) {
|
||||
builder_util_1.log.warn({ file: fileName, ...this.releaseLogFields }, "skipped publishing");
|
||||
return;
|
||||
}
|
||||
const logFields = {
|
||||
file: fileName,
|
||||
arch: builder_util_1.Arch[arch],
|
||||
size: dataLength,
|
||||
uploadTarget: this.info.uploadTarget || "project_upload",
|
||||
};
|
||||
try {
|
||||
builder_util_1.log.debug(logFields, "starting GitLab upload");
|
||||
const assetPath = await this.uploadFileAndReturnAssetPath(fileName, dataLength, requestProcessor, filePath);
|
||||
// Add the uploaded file as a release asset link
|
||||
if (assetPath) {
|
||||
await this.addReleaseAssetLink(fileName, assetPath);
|
||||
builder_util_1.log.info({ ...logFields, assetPath }, "GitLab upload completed successfully");
|
||||
}
|
||||
else {
|
||||
builder_util_1.log.warn({ ...logFields }, "No asset URL found for file");
|
||||
}
|
||||
return assetPath;
|
||||
}
|
||||
catch (e) {
|
||||
const errorInfo = this.categorizeGitlabError(e);
|
||||
builder_util_1.log.error({
|
||||
...logFields,
|
||||
error: e.message,
|
||||
errorType: errorInfo.type,
|
||||
statusCode: errorInfo.statusCode,
|
||||
}, "GitLab upload failed");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
async uploadFileAndReturnAssetPath(fileName, dataLength, requestProcessor, filePath) {
|
||||
// Default to project_upload method
|
||||
const uploadTarget = this.info.uploadTarget || "project_upload";
|
||||
let assetPath;
|
||||
if (uploadTarget === "generic_package") {
|
||||
await this.uploadToGenericPackages(fileName, dataLength, requestProcessor);
|
||||
// For generic packages, construct the download URL
|
||||
const projectId = encodeURIComponent(this.projectId);
|
||||
assetPath = `${this.baseApiPath}/projects/${projectId}/packages/generic/releases/${this.version}/${fileName}`;
|
||||
}
|
||||
else {
|
||||
// Default to project_upload
|
||||
const uploadResult = await this.uploadToProjectUpload(fileName, filePath);
|
||||
// For project uploads, construct full URL from relative path
|
||||
assetPath = `https://${this.host}${uploadResult.full_path}`;
|
||||
}
|
||||
return assetPath;
|
||||
}
|
||||
async addReleaseAssetLink(fileName, assetUrl) {
|
||||
try {
|
||||
const linkData = {
|
||||
name: fileName,
|
||||
url: assetUrl,
|
||||
link_type: "other",
|
||||
};
|
||||
const url = this.buildProjectUrl(`/releases/${this.tag}/assets/links`);
|
||||
await this.gitlabRequest(url, linkData, "POST");
|
||||
builder_util_1.log.debug({ fileName, assetUrl }, "Successfully linked asset to GitLab release");
|
||||
}
|
||||
catch (e) {
|
||||
builder_util_1.log.warn({ fileName, assetUrl, error: e.message }, "Failed to link asset to GitLab release");
|
||||
// Don't throw - the file was uploaded successfully, linking is optional
|
||||
}
|
||||
}
|
||||
async uploadToProjectUpload(fileName, filePath) {
|
||||
const uploadUrl = `${this.baseApiPath}/projects/${encodeURIComponent(this.projectId)}/uploads`;
|
||||
const parsedUrl = new url_1.URL(uploadUrl);
|
||||
// Check file size to determine upload method
|
||||
const stats = await (0, promises_1.stat)(filePath);
|
||||
const fileSize = stats.size;
|
||||
const STREAMING_THRESHOLD = 50 * 1024 * 1024; // 50MB
|
||||
const form = new FormData();
|
||||
if (fileSize > STREAMING_THRESHOLD) {
|
||||
// Use streaming for large files
|
||||
builder_util_1.log.debug({ fileName, fileSize }, "using streaming upload for large file");
|
||||
const fileStream = (0, fs_1.createReadStream)(filePath);
|
||||
form.append("file", fileStream, fileName);
|
||||
}
|
||||
else {
|
||||
// Use buffer for small files
|
||||
builder_util_1.log.debug({ fileName, fileSize }, "using buffer upload for small file");
|
||||
const fileContent = await (0, promises_2.readFile)(filePath);
|
||||
form.append("file", fileContent, fileName);
|
||||
}
|
||||
const response = await builder_util_1.httpExecutor.doApiRequest((0, builder_util_runtime_1.configureRequestOptions)({
|
||||
protocol: parsedUrl.protocol,
|
||||
hostname: parsedUrl.hostname,
|
||||
port: parsedUrl.port,
|
||||
path: parsedUrl.pathname,
|
||||
headers: { ...form.getHeaders(), ...this.setAuthHeaderForToken(this.token) },
|
||||
timeout: this.info.timeout || undefined,
|
||||
}, null, "POST"), this.context.cancellationToken, (it) => form.pipe(it));
|
||||
// Parse the JSON response string
|
||||
return JSON.parse(response);
|
||||
}
|
||||
async uploadToGenericPackages(fileName, dataLength, requestProcessor) {
|
||||
const uploadUrl = `${this.baseApiPath}/projects/${encodeURIComponent(this.projectId)}/packages/generic/releases/${this.version}/${fileName}`;
|
||||
const parsedUrl = new url_1.URL(uploadUrl);
|
||||
return builder_util_1.httpExecutor.doApiRequest((0, builder_util_runtime_1.configureRequestOptions)({
|
||||
protocol: parsedUrl.protocol,
|
||||
hostname: parsedUrl.hostname,
|
||||
port: parsedUrl.port,
|
||||
path: parsedUrl.pathname,
|
||||
headers: { "Content-Length": dataLength, "Content-Type": mime.getType(fileName) || "application/octet-stream", ...this.setAuthHeaderForToken(this.token) },
|
||||
timeout: this.info.timeout || undefined,
|
||||
}, null, "PUT"), this.context.cancellationToken, requestProcessor);
|
||||
}
|
||||
buildProjectUrl(path = "") {
|
||||
return new url_1.URL(`${this.baseApiPath}/projects/${encodeURIComponent(this.projectId)}${path}`);
|
||||
}
|
||||
resolveProjectId() {
|
||||
if (this.info.projectId) {
|
||||
return String(this.info.projectId);
|
||||
}
|
||||
throw new builder_util_1.InvalidConfigurationError("GitLab project ID is not specified, please set it in configuration.");
|
||||
}
|
||||
gitlabRequest(url, data = null, method = "GET") {
|
||||
return (0, builder_util_runtime_1.parseJson)(builder_util_1.httpExecutor.request((0, builder_util_runtime_1.configureRequestOptions)({
|
||||
port: url.port,
|
||||
path: url.pathname,
|
||||
protocol: url.protocol,
|
||||
hostname: url.hostname,
|
||||
headers: { "Content-Type": "application/json", ...this.setAuthHeaderForToken(this.token) },
|
||||
timeout: this.info.timeout || undefined,
|
||||
}, null, method), this.context.cancellationToken, data));
|
||||
}
|
||||
setAuthHeaderForToken(token) {
|
||||
const headers = {};
|
||||
if (token != null) {
|
||||
// If the token starts with "Bearer", it is an OAuth application secret
|
||||
// Note that the original gitlab token would not start with "Bearer"
|
||||
// it might start with "gloas-", if so user needs to add "Bearer " prefix to the token
|
||||
if (token.startsWith("Bearer")) {
|
||||
headers.authorization = token;
|
||||
}
|
||||
else {
|
||||
headers["PRIVATE-TOKEN"] = token;
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
categorizeGitlabError(error) {
|
||||
if (error instanceof builder_util_runtime_1.HttpError) {
|
||||
const statusCode = error.statusCode;
|
||||
switch (statusCode) {
|
||||
case 401:
|
||||
return { type: "authentication", statusCode };
|
||||
case 403:
|
||||
return { type: "authorization", statusCode };
|
||||
case 404:
|
||||
return { type: "not_found", statusCode };
|
||||
case 409:
|
||||
return { type: "conflict", statusCode };
|
||||
case 413:
|
||||
return { type: "file_too_large", statusCode };
|
||||
case 422:
|
||||
return { type: "validation_error", statusCode };
|
||||
case 429:
|
||||
return { type: "rate_limit", statusCode };
|
||||
case 500:
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
return { type: "server_error", statusCode };
|
||||
default:
|
||||
return { type: "http_error", statusCode };
|
||||
}
|
||||
}
|
||||
if (error.code === "ECONNRESET" || error.code === "ENOTFOUND" || error.code === "ETIMEDOUT") {
|
||||
return { type: "network_error" };
|
||||
}
|
||||
return { type: "unknown_error" };
|
||||
}
|
||||
toString() {
|
||||
return `GitLab (project: ${this.projectId}, version: ${this.version})`;
|
||||
}
|
||||
}
|
||||
exports.GitlabPublisher = GitlabPublisher;
|
||||
//# sourceMappingURL=gitlabPublisher.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+11
@@ -0,0 +1,11 @@
|
||||
import { Arch } from "builder-util";
|
||||
import { ClientRequest } from "http";
|
||||
import { PublishContext, UploadTask } from ".";
|
||||
import { Publisher } from "./publisher";
|
||||
export declare abstract class HttpPublisher extends Publisher {
|
||||
protected readonly context: PublishContext;
|
||||
private readonly useSafeArtifactName;
|
||||
protected constructor(context: PublishContext, useSafeArtifactName?: boolean);
|
||||
upload(task: UploadTask): Promise<any>;
|
||||
protected abstract doUpload(fileName: string, arch: Arch, dataLength: number, requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void, file: string): Promise<any>;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.HttpPublisher = void 0;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const fs_extra_1 = require("fs-extra");
|
||||
const path_1 = require("path");
|
||||
const publisher_1 = require("./publisher");
|
||||
class HttpPublisher extends publisher_1.Publisher {
|
||||
constructor(context, useSafeArtifactName = false) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.useSafeArtifactName = useSafeArtifactName;
|
||||
}
|
||||
async upload(task) {
|
||||
const fileName = (this.useSafeArtifactName ? task.safeArtifactName : null) || (0, path_1.basename)(task.file);
|
||||
if (task.fileContent != null) {
|
||||
await this.doUpload(fileName, task.arch || builder_util_1.Arch.x64, task.fileContent.length, (request, reject) => {
|
||||
if (task.timeout) {
|
||||
request.setTimeout(task.timeout, () => {
|
||||
request.destroy();
|
||||
reject(new Error("Request timed out"));
|
||||
});
|
||||
}
|
||||
return request.end(task.fileContent);
|
||||
}, task.file);
|
||||
return;
|
||||
}
|
||||
const fileStat = await (0, fs_extra_1.stat)(task.file);
|
||||
const progressBar = this.createProgressBar(fileName, fileStat.size);
|
||||
return this.doUpload(fileName, task.arch || builder_util_1.Arch.x64, fileStat.size, (request, reject) => {
|
||||
if (progressBar != null) {
|
||||
// reset (because can be called several times (several attempts)
|
||||
progressBar.update(0);
|
||||
}
|
||||
if (task.timeout) {
|
||||
request.setTimeout(task.timeout, () => {
|
||||
request.destroy();
|
||||
reject(new Error("Request timed out"));
|
||||
});
|
||||
}
|
||||
return this.createReadStreamAndProgressBar(task.file, fileStat, progressBar, reject).pipe(request);
|
||||
}, task.file);
|
||||
}
|
||||
}
|
||||
exports.HttpPublisher = HttpPublisher;
|
||||
//# sourceMappingURL=httpPublisher.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"httpPublisher.js","sourceRoot":"","sources":["../src/httpPublisher.ts"],"names":[],"mappings":";;;AAAA,+CAAmC;AACnC,uCAA+B;AAE/B,+BAA+B;AAE/B,2CAAuC;AAEvC,MAAsB,aAAc,SAAQ,qBAAS;IACnD,YACqB,OAAuB,EACzB,sBAAsB,KAAK;QAE5C,KAAK,CAAC,OAAO,CAAC,CAAA;QAHK,YAAO,GAAP,OAAO,CAAgB;QACzB,wBAAmB,GAAnB,mBAAmB,CAAQ;IAG9C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAgB;QAC3B,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAA,eAAQ,EAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAEjG,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,EAAE,CAAC;YAC7B,MAAM,IAAI,CAAC,QAAQ,CACjB,QAAQ,EACR,IAAI,CAAC,IAAI,IAAI,mBAAI,CAAC,GAAG,EACrB,IAAI,CAAC,WAAW,CAAC,MAAM,EACvB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAClB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;oBACjB,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;wBACpC,OAAO,CAAC,OAAO,EAAE,CAAA;wBACjB,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAA;oBACxC,CAAC,CAAC,CAAA;gBACJ,CAAC;gBACD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YACtC,CAAC,EACD,IAAI,CAAC,IAAI,CACV,CAAA;YACD,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,IAAA,eAAI,EAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAEtC,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;QACnE,OAAO,IAAI,CAAC,QAAQ,CAClB,QAAQ,EACR,IAAI,CAAC,IAAI,IAAI,mBAAI,CAAC,GAAG,EACrB,QAAQ,CAAC,IAAI,EACb,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAClB,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;gBACxB,gEAAgE;gBAChE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;YACvB,CAAC;YACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;oBACpC,OAAO,CAAC,OAAO,EAAE,CAAA;oBACjB,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAA;gBACxC,CAAC,CAAC,CAAA;YACJ,CAAC;YACD,OAAO,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACpG,CAAC,EACD,IAAI,CAAC,IAAI,CACV,CAAA;IACH,CAAC;CASF;AA7DD,sCA6DC","sourcesContent":["import { Arch } from \"builder-util\"\nimport { stat } from \"fs-extra\"\nimport { ClientRequest } from \"http\"\nimport { basename } from \"path\"\nimport { PublishContext, UploadTask } from \".\"\nimport { Publisher } from \"./publisher\"\n\nexport abstract class HttpPublisher extends Publisher {\n protected constructor(\n protected readonly context: PublishContext,\n private readonly useSafeArtifactName = false\n ) {\n super(context)\n }\n\n async upload(task: UploadTask): Promise<any> {\n const fileName = (this.useSafeArtifactName ? task.safeArtifactName : null) || basename(task.file)\n\n if (task.fileContent != null) {\n await this.doUpload(\n fileName,\n task.arch || Arch.x64,\n task.fileContent.length,\n (request, reject) => {\n if (task.timeout) {\n request.setTimeout(task.timeout, () => {\n request.destroy()\n reject(new Error(\"Request timed out\"))\n })\n }\n return request.end(task.fileContent)\n },\n task.file\n )\n return\n }\n\n const fileStat = await stat(task.file)\n\n const progressBar = this.createProgressBar(fileName, fileStat.size)\n return this.doUpload(\n fileName,\n task.arch || Arch.x64,\n fileStat.size,\n (request, reject) => {\n if (progressBar != null) {\n // reset (because can be called several times (several attempts)\n progressBar.update(0)\n }\n if (task.timeout) {\n request.setTimeout(task.timeout, () => {\n request.destroy()\n reject(new Error(\"Request timed out\"))\n })\n }\n return this.createReadStreamAndProgressBar(task.file, fileStat, progressBar, reject).pipe(request)\n },\n task.file\n )\n }\n\n protected abstract doUpload(\n fileName: string,\n arch: Arch,\n dataLength: number,\n requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void,\n file: string\n ): Promise<any>\n}\n"]}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { Arch } from "builder-util";
|
||||
import { CancellationToken } from "builder-util-runtime";
|
||||
import { MultiProgress } from "./multiProgress";
|
||||
export { BitbucketPublisher } from "./bitbucketPublisher";
|
||||
export { GitHubPublisher } from "./gitHubPublisher";
|
||||
export { GitlabPublisher } from "./gitlabPublisher";
|
||||
export { KeygenPublisher } from "./keygenPublisher";
|
||||
export { S3Publisher } from "./s3/s3Publisher";
|
||||
export { SpacesPublisher } from "./s3/spacesPublisher";
|
||||
export { SnapStorePublisher } from "./snapStorePublisher";
|
||||
export type PublishPolicy = "onTag" | "onTagOrDraft" | "always" | "never";
|
||||
export { ProgressCallback } from "./progress";
|
||||
export interface PublishOptions {
|
||||
publish?: PublishPolicy | null;
|
||||
}
|
||||
export { HttpPublisher } from "./httpPublisher";
|
||||
export { getCiTag, Publisher } from "./publisher";
|
||||
export interface PublishContext {
|
||||
readonly cancellationToken: CancellationToken;
|
||||
readonly progress: MultiProgress | null;
|
||||
}
|
||||
export interface UploadTask {
|
||||
file: string;
|
||||
fileContent?: Buffer | null;
|
||||
arch: Arch | null;
|
||||
safeArtifactName?: string | null;
|
||||
timeout?: number | null;
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Publisher = exports.getCiTag = exports.HttpPublisher = exports.ProgressCallback = exports.SnapStorePublisher = exports.SpacesPublisher = exports.S3Publisher = exports.KeygenPublisher = exports.GitlabPublisher = exports.GitHubPublisher = exports.BitbucketPublisher = void 0;
|
||||
var bitbucketPublisher_1 = require("./bitbucketPublisher");
|
||||
Object.defineProperty(exports, "BitbucketPublisher", { enumerable: true, get: function () { return bitbucketPublisher_1.BitbucketPublisher; } });
|
||||
var gitHubPublisher_1 = require("./gitHubPublisher");
|
||||
Object.defineProperty(exports, "GitHubPublisher", { enumerable: true, get: function () { return gitHubPublisher_1.GitHubPublisher; } });
|
||||
var gitlabPublisher_1 = require("./gitlabPublisher");
|
||||
Object.defineProperty(exports, "GitlabPublisher", { enumerable: true, get: function () { return gitlabPublisher_1.GitlabPublisher; } });
|
||||
var keygenPublisher_1 = require("./keygenPublisher");
|
||||
Object.defineProperty(exports, "KeygenPublisher", { enumerable: true, get: function () { return keygenPublisher_1.KeygenPublisher; } });
|
||||
var s3Publisher_1 = require("./s3/s3Publisher");
|
||||
Object.defineProperty(exports, "S3Publisher", { enumerable: true, get: function () { return s3Publisher_1.S3Publisher; } });
|
||||
var spacesPublisher_1 = require("./s3/spacesPublisher");
|
||||
Object.defineProperty(exports, "SpacesPublisher", { enumerable: true, get: function () { return spacesPublisher_1.SpacesPublisher; } });
|
||||
var snapStorePublisher_1 = require("./snapStorePublisher");
|
||||
Object.defineProperty(exports, "SnapStorePublisher", { enumerable: true, get: function () { return snapStorePublisher_1.SnapStorePublisher; } });
|
||||
var progress_1 = require("./progress");
|
||||
Object.defineProperty(exports, "ProgressCallback", { enumerable: true, get: function () { return progress_1.ProgressCallback; } });
|
||||
var httpPublisher_1 = require("./httpPublisher");
|
||||
Object.defineProperty(exports, "HttpPublisher", { enumerable: true, get: function () { return httpPublisher_1.HttpPublisher; } });
|
||||
var publisher_1 = require("./publisher");
|
||||
Object.defineProperty(exports, "getCiTag", { enumerable: true, get: function () { return publisher_1.getCiTag; } });
|
||||
Object.defineProperty(exports, "Publisher", { enumerable: true, get: function () { return publisher_1.Publisher; } });
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAIA,2DAAyD;AAAhD,wHAAA,kBAAkB,OAAA;AAC3B,qDAAmD;AAA1C,kHAAA,eAAe,OAAA;AACxB,qDAAmD;AAA1C,kHAAA,eAAe,OAAA;AACxB,qDAAmD;AAA1C,kHAAA,eAAe,OAAA;AACxB,gDAA8C;AAArC,0GAAA,WAAW,OAAA;AACpB,wDAAsD;AAA7C,kHAAA,eAAe,OAAA;AACxB,2DAAyD;AAAhD,wHAAA,kBAAkB,OAAA;AAI3B,uCAA6C;AAApC,4GAAA,gBAAgB,OAAA;AAMzB,iDAA+C;AAAtC,8GAAA,aAAa,OAAA;AACtB,yCAAiD;AAAxC,qGAAA,QAAQ,OAAA;AAAE,sGAAA,SAAS,OAAA","sourcesContent":["import { Arch } from \"builder-util\"\nimport { CancellationToken } from \"builder-util-runtime\"\nimport { MultiProgress } from \"./multiProgress\"\n\nexport { BitbucketPublisher } from \"./bitbucketPublisher\"\nexport { GitHubPublisher } from \"./gitHubPublisher\"\nexport { GitlabPublisher } from \"./gitlabPublisher\"\nexport { KeygenPublisher } from \"./keygenPublisher\"\nexport { S3Publisher } from \"./s3/s3Publisher\"\nexport { SpacesPublisher } from \"./s3/spacesPublisher\"\nexport { SnapStorePublisher } from \"./snapStorePublisher\"\n\nexport type PublishPolicy = \"onTag\" | \"onTagOrDraft\" | \"always\" | \"never\"\n\nexport { ProgressCallback } from \"./progress\"\n\nexport interface PublishOptions {\n publish?: PublishPolicy | null\n}\n\nexport { HttpPublisher } from \"./httpPublisher\"\nexport { getCiTag, Publisher } from \"./publisher\"\n\nexport interface PublishContext {\n readonly cancellationToken: CancellationToken\n readonly progress: MultiProgress | null\n}\n\nexport interface UploadTask {\n file: string\n fileContent?: Buffer | null\n\n arch: Arch | null\n safeArtifactName?: string | null\n timeout?: number | null\n}\n"]}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
import { Arch } from "builder-util";
|
||||
import { KeygenOptions } from "builder-util-runtime/out/publishOptions";
|
||||
import { ClientRequest } from "http";
|
||||
import { PublishContext } from "./";
|
||||
import { HttpPublisher } from "./httpPublisher";
|
||||
export interface KeygenError {
|
||||
title: string;
|
||||
detail: string;
|
||||
code: string;
|
||||
}
|
||||
export interface KeygenRelease {
|
||||
id: string;
|
||||
type: "releases";
|
||||
attributes: {
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
channel: "stable" | "rc" | "beta" | "alpha" | "dev";
|
||||
status: "DRAFT" | "PUBLISHED" | "YANKED";
|
||||
tag: string;
|
||||
version: string;
|
||||
semver: {
|
||||
major: number;
|
||||
minor: number;
|
||||
patch: number;
|
||||
prerelease: string | null;
|
||||
build: string | null;
|
||||
};
|
||||
metadata: {
|
||||
[s: string]: any;
|
||||
};
|
||||
created: string;
|
||||
updated: string;
|
||||
yanked: string | null;
|
||||
};
|
||||
relationships: {
|
||||
account: {
|
||||
data: {
|
||||
type: "accounts";
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
product: {
|
||||
data: {
|
||||
type: "products";
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
export interface KeygenArtifact {
|
||||
id: string;
|
||||
type: "artifacts";
|
||||
attributes: {
|
||||
filename: string;
|
||||
filetype: string | null;
|
||||
filesize: number | null;
|
||||
platform: string | null;
|
||||
arch: string | null;
|
||||
signature: string | null;
|
||||
checksum: string | null;
|
||||
status: "WAITING" | "UPLOADED" | "FAILED" | "YANKED";
|
||||
metadata: {
|
||||
[s: string]: any;
|
||||
};
|
||||
created: string;
|
||||
updated: string;
|
||||
};
|
||||
relationships: {
|
||||
account: {
|
||||
data: {
|
||||
type: "accounts";
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
release: {
|
||||
data: {
|
||||
type: "releases";
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
links: {
|
||||
redirect: string;
|
||||
};
|
||||
}
|
||||
export declare class KeygenPublisher extends HttpPublisher {
|
||||
readonly providerName = "keygen";
|
||||
readonly defaultHostname = "api.keygen.sh";
|
||||
private readonly info;
|
||||
private readonly auth;
|
||||
private readonly version;
|
||||
private readonly basePath;
|
||||
get hostname(): string;
|
||||
constructor(context: PublishContext, info: KeygenOptions, version: string);
|
||||
protected doUpload(fileName: string, _arch: Arch, dataLength: number, requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void, _file: string): Promise<string>;
|
||||
private uploadArtifact;
|
||||
private createArtifact;
|
||||
private getOrCreateRelease;
|
||||
private getRelease;
|
||||
private createRelease;
|
||||
deleteRelease(releaseId: string): Promise<void>;
|
||||
toString(): string;
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.KeygenPublisher = void 0;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const builder_util_runtime_1 = require("builder-util-runtime");
|
||||
const filename_1 = require("builder-util/out/filename");
|
||||
const httpPublisher_1 = require("./httpPublisher");
|
||||
class KeygenPublisher extends httpPublisher_1.HttpPublisher {
|
||||
get hostname() {
|
||||
return this.info.host || this.defaultHostname;
|
||||
}
|
||||
constructor(context, info, version) {
|
||||
super(context);
|
||||
this.providerName = "keygen";
|
||||
this.defaultHostname = "api.keygen.sh";
|
||||
const token = process.env.KEYGEN_TOKEN;
|
||||
if ((0, builder_util_1.isEmptyOrSpaces)(token)) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`Keygen token is not set using env "KEYGEN_TOKEN" (see https://www.electron.build/publish#KeygenOptions)`);
|
||||
}
|
||||
this.info = info;
|
||||
this.auth = `Bearer ${token.trim()}`;
|
||||
this.version = version;
|
||||
this.basePath = `/v1/accounts/${this.info.account}`;
|
||||
}
|
||||
doUpload(fileName, _arch, dataLength, requestProcessor, _file) {
|
||||
return builder_util_runtime_1.HttpExecutor.retryOnServerError(async () => {
|
||||
const { data, errors } = await this.getOrCreateRelease();
|
||||
if (errors) {
|
||||
throw new Error(`Keygen - Creating release returned errors: ${JSON.stringify(errors)}`);
|
||||
}
|
||||
await this.uploadArtifact(data.id, fileName, dataLength, requestProcessor);
|
||||
return data.id;
|
||||
});
|
||||
}
|
||||
async uploadArtifact(releaseId, fileName, dataLength, requestProcessor) {
|
||||
const { data, errors } = await this.createArtifact(releaseId, fileName, dataLength);
|
||||
if (errors) {
|
||||
throw new Error(`Keygen - Creating artifact returned errors: ${JSON.stringify(errors)}`);
|
||||
}
|
||||
// Follow the redirect and upload directly to S3-equivalent storage provider
|
||||
const url = new URL(data.links.redirect);
|
||||
const upload = {
|
||||
hostname: url.hostname,
|
||||
path: url.pathname + url.search,
|
||||
headers: {
|
||||
"Content-Length": dataLength,
|
||||
},
|
||||
timeout: this.info.timeout || undefined,
|
||||
};
|
||||
await builder_util_1.httpExecutor.doApiRequest((0, builder_util_runtime_1.configureRequestOptions)(upload, null, "PUT"), this.context.cancellationToken, requestProcessor);
|
||||
}
|
||||
async createArtifact(releaseId, fileName, dataLength) {
|
||||
const upload = {
|
||||
hostname: this.hostname,
|
||||
path: `${this.basePath}/artifacts`,
|
||||
headers: {
|
||||
"Content-Type": "application/vnd.api+json",
|
||||
Accept: "application/vnd.api+json",
|
||||
"Keygen-Version": "1.1",
|
||||
Prefer: "no-redirect",
|
||||
},
|
||||
timeout: this.info.timeout || undefined,
|
||||
};
|
||||
const data = {
|
||||
type: "artifacts",
|
||||
attributes: {
|
||||
filename: fileName,
|
||||
filetype: (0, filename_1.getCompleteExtname)(fileName),
|
||||
filesize: dataLength,
|
||||
platform: this.info.platform,
|
||||
},
|
||||
relationships: {
|
||||
release: {
|
||||
data: {
|
||||
type: "releases",
|
||||
id: releaseId,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
builder_util_1.log.debug({ data: JSON.stringify(data) }, "Keygen create artifact");
|
||||
return (0, builder_util_runtime_1.parseJson)(builder_util_1.httpExecutor.request((0, builder_util_runtime_1.configureRequestOptions)(upload, this.auth, "POST"), this.context.cancellationToken, { data }));
|
||||
}
|
||||
async getOrCreateRelease() {
|
||||
try {
|
||||
// First, we'll attempt to fetch the release.
|
||||
return await this.getRelease();
|
||||
}
|
||||
catch (e) {
|
||||
if (e.statusCode !== 404) {
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
// Next, if the release doesn't exist, we'll attempt to create it.
|
||||
return await this.createRelease();
|
||||
}
|
||||
catch (e) {
|
||||
if (e.statusCode !== 409 && e.statusCode !== 422) {
|
||||
throw e;
|
||||
}
|
||||
// Lastly, when a conflict occurs (in the case of parallel uploads),
|
||||
// we'll try to fetch it one last time.
|
||||
return this.getRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
async getRelease() {
|
||||
const req = {
|
||||
hostname: this.hostname,
|
||||
path: `${this.basePath}/releases/${this.version}?product=${this.info.product}`,
|
||||
headers: {
|
||||
Accept: "application/vnd.api+json",
|
||||
"Keygen-Version": "1.1",
|
||||
},
|
||||
timeout: this.info.timeout || undefined,
|
||||
};
|
||||
return (0, builder_util_runtime_1.parseJson)(builder_util_1.httpExecutor.request((0, builder_util_runtime_1.configureRequestOptions)(req, this.auth, "GET"), this.context.cancellationToken, null));
|
||||
}
|
||||
async createRelease() {
|
||||
const req = {
|
||||
hostname: this.hostname,
|
||||
path: `${this.basePath}/releases`,
|
||||
headers: {
|
||||
"Content-Type": "application/vnd.api+json",
|
||||
Accept: "application/vnd.api+json",
|
||||
"Keygen-Version": "1.1",
|
||||
},
|
||||
timeout: this.info.timeout || undefined,
|
||||
};
|
||||
const data = {
|
||||
type: "releases",
|
||||
attributes: {
|
||||
version: this.version,
|
||||
channel: this.info.channel || "stable",
|
||||
status: "PUBLISHED",
|
||||
},
|
||||
relationships: {
|
||||
product: {
|
||||
data: {
|
||||
type: "products",
|
||||
id: this.info.product,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
builder_util_1.log.debug({ data: JSON.stringify(data) }, "Keygen create release");
|
||||
return (0, builder_util_runtime_1.parseJson)(builder_util_1.httpExecutor.request((0, builder_util_runtime_1.configureRequestOptions)(req, this.auth, "POST"), this.context.cancellationToken, { data }));
|
||||
}
|
||||
async deleteRelease(releaseId) {
|
||||
const req = {
|
||||
hostname: this.hostname,
|
||||
path: `${this.basePath}/releases/${releaseId}`,
|
||||
headers: {
|
||||
Accept: "application/vnd.api+json",
|
||||
"Keygen-Version": "1.1",
|
||||
},
|
||||
timeout: this.info.timeout || undefined,
|
||||
};
|
||||
await builder_util_1.httpExecutor.request((0, builder_util_runtime_1.configureRequestOptions)(req, this.auth, "DELETE"), this.context.cancellationToken);
|
||||
}
|
||||
toString() {
|
||||
const { account, product, platform } = this.info;
|
||||
return `Keygen (account: ${account}, product: ${product}, platform: ${platform}, version: ${this.version})`;
|
||||
}
|
||||
}
|
||||
exports.KeygenPublisher = KeygenPublisher;
|
||||
//# sourceMappingURL=keygenPublisher.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+12
@@ -0,0 +1,12 @@
|
||||
import { ProgressBar } from "./progress";
|
||||
export declare class MultiProgress {
|
||||
private readonly stream;
|
||||
private cursor;
|
||||
private totalLines;
|
||||
private isLogListenerAdded;
|
||||
private barCount;
|
||||
createBar(format: string, options: any): ProgressBar;
|
||||
private allocateLines;
|
||||
private moveCursor;
|
||||
terminate(): void;
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.MultiProgress = void 0;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const progress_1 = require("./progress");
|
||||
class MultiProgress {
|
||||
constructor() {
|
||||
this.stream = process.stdout;
|
||||
this.cursor = 0;
|
||||
this.totalLines = 0;
|
||||
this.isLogListenerAdded = false;
|
||||
this.barCount = 0;
|
||||
}
|
||||
createBar(format, options) {
|
||||
options.stream = this.stream;
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const manager = this;
|
||||
class MultiProgressBar extends progress_1.ProgressBar {
|
||||
constructor(format, options) {
|
||||
super(format, options);
|
||||
this.index = -1;
|
||||
}
|
||||
render() {
|
||||
if (this.index === -1) {
|
||||
this.index = manager.totalLines;
|
||||
manager.allocateLines(1);
|
||||
}
|
||||
else {
|
||||
manager.moveCursor(this.index);
|
||||
}
|
||||
super.render();
|
||||
if (!manager.isLogListenerAdded) {
|
||||
manager.isLogListenerAdded = true;
|
||||
(0, builder_util_1.setPrinter)(message => {
|
||||
let newLineCount = 0;
|
||||
let newLineIndex = message.indexOf("\n");
|
||||
while (newLineIndex > -1) {
|
||||
newLineCount++;
|
||||
newLineIndex = message.indexOf("\n", ++newLineIndex);
|
||||
}
|
||||
manager.allocateLines(newLineCount + 1);
|
||||
manager.stream.write(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
terminate() {
|
||||
manager.barCount--;
|
||||
if (manager.barCount === 0 && manager.totalLines > 0) {
|
||||
manager.allocateLines(1);
|
||||
manager.totalLines = 0;
|
||||
manager.cursor = 0;
|
||||
(0, builder_util_1.setPrinter)(null);
|
||||
manager.isLogListenerAdded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
const bar = new MultiProgressBar(format, options);
|
||||
this.barCount++;
|
||||
return bar;
|
||||
}
|
||||
allocateLines(count) {
|
||||
this.stream.moveCursor(0, this.totalLines - 1);
|
||||
// if cursor pointed to previous line where \n is already printed, another \n is ignored, so, we can simply print it
|
||||
this.stream.write("\n");
|
||||
this.totalLines += count;
|
||||
this.cursor = this.totalLines - 1;
|
||||
}
|
||||
moveCursor(index) {
|
||||
this.stream.moveCursor(0, index - this.cursor);
|
||||
this.cursor = index;
|
||||
}
|
||||
terminate() {
|
||||
this.moveCursor(this.totalLines);
|
||||
this.stream.clearLine();
|
||||
this.stream.cursorTo(0);
|
||||
}
|
||||
}
|
||||
exports.MultiProgress = MultiProgress;
|
||||
//# sourceMappingURL=multiProgress.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"multiProgress.js","sourceRoot":"","sources":["../src/multiProgress.ts"],"names":[],"mappings":"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","sourcesContent":["import { setPrinter } from \"builder-util\"\nimport { ProgressBar } from \"./progress\"\n\nexport class MultiProgress {\n private readonly stream = process.stdout as any\n private cursor = 0\n\n private totalLines = 0\n\n private isLogListenerAdded = false\n\n private barCount = 0\n\n createBar(format: string, options: any): ProgressBar {\n options.stream = this.stream\n\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n const manager = this\n class MultiProgressBar extends ProgressBar {\n private index = -1\n\n constructor(format: string, options: any) {\n super(format, options)\n }\n\n render() {\n if (this.index === -1) {\n this.index = manager.totalLines\n manager.allocateLines(1)\n } else {\n manager.moveCursor(this.index)\n }\n\n super.render()\n\n if (!manager.isLogListenerAdded) {\n manager.isLogListenerAdded = true\n setPrinter(message => {\n let newLineCount = 0\n let newLineIndex = message.indexOf(\"\\n\")\n while (newLineIndex > -1) {\n newLineCount++\n newLineIndex = message.indexOf(\"\\n\", ++newLineIndex)\n }\n\n manager.allocateLines(newLineCount + 1)\n manager.stream.write(message)\n })\n }\n }\n\n terminate() {\n manager.barCount--\n if (manager.barCount === 0 && manager.totalLines > 0) {\n manager.allocateLines(1)\n manager.totalLines = 0\n manager.cursor = 0\n setPrinter(null)\n manager.isLogListenerAdded = false\n }\n }\n }\n\n const bar = new MultiProgressBar(format, options)\n this.barCount++\n return bar\n }\n\n private allocateLines(count: number) {\n this.stream.moveCursor(0, this.totalLines - 1)\n // if cursor pointed to previous line where \\n is already printed, another \\n is ignored, so, we can simply print it\n this.stream.write(\"\\n\")\n this.totalLines += count\n this.cursor = this.totalLines - 1\n }\n\n private moveCursor(index: number) {\n this.stream.moveCursor(0, index - this.cursor)\n this.cursor = index\n }\n\n terminate() {\n this.moveCursor(this.totalLines)\n this.stream.clearLine()\n this.stream.cursorTo(0)\n }\n}\n"]}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*!
|
||||
* node-progress
|
||||
* Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
|
||||
* MIT Licensed
|
||||
*/
|
||||
export declare abstract class ProgressBar {
|
||||
private readonly format;
|
||||
private readonly stream;
|
||||
private current;
|
||||
total: number;
|
||||
private readonly width;
|
||||
private chars;
|
||||
private tokens;
|
||||
private lastDraw;
|
||||
private start;
|
||||
private complete;
|
||||
/**
|
||||
* Initialize a `ProgressBar` with the given `fmt` string and `options` or`total`.
|
||||
*
|
||||
* Options:
|
||||
* - `curr` current completed index
|
||||
* - `total` total number of ticks to complete
|
||||
* - `width` the displayed width of the progress bar defaulting to total
|
||||
* - `stream` the output stream defaulting to stderr
|
||||
* - `head` head character defaulting to complete character
|
||||
* - `complete` completion character defaulting to "="
|
||||
* - `incomplete` incomplete character defaulting to "-"
|
||||
* - `renderThrottle` minimum time between updates in milliseconds defaulting to 16
|
||||
* - `callback` optional function to call when the progress bar completes
|
||||
* - `clear` will clear the progress bar upon termination
|
||||
*
|
||||
* Tokens:
|
||||
* - `:bar` the progress bar itself
|
||||
* - `:current` current tick number
|
||||
* - `:total` total ticks
|
||||
* - `:elapsed` time elapsed in seconds
|
||||
* - `:percent` completion percentage
|
||||
* - `:eta` eta in seconds
|
||||
* - `:rate` rate of ticks per second
|
||||
*/
|
||||
constructor(format: string, options?: any);
|
||||
/**
|
||||
* "tick" the progress bar with optional `len` and optional `tokens`.
|
||||
*/
|
||||
tick(delta: number): void;
|
||||
set currentAmount(value: number);
|
||||
render(): void;
|
||||
/**
|
||||
* "update" the progress bar to represent an exact percentage.
|
||||
* The ratio (between 0 and 1) specified will be multiplied by `total` and
|
||||
* floored, representing the closest available "tick." For example, if a
|
||||
* progress bar has a length of 3 and `update(0.5)` is called, the progress
|
||||
* will be set to 1.
|
||||
*
|
||||
* A ratio of 0.5 will attempt to set the progress to halfway.
|
||||
*/
|
||||
update(ratio: number): void;
|
||||
/**
|
||||
* "interrupt" the progress bar and write a message above it.
|
||||
*/
|
||||
interrupt(message: string): void;
|
||||
abstract terminate(): void;
|
||||
}
|
||||
export declare class ProgressCallback {
|
||||
private readonly progressBar;
|
||||
private start;
|
||||
private nextUpdate;
|
||||
constructor(progressBar: ProgressBar);
|
||||
update(transferred: number, total: number): void;
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
"use strict";
|
||||
/*!
|
||||
* node-progress
|
||||
* Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
|
||||
* MIT Licensed
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ProgressCallback = exports.ProgressBar = void 0;
|
||||
class ProgressBar {
|
||||
/**
|
||||
* Initialize a `ProgressBar` with the given `fmt` string and `options` or`total`.
|
||||
*
|
||||
* Options:
|
||||
* - `curr` current completed index
|
||||
* - `total` total number of ticks to complete
|
||||
* - `width` the displayed width of the progress bar defaulting to total
|
||||
* - `stream` the output stream defaulting to stderr
|
||||
* - `head` head character defaulting to complete character
|
||||
* - `complete` completion character defaulting to "="
|
||||
* - `incomplete` incomplete character defaulting to "-"
|
||||
* - `renderThrottle` minimum time between updates in milliseconds defaulting to 16
|
||||
* - `callback` optional function to call when the progress bar completes
|
||||
* - `clear` will clear the progress bar upon termination
|
||||
*
|
||||
* Tokens:
|
||||
* - `:bar` the progress bar itself
|
||||
* - `:current` current tick number
|
||||
* - `:total` total ticks
|
||||
* - `:elapsed` time elapsed in seconds
|
||||
* - `:percent` completion percentage
|
||||
* - `:eta` eta in seconds
|
||||
* - `:rate` rate of ticks per second
|
||||
*/
|
||||
constructor(format, options = {}) {
|
||||
this.format = format;
|
||||
this.current = 0;
|
||||
this.total = 0;
|
||||
this.tokens = null;
|
||||
this.lastDraw = "";
|
||||
this.start = 0;
|
||||
this.complete = false;
|
||||
this.stream = options.stream || process.stderr;
|
||||
this.total = options.total;
|
||||
this.width = options.width || this.total;
|
||||
this.chars = {
|
||||
complete: options.complete || "=",
|
||||
incomplete: options.incomplete || "-",
|
||||
head: options.head || options.complete || "=",
|
||||
};
|
||||
}
|
||||
/**
|
||||
* "tick" the progress bar with optional `len` and optional `tokens`.
|
||||
*/
|
||||
tick(delta) {
|
||||
this.currentAmount = this.current + delta;
|
||||
}
|
||||
set currentAmount(value) {
|
||||
this.current = value;
|
||||
if (this.complete) {
|
||||
return;
|
||||
}
|
||||
this.render();
|
||||
if (this.current >= this.total) {
|
||||
this.complete = true;
|
||||
this.terminate();
|
||||
}
|
||||
}
|
||||
render() {
|
||||
// start time for eta
|
||||
if (this.start === 0) {
|
||||
this.start = Date.now();
|
||||
}
|
||||
const ratio = Math.min(Math.max(this.current / this.total, 0), 1);
|
||||
const percent = ratio * 100;
|
||||
const elapsed = Date.now() - this.start;
|
||||
const eta = percent === 100 ? 0 : elapsed * (this.total / this.current - 1);
|
||||
const rate = this.current / (elapsed / 1000);
|
||||
/* populate the bar template with percentages and timestamps */
|
||||
let str = this.format
|
||||
.replace(":current", this.current.toString())
|
||||
.replace(":total", this.total.toString())
|
||||
.replace(":elapsed", isNaN(elapsed) ? "0.0" : (elapsed / 1000).toFixed(1))
|
||||
.replace(":eta", isNaN(eta) || !isFinite(eta) ? "0.0" : (eta / 1000).toFixed(1))
|
||||
.replace(":percent", percent.toFixed(0) + "%")
|
||||
.replace(":rate", Math.round(rate).toString());
|
||||
// compute the available space (non-zero) for the bar
|
||||
let availableSpace = Math.max(0, this.stream.columns - str.replace(":bar", "").length);
|
||||
if (availableSpace && process.platform === "win32") {
|
||||
availableSpace -= 1;
|
||||
}
|
||||
const width = Math.min(this.width, availableSpace);
|
||||
const completeLength = Math.round(width * ratio);
|
||||
let complete = Array(Math.max(0, completeLength + 1)).join(this.chars.complete);
|
||||
const incomplete = Array(Math.max(0, width - completeLength + 1)).join(this.chars.incomplete);
|
||||
/* add head to the complete string */
|
||||
if (completeLength > 0) {
|
||||
complete = `${complete.slice(0, -1)}${this.chars.head}`;
|
||||
}
|
||||
/* fill in the actual progress bar */
|
||||
str = str.replace(":bar", complete + incomplete);
|
||||
/* replace the extra tokens */
|
||||
if (this.tokens != null) {
|
||||
for (const key of Object.keys(this.tokens)) {
|
||||
str = str.replace(`:${key}`, this.tokens[key]);
|
||||
}
|
||||
}
|
||||
if (this.lastDraw !== str) {
|
||||
this.stream.cursorTo(0);
|
||||
this.stream.write(str);
|
||||
this.stream.clearLine(1);
|
||||
this.lastDraw = str;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* "update" the progress bar to represent an exact percentage.
|
||||
* The ratio (between 0 and 1) specified will be multiplied by `total` and
|
||||
* floored, representing the closest available "tick." For example, if a
|
||||
* progress bar has a length of 3 and `update(0.5)` is called, the progress
|
||||
* will be set to 1.
|
||||
*
|
||||
* A ratio of 0.5 will attempt to set the progress to halfway.
|
||||
*/
|
||||
update(ratio) {
|
||||
const goal = Math.floor(ratio * this.total);
|
||||
const delta = goal - this.current;
|
||||
this.tick(delta);
|
||||
}
|
||||
/**
|
||||
* "interrupt" the progress bar and write a message above it.
|
||||
*/
|
||||
interrupt(message) {
|
||||
// clear the current line
|
||||
const stream = this.stream;
|
||||
stream.clearLine();
|
||||
// move the cursor to the start of the line
|
||||
stream.cursorTo(0);
|
||||
// write the message text
|
||||
stream.write(message);
|
||||
// terminate the line after writing the message
|
||||
stream.write("\n");
|
||||
// re-display the progress bar with its lastDraw
|
||||
stream.write(this.lastDraw);
|
||||
}
|
||||
}
|
||||
exports.ProgressBar = ProgressBar;
|
||||
class ProgressCallback {
|
||||
constructor(progressBar) {
|
||||
this.progressBar = progressBar;
|
||||
this.start = Date.now();
|
||||
this.nextUpdate = this.start + 1000;
|
||||
}
|
||||
update(transferred, total) {
|
||||
const now = Date.now();
|
||||
if (now >= this.nextUpdate || transferred >= total) {
|
||||
this.nextUpdate = now + 1000;
|
||||
this.progressBar.total = total;
|
||||
this.progressBar.currentAmount = transferred;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.ProgressCallback = ProgressCallback;
|
||||
//# sourceMappingURL=progress.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+14
@@ -0,0 +1,14 @@
|
||||
import { PublishProvider } from "builder-util-runtime";
|
||||
import { Stats } from "fs-extra";
|
||||
import { PublishContext, UploadTask } from ".";
|
||||
import { ProgressBar } from "./progress";
|
||||
export declare abstract class Publisher {
|
||||
protected readonly context: PublishContext;
|
||||
protected constructor(context: PublishContext);
|
||||
abstract get providerName(): PublishProvider;
|
||||
abstract upload(task: UploadTask): Promise<any>;
|
||||
protected createProgressBar(fileName: string, size: number): ProgressBar | null;
|
||||
protected createReadStreamAndProgressBar(file: string, fileStat: Stats, progressBar: ProgressBar | null, reject: (error: Error) => void): NodeJS.ReadableStream;
|
||||
abstract toString(): string;
|
||||
}
|
||||
export declare function getCiTag(): string | null;
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Publisher = void 0;
|
||||
exports.getCiTag = getCiTag;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const builder_util_runtime_1 = require("builder-util-runtime");
|
||||
const chalk = require("chalk");
|
||||
const fs_extra_1 = require("fs-extra");
|
||||
const progressBarOptions = {
|
||||
incomplete: " ",
|
||||
width: 20,
|
||||
};
|
||||
class Publisher {
|
||||
constructor(context) {
|
||||
this.context = context;
|
||||
}
|
||||
createProgressBar(fileName, size) {
|
||||
builder_util_1.log.info({ file: fileName, provider: this.providerName }, "uploading");
|
||||
if (this.context.progress == null || size < 512 * 1024) {
|
||||
return null;
|
||||
}
|
||||
return this.context.progress.createBar(`${" ".repeat(builder_util_1.PADDING + 2)}[:bar] :percent :etas | ${chalk.green(fileName)} to ${this.providerName}`, {
|
||||
total: size,
|
||||
...progressBarOptions,
|
||||
});
|
||||
}
|
||||
createReadStreamAndProgressBar(file, fileStat, progressBar, reject) {
|
||||
const fileInputStream = (0, fs_extra_1.createReadStream)(file);
|
||||
fileInputStream.on("error", reject);
|
||||
if (progressBar == null) {
|
||||
return fileInputStream;
|
||||
}
|
||||
else {
|
||||
const progressStream = new builder_util_runtime_1.ProgressCallbackTransform(fileStat.size, this.context.cancellationToken, it => progressBar.tick(it.delta));
|
||||
progressStream.on("error", reject);
|
||||
return fileInputStream.pipe(progressStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.Publisher = Publisher;
|
||||
function getCiTag() {
|
||||
const tag = process.env.TRAVIS_TAG ||
|
||||
process.env.APPVEYOR_REPO_TAG_NAME ||
|
||||
process.env.CIRCLE_TAG ||
|
||||
process.env.BITRISE_GIT_TAG ||
|
||||
process.env.CI_BUILD_TAG || // deprecated, GitLab uses `CI_COMMIT_TAG` instead
|
||||
process.env.CI_COMMIT_TAG ||
|
||||
process.env.BITBUCKET_TAG ||
|
||||
(process.env.GITHUB_REF_TYPE === "tag" ? process.env.GITHUB_REF_NAME : null);
|
||||
return tag != null && tag.length > 0 ? tag : null;
|
||||
}
|
||||
//# sourceMappingURL=publisher.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"publisher.js","sourceRoot":"","sources":["../src/publisher.ts"],"names":[],"mappings":";;;AA8CA,4BAWC;AAzDD,+CAA2C;AAC3C,+DAAiF;AACjF,+BAA8B;AAC9B,uCAAkD;AAIlD,MAAM,kBAAkB,GAAG;IACzB,UAAU,EAAE,GAAG;IACf,KAAK,EAAE,EAAE;CACV,CAAA;AAED,MAAsB,SAAS;IAC7B,YAAyC,OAAuB;QAAvB,YAAO,GAAP,OAAO,CAAgB;IAAG,CAAC;IAM1D,iBAAiB,CAAC,QAAgB,EAAE,IAAY;QACxD,kBAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,EAAE,WAAW,CAAC,CAAA;QACtE,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,IAAI,EAAE,CAAC;YACvD,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,sBAAO,GAAG,CAAC,CAAC,2BAA2B,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,IAAI,CAAC,YAAY,EAAE,EAAE;YAC3I,KAAK,EAAE,IAAI;YACX,GAAG,kBAAkB;SACtB,CAAC,CAAA;IACJ,CAAC;IAES,8BAA8B,CAAC,IAAY,EAAE,QAAe,EAAE,WAA+B,EAAE,MAA8B;QACrI,MAAM,eAAe,GAAG,IAAA,2BAAgB,EAAC,IAAI,CAAC,CAAA;QAC9C,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAEnC,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;YACxB,OAAO,eAAe,CAAA;QACxB,CAAC;aAAM,CAAC;YACN,MAAM,cAAc,GAAG,IAAI,gDAAyB,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;YACrI,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;YAClC,OAAO,eAAe,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QAC7C,CAAC;IACH,CAAC;CAGF;AAhCD,8BAgCC;AAED,SAAgB,QAAQ;IACtB,MAAM,GAAG,GACP,OAAO,CAAC,GAAG,CAAC,UAAU;QACtB,OAAO,CAAC,GAAG,CAAC,sBAAsB;QAClC,OAAO,CAAC,GAAG,CAAC,UAAU;QACtB,OAAO,CAAC,GAAG,CAAC,eAAe;QAC3B,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,kDAAkD;QAC9E,OAAO,CAAC,GAAG,CAAC,aAAa;QACzB,OAAO,CAAC,GAAG,CAAC,aAAa;QACzB,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IAC9E,OAAO,GAAG,IAAI,IAAI,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;AACnD,CAAC","sourcesContent":["import { log, PADDING } from \"builder-util\"\nimport { ProgressCallbackTransform, PublishProvider } from \"builder-util-runtime\"\nimport * as chalk from \"chalk\"\nimport { createReadStream, Stats } from \"fs-extra\"\nimport { PublishContext, UploadTask } from \".\"\nimport { ProgressBar } from \"./progress\"\n\nconst progressBarOptions = {\n incomplete: \" \",\n width: 20,\n}\n\nexport abstract class Publisher {\n protected constructor(protected readonly context: PublishContext) {}\n\n abstract get providerName(): PublishProvider\n\n abstract upload(task: UploadTask): Promise<any>\n\n protected createProgressBar(fileName: string, size: number): ProgressBar | null {\n log.info({ file: fileName, provider: this.providerName }, \"uploading\")\n if (this.context.progress == null || size < 512 * 1024) {\n return null\n }\n return this.context.progress.createBar(`${\" \".repeat(PADDING + 2)}[:bar] :percent :etas | ${chalk.green(fileName)} to ${this.providerName}`, {\n total: size,\n ...progressBarOptions,\n })\n }\n\n protected createReadStreamAndProgressBar(file: string, fileStat: Stats, progressBar: ProgressBar | null, reject: (error: Error) => void): NodeJS.ReadableStream {\n const fileInputStream = createReadStream(file)\n fileInputStream.on(\"error\", reject)\n\n if (progressBar == null) {\n return fileInputStream\n } else {\n const progressStream = new ProgressCallbackTransform(fileStat.size, this.context.cancellationToken, it => progressBar.tick(it.delta))\n progressStream.on(\"error\", reject)\n return fileInputStream.pipe(progressStream)\n }\n }\n\n abstract toString(): string\n}\n\nexport function getCiTag() {\n const tag =\n process.env.TRAVIS_TAG ||\n process.env.APPVEYOR_REPO_TAG_NAME ||\n process.env.CIRCLE_TAG ||\n process.env.BITRISE_GIT_TAG ||\n process.env.CI_BUILD_TAG || // deprecated, GitLab uses `CI_COMMIT_TAG` instead\n process.env.CI_COMMIT_TAG ||\n process.env.BITBUCKET_TAG ||\n (process.env.GITHUB_REF_TYPE === \"tag\" ? process.env.GITHUB_REF_NAME : null)\n return tag != null && tag.length > 0 ? tag : null\n}\n"]}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { BaseS3Options } from "builder-util-runtime";
|
||||
import { PublishContext, UploadTask } from "..";
|
||||
import { Publisher } from "../publisher";
|
||||
export declare abstract class BaseS3Publisher extends Publisher {
|
||||
private options;
|
||||
protected constructor(context: PublishContext, options: BaseS3Options);
|
||||
protected abstract getBucketName(): string;
|
||||
protected configureS3Options(args: Array<string>): void;
|
||||
upload(task: UploadTask): Promise<any>;
|
||||
toString(): string;
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BaseS3Publisher = void 0;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const promises_1 = require("fs/promises");
|
||||
const path = require("path");
|
||||
const publisher_1 = require("../publisher");
|
||||
class BaseS3Publisher extends publisher_1.Publisher {
|
||||
constructor(context, options) {
|
||||
super(context);
|
||||
this.options = options;
|
||||
}
|
||||
configureS3Options(args) {
|
||||
// if explicitly set to null, do not add
|
||||
if (this.options.acl !== null) {
|
||||
args.push("--acl", this.options.acl || "public-read");
|
||||
}
|
||||
}
|
||||
// http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/s3-example-creating-buckets.html
|
||||
async upload(task) {
|
||||
const fileName = path.basename(task.file);
|
||||
const cancellationToken = this.context.cancellationToken;
|
||||
const target = (this.options.path == null ? "" : `${this.options.path}/`) + fileName;
|
||||
const args = ["publish-s3", "--bucket", this.getBucketName(), "--key", target, "--file", task.file];
|
||||
this.configureS3Options(args);
|
||||
if (process.env.__TEST_S3_PUBLISHER__ != null) {
|
||||
const testFile = path.join(process.env.__TEST_S3_PUBLISHER__, target);
|
||||
await (0, promises_1.mkdir)(path.dirname(testFile), { recursive: true });
|
||||
await (0, promises_1.symlink)(task.file, testFile);
|
||||
return;
|
||||
}
|
||||
// https://github.com/aws/aws-sdk-go/issues/279
|
||||
this.createProgressBar(fileName, -1);
|
||||
// if (progressBar != null) {
|
||||
// const callback = new ProgressCallback(progressBar)
|
||||
// uploader.on("progress", () => {
|
||||
// if (!cancellationToken.cancelled) {
|
||||
// callback.update(uploader.loaded, uploader.contentLength)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
return await cancellationToken.createPromise((resolve, reject, onCancel) => {
|
||||
(0, builder_util_1.executeAppBuilder)(args, process => {
|
||||
onCancel(() => {
|
||||
process.kill("SIGINT");
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
try {
|
||||
builder_util_1.log.debug({ provider: this.providerName, file: fileName, bucket: this.getBucketName() }, "uploaded");
|
||||
}
|
||||
finally {
|
||||
resolve(undefined);
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
toString() {
|
||||
return `${this.providerName} (bucket: ${this.getBucketName()})`;
|
||||
}
|
||||
}
|
||||
exports.BaseS3Publisher = BaseS3Publisher;
|
||||
//# sourceMappingURL=baseS3Publisher.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"baseS3Publisher.js","sourceRoot":"","sources":["../../src/s3/baseS3Publisher.ts"],"names":[],"mappings":"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","sourcesContent":["import { executeAppBuilder, log } from \"builder-util\"\nimport { BaseS3Options } from \"builder-util-runtime\"\nimport { mkdir, symlink } from \"fs/promises\"\nimport * as path from \"path\"\nimport { PublishContext, UploadTask } from \"..\"\nimport { Publisher } from \"../publisher\"\n\nexport abstract class BaseS3Publisher extends Publisher {\n protected constructor(\n context: PublishContext,\n private options: BaseS3Options\n ) {\n super(context)\n }\n\n protected abstract getBucketName(): string\n\n protected configureS3Options(args: Array<string>) {\n // if explicitly set to null, do not add\n if (this.options.acl !== null) {\n args.push(\"--acl\", this.options.acl || \"public-read\")\n }\n }\n\n // http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/s3-example-creating-buckets.html\n async upload(task: UploadTask): Promise<any> {\n const fileName = path.basename(task.file)\n const cancellationToken = this.context.cancellationToken\n\n const target = (this.options.path == null ? \"\" : `${this.options.path}/`) + fileName\n\n const args = [\"publish-s3\", \"--bucket\", this.getBucketName(), \"--key\", target, \"--file\", task.file]\n this.configureS3Options(args)\n\n if (process.env.__TEST_S3_PUBLISHER__ != null) {\n const testFile = path.join(process.env.__TEST_S3_PUBLISHER__, target)\n await mkdir(path.dirname(testFile), { recursive: true })\n await symlink(task.file, testFile)\n return\n }\n\n // https://github.com/aws/aws-sdk-go/issues/279\n this.createProgressBar(fileName, -1)\n // if (progressBar != null) {\n // const callback = new ProgressCallback(progressBar)\n // uploader.on(\"progress\", () => {\n // if (!cancellationToken.cancelled) {\n // callback.update(uploader.loaded, uploader.contentLength)\n // }\n // })\n // }\n\n return await cancellationToken.createPromise((resolve, reject, onCancel) => {\n executeAppBuilder(args, process => {\n onCancel(() => {\n process.kill(\"SIGINT\")\n })\n })\n .then(() => {\n try {\n log.debug({ provider: this.providerName, file: fileName, bucket: this.getBucketName() }, \"uploaded\")\n } finally {\n resolve(undefined)\n }\n })\n .catch(reject)\n })\n }\n\n toString() {\n return `${this.providerName} (bucket: ${this.getBucketName()})`\n }\n}\n"]}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { S3Options } from "builder-util-runtime";
|
||||
import { PublishContext } from "..";
|
||||
import { BaseS3Publisher } from "./baseS3Publisher";
|
||||
export declare class S3Publisher extends BaseS3Publisher {
|
||||
private readonly info;
|
||||
readonly providerName = "s3";
|
||||
constructor(context: PublishContext, info: S3Options);
|
||||
static checkAndResolveOptions(options: S3Options, channelFromAppVersion: string | null, errorIfCannot: boolean): Promise<void>;
|
||||
protected getBucketName(): string;
|
||||
protected configureS3Options(args: Array<string>): void;
|
||||
toString(): string;
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.S3Publisher = void 0;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const baseS3Publisher_1 = require("./baseS3Publisher");
|
||||
class S3Publisher extends baseS3Publisher_1.BaseS3Publisher {
|
||||
constructor(context, info) {
|
||||
super(context, info);
|
||||
this.info = info;
|
||||
this.providerName = "s3";
|
||||
}
|
||||
static async checkAndResolveOptions(options, channelFromAppVersion, errorIfCannot) {
|
||||
const bucket = options.bucket;
|
||||
if (bucket == null) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`Please specify "bucket" for "s3" publish provider`);
|
||||
}
|
||||
if (options.endpoint == null && bucket.includes(".") && options.region == null) {
|
||||
// on dotted bucket names, we need to use a path-based endpoint URL. Path-based endpoint URLs need to include the region.
|
||||
try {
|
||||
options.region = await (0, builder_util_1.executeAppBuilder)(["get-bucket-location", "--bucket", bucket]);
|
||||
}
|
||||
catch (e) {
|
||||
if (errorIfCannot) {
|
||||
throw e;
|
||||
}
|
||||
else {
|
||||
builder_util_1.log.warn(`cannot compute region for bucket (required because on dotted bucket names, we need to use a path-based endpoint URL): ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.channel == null && channelFromAppVersion != null) {
|
||||
options.channel = channelFromAppVersion;
|
||||
}
|
||||
if (options.endpoint != null && options.endpoint.endsWith("/")) {
|
||||
;
|
||||
options.endpoint = options.endpoint.slice(0, -1);
|
||||
}
|
||||
}
|
||||
getBucketName() {
|
||||
return this.info.bucket;
|
||||
}
|
||||
configureS3Options(args) {
|
||||
super.configureS3Options(args);
|
||||
if (this.info.endpoint != null) {
|
||||
args.push("--endpoint", this.info.endpoint);
|
||||
}
|
||||
if (this.info.region != null) {
|
||||
args.push("--region", this.info.region);
|
||||
}
|
||||
if (this.info.storageClass != null) {
|
||||
args.push("--storageClass", this.info.storageClass);
|
||||
}
|
||||
if (this.info.encryption != null) {
|
||||
args.push("--encryption", this.info.encryption);
|
||||
}
|
||||
if (this.info.forcePathStyle != null) {
|
||||
args.push("--forcePathStyle", this.info.forcePathStyle ? "true" : "false");
|
||||
}
|
||||
}
|
||||
toString() {
|
||||
const result = super.toString();
|
||||
const endpoint = this.info.endpoint;
|
||||
if (endpoint != null) {
|
||||
return result.substring(0, result.length - 1) + `, endpoint: ${endpoint})`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
exports.S3Publisher = S3Publisher;
|
||||
//# sourceMappingURL=s3Publisher.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+11
@@ -0,0 +1,11 @@
|
||||
import { SpacesOptions } from "builder-util-runtime";
|
||||
import { PublishContext } from "../";
|
||||
import { BaseS3Publisher } from "./baseS3Publisher";
|
||||
export declare class SpacesPublisher extends BaseS3Publisher {
|
||||
private readonly info;
|
||||
readonly providerName = "spaces";
|
||||
constructor(context: PublishContext, info: SpacesOptions);
|
||||
static checkAndResolveOptions(options: SpacesOptions, channelFromAppVersion: string | null, errorIfCannot: boolean): Promise<void>;
|
||||
protected getBucketName(): string;
|
||||
protected configureS3Options(args: Array<string>): void;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.SpacesPublisher = void 0;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const baseS3Publisher_1 = require("./baseS3Publisher");
|
||||
class SpacesPublisher extends baseS3Publisher_1.BaseS3Publisher {
|
||||
constructor(context, info) {
|
||||
super(context, info);
|
||||
this.info = info;
|
||||
this.providerName = "spaces";
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
static checkAndResolveOptions(options, channelFromAppVersion, errorIfCannot) {
|
||||
if (options.name == null) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`Please specify "name" for "spaces" publish provider (see https://www.electron.build/publish#spacesoptions)`);
|
||||
}
|
||||
if (options.region == null) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`Please specify "region" for "spaces" publish provider (see https://www.electron.build/publish#spacesoptions)`);
|
||||
}
|
||||
if (options.channel == null && channelFromAppVersion != null) {
|
||||
options.channel = channelFromAppVersion;
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
getBucketName() {
|
||||
return this.info.name;
|
||||
}
|
||||
configureS3Options(args) {
|
||||
super.configureS3Options(args);
|
||||
args.push("--endpoint", `${this.info.region}.digitaloceanspaces.com`);
|
||||
args.push("--region", this.info.region);
|
||||
const accessKey = process.env.DO_KEY_ID;
|
||||
const secretKey = process.env.DO_SECRET_KEY;
|
||||
if ((0, builder_util_1.isEmptyOrSpaces)(accessKey)) {
|
||||
throw new builder_util_1.InvalidConfigurationError("Please set env DO_KEY_ID (see https://www.electron.build/publish#spacesoptions)");
|
||||
}
|
||||
if ((0, builder_util_1.isEmptyOrSpaces)(secretKey)) {
|
||||
throw new builder_util_1.InvalidConfigurationError("Please set env DO_SECRET_KEY (see https://www.electron.build/publish#spacesoptions)");
|
||||
}
|
||||
args.push("--accessKey", accessKey);
|
||||
args.push("--secretKey", secretKey);
|
||||
}
|
||||
}
|
||||
exports.SpacesPublisher = SpacesPublisher;
|
||||
//# sourceMappingURL=spacesPublisher.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"spacesPublisher.js","sourceRoot":"","sources":["../../src/s3/spacesPublisher.ts"],"names":[],"mappings":";;;AAAA,+CAAyE;AAGzE,uDAAmD;AAEnD,MAAa,eAAgB,SAAQ,iCAAe;IAGlD,YACE,OAAuB,EACN,IAAmB;QAEpC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QAFH,SAAI,GAAJ,IAAI,CAAe;QAJ7B,iBAAY,GAAG,QAAQ,CAAA;IAOhC,CAAC;IAED,6DAA6D;IAC7D,MAAM,CAAC,sBAAsB,CAAC,OAAsB,EAAE,qBAAoC,EAAE,aAAsB;QAChH,IAAI,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;YACzB,MAAM,IAAI,wCAAyB,CAAC,4GAA4G,CAAC,CAAA;QACnJ,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;YAC3B,MAAM,IAAI,wCAAyB,CAAC,8GAA8G,CAAC,CAAA;QACrJ,CAAC;QAED,IAAI,OAAO,CAAC,OAAO,IAAI,IAAI,IAAI,qBAAqB,IAAI,IAAI,EAAE,CAAC;YAC7D,OAAO,CAAC,OAAO,GAAG,qBAAqB,CAAA;QACzC,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;IAC1B,CAAC;IAES,aAAa;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAA;IACvB,CAAC;IAES,kBAAkB,CAAC,IAAmB;QAC9C,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAA;QAE9B,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,yBAAyB,CAAC,CAAA;QACrE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAEvC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAA;QACvC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAA;QAC3C,IAAI,IAAA,8BAAe,EAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,wCAAyB,CAAC,iFAAiF,CAAC,CAAA;QACxH,CAAC;QACD,IAAI,IAAA,8BAAe,EAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,wCAAyB,CAAC,qFAAqF,CAAC,CAAA;QAC5H,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;QACnC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;IACrC,CAAC;CACF;AA9CD,0CA8CC","sourcesContent":["import { InvalidConfigurationError, isEmptyOrSpaces } from \"builder-util\"\nimport { SpacesOptions } from \"builder-util-runtime\"\nimport { PublishContext } from \"../\"\nimport { BaseS3Publisher } from \"./baseS3Publisher\"\n\nexport class SpacesPublisher extends BaseS3Publisher {\n readonly providerName = \"spaces\"\n\n constructor(\n context: PublishContext,\n private readonly info: SpacesOptions\n ) {\n super(context, info)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n static checkAndResolveOptions(options: SpacesOptions, channelFromAppVersion: string | null, errorIfCannot: boolean) {\n if (options.name == null) {\n throw new InvalidConfigurationError(`Please specify \"name\" for \"spaces\" publish provider (see https://www.electron.build/publish#spacesoptions)`)\n }\n if (options.region == null) {\n throw new InvalidConfigurationError(`Please specify \"region\" for \"spaces\" publish provider (see https://www.electron.build/publish#spacesoptions)`)\n }\n\n if (options.channel == null && channelFromAppVersion != null) {\n options.channel = channelFromAppVersion\n }\n return Promise.resolve()\n }\n\n protected getBucketName(): string {\n return this.info.name\n }\n\n protected configureS3Options(args: Array<string>): void {\n super.configureS3Options(args)\n\n args.push(\"--endpoint\", `${this.info.region}.digitaloceanspaces.com`)\n args.push(\"--region\", this.info.region)\n\n const accessKey = process.env.DO_KEY_ID\n const secretKey = process.env.DO_SECRET_KEY\n if (isEmptyOrSpaces(accessKey)) {\n throw new InvalidConfigurationError(\"Please set env DO_KEY_ID (see https://www.electron.build/publish#spacesoptions)\")\n }\n if (isEmptyOrSpaces(secretKey)) {\n throw new InvalidConfigurationError(\"Please set env DO_SECRET_KEY (see https://www.electron.build/publish#spacesoptions)\")\n }\n args.push(\"--accessKey\", accessKey)\n args.push(\"--secretKey\", secretKey)\n }\n}\n"]}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { SnapStoreOptions } from "builder-util-runtime/out/publishOptions";
|
||||
import { PublishContext, UploadTask } from ".";
|
||||
import { Publisher } from "./publisher";
|
||||
export declare class SnapStorePublisher extends Publisher {
|
||||
private options;
|
||||
readonly providerName = "snapStore";
|
||||
constructor(context: PublishContext, options: SnapStoreOptions);
|
||||
upload(task: UploadTask): Promise<any>;
|
||||
toString(): string;
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.SnapStorePublisher = void 0;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const path = require("path");
|
||||
const publisher_1 = require("./publisher");
|
||||
class SnapStorePublisher extends publisher_1.Publisher {
|
||||
constructor(context, options) {
|
||||
super(context);
|
||||
this.options = options;
|
||||
this.providerName = "snapStore";
|
||||
}
|
||||
upload(task) {
|
||||
this.createProgressBar(path.basename(task.file), -1);
|
||||
const args = ["publish-snap", "-f", task.file];
|
||||
let channels = this.options.channels;
|
||||
if (channels == null) {
|
||||
channels = ["edge"];
|
||||
}
|
||||
else {
|
||||
if (typeof channels === "string") {
|
||||
channels = channels.split(",");
|
||||
}
|
||||
}
|
||||
for (const channel of channels) {
|
||||
args.push("-c", channel);
|
||||
}
|
||||
return (0, builder_util_1.executeAppBuilder)(args);
|
||||
}
|
||||
toString() {
|
||||
return "Snap Store";
|
||||
}
|
||||
}
|
||||
exports.SnapStorePublisher = SnapStorePublisher;
|
||||
//# sourceMappingURL=snapStorePublisher.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"snapStorePublisher.js","sourceRoot":"","sources":["../src/snapStorePublisher.ts"],"names":[],"mappings":";;;AAAA,+CAAgD;AAEhD,6BAA4B;AAE5B,2CAAuC;AAEvC,MAAa,kBAAmB,SAAQ,qBAAS;IAG/C,YACE,OAAuB,EACf,OAAyB;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAA;QAFN,YAAO,GAAP,OAAO,CAAkB;QAJ1B,iBAAY,GAAG,WAAW,CAAA;IAOnC,CAAC;IAED,MAAM,CAAC,IAAgB;QACrB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAEpD,MAAM,IAAI,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAE9C,IAAI,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAA;QACpC,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,QAAQ,GAAG,CAAC,MAAM,CAAC,CAAA;QACrB,CAAC;aAAM,CAAC;YACN,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACjC,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAChC,CAAC;QACH,CAAC;QAED,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAC1B,CAAC;QAED,OAAO,IAAA,gCAAiB,EAAC,IAAI,CAAC,CAAA;IAChC,CAAC;IAED,QAAQ;QACN,OAAO,YAAY,CAAA;IACrB,CAAC;CACF;AAlCD,gDAkCC","sourcesContent":["import { executeAppBuilder } from \"builder-util\"\nimport { SnapStoreOptions } from \"builder-util-runtime/out/publishOptions\"\nimport * as path from \"path\"\nimport { PublishContext, UploadTask } from \".\"\nimport { Publisher } from \"./publisher\"\n\nexport class SnapStorePublisher extends Publisher {\n readonly providerName = \"snapStore\"\n\n constructor(\n context: PublishContext,\n private options: SnapStoreOptions\n ) {\n super(context)\n }\n\n upload(task: UploadTask): Promise<any> {\n this.createProgressBar(path.basename(task.file), -1)\n\n const args = [\"publish-snap\", \"-f\", task.file]\n\n let channels = this.options.channels\n if (channels == null) {\n channels = [\"edge\"]\n } else {\n if (typeof channels === \"string\") {\n channels = channels.split(\",\")\n }\n }\n\n for (const channel of channels) {\n args.push(\"-c\", channel)\n }\n\n return executeAppBuilder(args)\n }\n\n toString(): string {\n return \"Snap Store\"\n }\n}\n"]}
|
||||
Reference in New Issue
Block a user