Skip to content
Permalink
Browse files

feat(publisher): initial work on a publish command to sent make artif…

…acts to github

Makers now return an array of artifacts which the publish command will use to send them to GitHub
  • Loading branch information
MarshallOfSound committed Dec 28, 2016
1 parent f1cac74 commit 189cb0ccbbcc83065f8074125f4148e418f5ec17
@@ -66,6 +66,7 @@
"electron-packager": "^8.4.0",
"electron-winstaller": "^2.5.0",
"fs-promise": "^1.0.0",
"github": "^7.2.0",
"inquirer": "^2.0.0",
"log-symbols": "^1.0.2",
"node-gyp": "^3.4.0",
@@ -17,9 +17,10 @@ const main = async () => {
program
.version(require('../package.json').version)
.arguments('[cwd]')
.option('-s, --skip-package', 'Assume the app is already packaged')
.option('--skip-package', 'Assume the app is already packaged')
.option('-a, --arch [arch]', 'Target architecture')
.option('-p, --platform [platform]', 'Target build platform')
.allowUnknownOption(true)
.action((cwd) => {
if (!cwd) return;
if (path.isAbsolute(cwd) && fs.existsSync(cwd)) {
@@ -78,6 +79,7 @@ const main = async () => {

const packageJSON = await readPackageJSON(dir);
const appName = packageJSON.productName || packageJSON.name;
const outputs = [];

for (const targetArch of targetArchs) {
const packageDir = path.resolve(dir, `out/${appName}-${declaredPlatform}-${targetArch}`);
@@ -99,7 +101,7 @@ const main = async () => {
}
}
try {
await (maker.default || maker)(packageDir, appName, targetArch, forgeConfig, packageJSON);
outputs.push(await (maker.default || maker)(packageDir, appName, targetArch, forgeConfig, packageJSON));
} catch (err) {
makeSpinner.fail();
if (err) throw err;
@@ -108,6 +110,12 @@ const main = async () => {
makeSpinner.succeed();
}
}

return outputs;
};

main();
if (process.mainModule === module) {
main();
}

export default main;
@@ -0,0 +1,117 @@
import 'colors';
import fs from 'fs-promise';
import path from 'path';
import program from 'commander';
import ora from 'ora';

import './util/terminate';
import getForgeConfig from './util/forge-config';
import GitHub from './util/github';
import readPackageJSON from './util/read-package-json';
import resolveDir from './util/resolve-dir';

import make from './electron-forge-make';

const main = async () => {
const makeResults = await make();

let dir = process.cwd();
program
.version(require('../package.json').version)
.arguments('[cwd]')
.option('--auth-token', 'Provided GitHub authorization token')
.option('-t, --tag', 'The tag to publish to on GitHub')
.allowUnknownOption(true)
.action((cwd) => {
if (!cwd) return;
if (path.isAbsolute(cwd) && fs.existsSync(cwd)) {
dir = cwd;
} else if (fs.existsSync(path.resolve(dir, cwd))) {
dir = path.resolve(dir, cwd);
}
})
.parse(process.argv);

dir = await resolveDir(dir);
if (!dir) {
console.error('Failed to locate publishable Electron application'.red);
if (global._resolveError) global._resolveError();
process.exit(1);
}

const artifacts = makeResults.reduce((accum, arr) => {
accum.push(...arr);
return accum;
}, []);

const packageJSON = await readPackageJSON(dir);

const forgeConfig = await getForgeConfig(dir);

if (!(forgeConfig.github_repository && typeof forgeConfig.github_repository === 'object' &&
forgeConfig.github_repository.owner && forgeConfig.github_repository.name)) {
console.error('In order to publish you must set the "github_repository.owner" and "github_repository.name" properties in your forge config. See the docs for more info'.red); // eslint-disable-line
process.exit(1);
}

const github = new GitHub(program.authToken);

let release;
try {
release = (await github.getGitHub().repos.getReleases({
owner: forgeConfig.github_repository.owner,
repo: forgeConfig.github_repository.name,
per_page: 100,
})).find(testRelease => testRelease.tag_name === program.tag || `v${packageJSON.version}`);
} catch (err) {
if (err.code === 404) {
// Release does not exist, let's make it
release = await github.getGitHub().repos.createRelease({
owner: forgeConfig.github_repository.owner,
repo: forgeConfig.github_repository.name,
tag_name: program.tag || `v${packageJSON.version}`,
name: program.tag || `v${packageJSON.version}`,
draft: true,
});
} else {
// Unknown error
throw err;
}
}

let uploaded = 0;
const uploadSpinner = ora.ora(`Uploading Artifacts ${uploaded}/${artifacts.length}`).start();
const updateSpinner = () => {
uploadSpinner.text = `Uploading Artifacts ${uploaded}/${artifacts.length}`;
};

try {
await Promise.all(artifacts.map(artifactPath =>
new Promise(async (resolve) => {
const done = () => {
uploaded += 1;
updateSpinner();
resolve();
};
if (release.assets.find(asset => asset.name === path.basename(artifactPath))) {
return done();
}
await github.getGitHub().repos.uploadAsset({
owner: forgeConfig.github_repository.owner,
repo: forgeConfig.github_repository.name,
id: release.id,
filePath: artifactPath,
name: path.basename(artifactPath),
});
return done();
})
));
} catch (err) {
updateSpinner.fail();
throw err;
}

uploadSpinner.succeed();
};

main();
@@ -25,6 +25,7 @@ checkSystem()
.command('package', 'Package the current Electron application')
.command('make', 'Generate distributables for the current Electron application')
.command('start', 'Start the current Electron application')
.command('publish', 'Publish the current Electron application to GitHub')
.parse(process.argv);

config.reset();
@@ -5,7 +5,7 @@ import pify from 'pify';
import { ensureFile } from '../../util/ensure-output';

export default async (dir, appName, targetArch, forgeConfig, packageJSON) => { // eslint-disable-line
const outPath = path.resolve(dir, '../make', `${path.basename(dir)}.dmg`);
const outPath = path.resolve(dir, '../make', `${appName}.dmg`);
await ensureFile(outPath);
const dmgConfig = Object.assign({
overwrite: true,
@@ -15,4 +15,5 @@ export default async (dir, appName, targetArch, forgeConfig, packageJSON) => { /
out: path.dirname(outPath),
});
await pify(electronDMG)(dmgConfig);
return [outPath];
};
@@ -36,4 +36,5 @@ export default async (dir, appName, targetArch, forgeConfig, packageJSON) => { /
default:
throw new Error('Unrecognized platform');
}
return [zipPath];
};
@@ -27,4 +27,5 @@ export default async (dir, appName, targetArch, forgeConfig, packageJSON) => { /
const debianConfig = Object.assign({}, forgeConfig.electronInstallerDebian, debianDefaults);

await pify(installer)(debianConfig);
return [outPath];
};
@@ -27,4 +27,5 @@ export default async (dir, appName, targetArch, forgeConfig, packageJSON) => { /
const flatpakConfig = Object.assign({}, forgeConfig.electronInstallerFlatpak, flatpakDefaults);

await pify(installer)(flatpakConfig);
return [outPath];
};
@@ -27,4 +27,5 @@ export default async (dir, appName, targetArch, forgeConfig, packageJSON) => { /
const rpmConfig = Object.assign({}, forgeConfig.electronInstallerRedhat, rpmDefaults);

await pify(installer)(rpmConfig);
return [outPath];
};
@@ -1,4 +1,5 @@
import { createWindowsInstaller } from 'electron-winstaller';
import fs from 'fs-promise';
import path from 'path';

import { ensureDirectory } from '../../util/ensure-output';
@@ -7,9 +8,26 @@ export default async (dir, appName, targetArch, forgeConfig, packageJSON) => { /
const outPath = path.resolve(dir, `../make/squirrel.windows/${targetArch}`);
await ensureDirectory(outPath);

const winstallerConfig = Object.assign({}, forgeConfig.electronWinstallerConfig, {
const winstallerConfig = Object.assign({
name: packageJSON.name,
noMsi: true,
}, forgeConfig.electronWinstallerConfig, {
appDirectory: dir,
outputDirectory: outPath,
});
await createWindowsInstaller(winstallerConfig);
const artifacts = [
path.resolve(outPath, 'RELEASES'),
path.resolve(outPath, winstallerConfig.setupExe || `${appName}Setup.exe`),
path.resolve(outPath, `${winstallerConfig.name}-${packageJSON.version}-full.nupkg`),
];
const deltaPath = path.resolve(outPath, `${winstallerConfig.name}-${packageJSON.version}-delta.nupkg`);
if (winstallerConfig.remoteReleases || await fs.exists(deltaPath)) {
artifacts.push(deltaPath);
}
const msiPath = path.resolve(outPath, winstallerConfig.setupMsi || `${appName}Setup.msi`);
if (!winstallerConfig.noMsi && await fs.exists(msiPath)) {
artifacts.push(msiPath);
}
return artifacts;
};
@@ -0,0 +1,27 @@
import GitHubAPI from 'github';

export default class GitHub {
constructor(authToken) {
if (authToken) {
this.token = authToken;
} else if (process.env.GITHUB_TOKEN) {
this.token = process.env.GITHUB_TOKEN;
}
}

getGitHub() {
const github = new GitHubAPI({
protocol: 'https',
headers: {
'user-agent': 'Electron Forge',
},
});
if (this.token) {
github.authenticate({
type: 'token',
token: this.token,
});
}
return github;
}
}
@@ -6,13 +6,13 @@ const d = debug('electron-forge:lifecycle');

process.on('unhandledRejection', (err) => {
process.stdout.write('\n\nAn unhandled rejection has occurred inside Forge:\n');
console.error(colors.red(err.stack));
console.error(colors.red(err.stack || JSON.stringify(err)));
process.exit(1);
});

process.on('uncaughtException', (err) => {
process.stdout.write('\n\nAn unhandled exception has occurred inside Forge:\n');
console.error(colors.red(err.stack));
console.error(colors.red(err.stack || JSON.stringify(err)));
process.exit(1);
});

@@ -22,7 +22,11 @@
"name": ""
},
"electronInstallerDebian": {},
"electronInstallerRedhat": {}
"electronInstallerRedhat": {},
"github_repository": {
"owner": "",
"name": ""
}
}
}
}

0 comments on commit 189cb0c

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