Woongjae Lee
NHN Dooray - Frontend Team
2woongjae@gmail.com
#!/usr/bin/env node
// maintainer note - x.y.z-ab version in package.json -> x.y.z
var version = require('./package').version.replace(/-.*/, '')
var fs = require('fs')
var os = require('os')
var path = require('path')
var extract = require('extract-zip')
var download = require('electron-download')
var installedVersion = null
try {
installedVersion = fs.readFileSync(path.join(__dirname, 'dist', 'version'), 'utf-8').replace(/^v/, '')
} catch (ignored) {
// do nothing
}
var platformPath = getPlatformPath()
if (installedVersion === version && fs.existsSync(path.join(__dirname, platformPath))) {
process.exit(0)
}
// downloads if not cached
download({
cache: process.env.electron_config_cache,
version: version,
platform: process.env.npm_config_platform,
arch: process.env.npm_config_arch,
strictSSL: process.env.npm_config_strict_ssl === 'true',
force: process.env.force_no_cache === 'true',
quiet: ['info', 'verbose', 'silly', 'http'].indexOf(process.env.npm_config_loglevel) === -1
}, extractFile)
// unzips and makes path.txt point at the correct executable
function extractFile (err, zipPath) {
if (err) return onerror(err)
extract(zipPath, {dir: path.join(__dirname, 'dist')}, function (err) {
if (err) return onerror(err)
fs.writeFile(path.join(__dirname, 'path.txt'), platformPath, function (err) {
if (err) return onerror(err)
})
})
}
function onerror (err) {
throw err
}
function getPlatformPath () {
var platform = process.env.npm_config_platform || os.platform()
switch (platform) {
case 'darwin':
return 'dist/Electron.app/Contents/MacOS/Electron'
case 'freebsd':
case 'linux':
return 'dist/electron'
case 'win32':
return 'dist/electron.exe'
default:
throw new Error('Electron builds are not available on platform: ' + platform)
}
}
// On macOS
electron/Electron.app/Contents/Resources/app/
├── package.json
├── main.js
└── index.html
// On Windows and Linux
electron/resources/app
├── package.json
├── main.js
└── index.html
// On macOS
electron/Electron.app/Contents/Resources/app.asar
├── package.json
├── main.js
└── index.html
// On Windows and Linux
electron/resources/app.asar
├── package.json
├── main.js
└── index.html
// On macOS
electron/Electron.app/Contents/Resources/app/
├── package.json
├── main.js
└── index.html
// On Windows and Linux
electron/resources/app
├── package.json
├── main.js
└── index.html
// 프로젝트 폴더 (before packaging)
electron-packager-example/
├── node_modules/
├── icon.icns
├── package.json
├── package-lock.json
└── index.js
// 프로젝트 폴더 (after packaging)
electron-packager-example/
├── electron-packager-example-darwin-x64/
├── node_modules/
├── icon.icns
├── package.json
├── package-lock.json
└── index.js
// 패키징 된 app 폴더 (혹은 app.asar 파일)
electron-packager-example-darwin-x64/Contents/Resources/app/
├── icon.icns
├── package.json
├── package-lock.json
└── index.js
~/Project
➜ npm i electron-forge -g
npm WARN deprecated wrench@1.5.9: wrench.js is deprecated! You should check out fs-extra (https://github.com/jprichardson/node-fs-extra) for any operations you were using wrench for. Thanks for all the usage over the years.
npm WARN deprecated minimatch@0.3.0: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
/Users/mark/.nvm/versions/node/v8.9.0/bin/electron-forge-vscode-win -> /Users/mark/.nvm/versions/node/v8.9.0/lib/node_modules/electron-forge/script/vscode.cmd
/Users/mark/.nvm/versions/node/v8.9.0/bin/forge -> /Users/mark/.nvm/versions/node/v8.9.0/lib/node_modules/electron-forge/dist/electron-forge.js
/Users/mark/.nvm/versions/node/v8.9.0/bin/electron-forge-vscode-nix -> /Users/mark/.nvm/versions/node/v8.9.0/lib/node_modules/electron-forge/script/vscode.sh
/Users/mark/.nvm/versions/node/v8.9.0/bin/electron-forge -> /Users/mark/.nvm/versions/node/v8.9.0/lib/node_modules/electron-forge/dist/electron-forge.js
> macos-alias@0.2.11 install /Users/mark/.nvm/versions/node/v8.9.0/lib/node_modules/electron-forge/node_modules/macos-alias
> node-gyp rebuild
CXX(target) Release/obj.target/volume/src/volume.o
SOLINK_MODULE(target) Release/volume.node
> fs-xattr@0.1.17 install /Users/mark/.nvm/versions/node/v8.9.0/lib/node_modules/electron-forge/node_modules/fs-xattr
> node-gyp rebuild
CXX(target) Release/obj.target/xattr/src/async.o
CXX(target) Release/obj.target/xattr/src/error.o
CXX(target) Release/obj.target/xattr/src/sync.o
CXX(target) Release/obj.target/xattr/src/util.o
CXX(target) Release/obj.target/xattr/src/xattr.o
SOLINK_MODULE(target) Release/xattr.node
> electron-forge@4.1.2 install /Users/mark/.nvm/versions/node/v8.9.0/lib/node_modules/electron-forge
> node tabtab-install.js
[tabtab] Adding source line to load /Users/mark/.nvm/versions/node/v8.9.0/lib/node_modules/electron-forge/node_modules/tabtab/.completions/electron-forge.zsh
in /Users/mark/.zshrc
> spawn-sync@1.0.15 postinstall /Users/mark/.nvm/versions/node/v8.9.0/lib/node_modules/electron-forge/node_modules/spawn-sync
> node postinstall
+ electron-forge@4.1.2
added 596 packages in 112.037s
~/Project took 1m 54s
➜ electron-forge init electron-forge-example
✔ Checking your system
✔ Initializing Project Directory
✔ Initializing Git Repository
✔ Copying Starter Files
✔ Initializing NPM Module
✔ Installing NPM Dependencies
~/Project/electron-forge-example on master [?] is 📦 v1.0.0 via ⬢ v8.9.0
➜ npm start
> electron-forge-example@1.0.0 start /Users/mark/Project/electron-forge-example
> electron-forge start
✔ Checking your system
✔ Locating Application
✔ Preparing native dependencies
✔ Launching Application
Warning, the following targets are using a decimal version:
electron: 1.7
We recommend using a string for minor/patch versions to avoid numbers like 6.10
getting parsed as 6.1, which can lead to unexpected behavior.
~/Project/electron-forge-example on master [?] is 📦 v1.0.0 via ⬢ v8.9.0
➜ npm run package
> electron-forge-example@1.0.0 package /Users/mark/Project/electron-forge-example
> electron-forge package
✔ Checking your system
✔ Preparing to Package Application for arch: x64
✔ Compiling Application
✔ Preparing native dependencies
✔ Packaging Application
~/Project/electron-forge-example on master [?] is 📦 v1.0.0 via ⬢ v8.9.0 took 37s
➜ npm run make
> electron-forge-example@1.0.0 make /Users/mark/Project/electron-forge-example
> electron-forge make
✔ Checking your system
✔ Resolving Forge Config
We need to package your application before we can make it
✔ Preparing to Package Application for arch: x64
✔ Compiling Application
✔ Preparing native dependencies
✔ Packaging Application
Making for the following targets:
✔ Making for target: zip - On platform: darwin - For arch: x64
✔ Making for target: dmg - On platform: darwin - For arch: x64
// 업데이트 서버 주소 설정 및 초기화
autoUpdater.setFeedURL(`http://127.0.0.1:${port}/update/latest`);
// 업데이트 있는지 체크
autoUpdater.checkForUpdates();
// 업데이트 파일이 다운로드 다 되었는지 확인하는 이벤트 바인딩
autoUpdater.on('update-downloaded', async() => {
// 앱 종료 후 업데이트 후 재시작
autoUpdater.quitAndInstall();
});
http://download.myapp.com/update/win32/:version/RELEASES
http://download.myapp.com/update/win64/:version/RELEASES
http://download.myapp.com/update/win32/:version/:channel/RELEASES
http://download.myapp.com/update/win64/:version/:channel/RELEASES
; 프로그램 전체에 대한 설정을 정의한다.
[Setup]
AppId={#AppId}
AppName={#NameLong}
AppVerName={#NameVersion}
AppPublisher=Studio XID, Inc.
AppPublisherURL={#Homepage}
AppSupportURL={#Homepage}docs/
;AppUpdatesURL={#Homepage}
DefaultDirName={pf}\{#DirName}
DefaultGroupName={#NameLong}
AllowNoIcons=yes
OutputDir={#RepoDir}\dist
OutputBaseFilename=ProtoPie-{#Version}-Setup
Compression=lzma
SolidCompression=yes
AppMutex={#AppMutex}
WizardImageFile={#RepoDir}\build\win32\inno_big.bmp
WizardSmallImageFile={#RepoDir}\build\win32\inno_small.bmp
SetupIconFile={#RepoDir}\build\win32\icon_setup.ico
UninstallDisplayIcon={app}\{#ExeBasename}.exe
ChangesEnvironment=true
ChangesAssociations=true
MinVersion=6.1.7600
SourceDir={#SourceDir}
AppVersion={#Version}
VersionInfoVersion={#RawVersion}
ShowLanguageDialog=auto
CloseApplications=force
; PrivilegesRequired=admin
; 프로그램이 사용하는 언어를 정의한다.
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl,{#RepoDir}\build\win32\i18n\messages.en.isl"
Name: "simplifiedChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.zh-cn.isl,{#RepoDir}\build\win32\i18n\messages.zh-cn.isl"
; 설치시에 제거 할 파일이 있으면 정의한다.
[InstallDelete]
Type: filesandordirs; Name: {app}\resources
; 설치 프로세스를 사용자 측에서 상세하게 설정하는 경우에 각각의 처리(작업)에 대해 정의한다.
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1
Name: "associatewithfiles"; Description: "{cm:AssociateWithFiles,{#NameShort}}"; GroupDescription: "{cm:Other}"
Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent
...
// make `ProtoPie-${Version}-Setup.exe` file
npm run pack-prod
// make `${Version}.json` file
// upload to S3 - `${Version.json}` file, `ProtoPie-${Version}-Setup.exe` file
npm run deploy
{
"url": "http://release.protopie.io/ProtoPie-3.2.3-mac.zip",
"name": "3.2.3",
"notes": "## 3.2.3\n### 3.2.3\n- <strong>Special thanks to Jaret(郑皓), 田中良樹, and Shue(尹舒)</strong> (in alphabetical order)\n- Fixed the problem to lose images under a certain procedure\n- Improved the stability of the application\n- Added ProtoPie improvement program\nmetadata:{\"osx\":{\"size\":75391013,\"checksum\":\"39432d1cb5b4214515a443d4bec719e9d2b787ab\"}}\n## 3.2.2\n### 3.2.2\n- (3.2.2) Fixed window control bug\n- (3.2.1) Fixed dialog bug\n- Replace image layers\n- Copy and paste scenes across files\n- Open recent files\n- Rename Interaction Pieces' names\n- Keyboard shortcuts for panning and zooming canvas panel\n- Spinner interface to adjust values on the property panel\n- Rebuilt Sketch Import feature\n- Enhanced Preview's performance and interactions\n- Improved usability and fixed minor bugs\nmetadata:{\"osx\":{\"size\":72656810,\"checksum\":\"4c45300147ac566e6cb5975cd7c637da0a3d6fcd\"}}",
"metadata": {
"osx": {
"size": 75391013,
"checksum": "39432d1cb5b4214515a443d4bec719e9d2b787ab"
}
},
"pub_date": "2017-03-21T09:54:30.939Z"
}
{
"version": "3.2.3",
"note": "### 3.2.3\n- <strong>Special thanks to Jaret(郑皓), 田中良樹, and Shue(尹舒)</strong> (in alphabetical order)\n- Fixed the problem to lose images under a certain procedure\n- Improved the stability of the application\n- Added ProtoPie improvement program",
"file": "ProtoPie-3.2.3-mac.zip",
"metadata": {
"osx": {
"size": 75391013,
"checksum": "39432d1cb5b4214515a443d4bec719e9d2b787ab"
}
},
"pub_date": "2017-03-21T09:54:30.939Z",
"state": "enabled"
}
{
"version": "3.2.3",
"note": "### 3.2.3\n- <strong>Official version for Windows</strong>\n- Thank you for your effort to improve ProtoPie for Windows.\n- Beta test program is over on March 31, 2017 in KST.\n- Please jump in and ride on the official version. ;)",
"file": "ProtoPie-3.2.3-Setup.exe",
"metadata": {
"win32": {
"size": 55642520,
"checksum": "5df925f649af2924e48bbd935eb9a3f4807bad65"
}
},
"pub_date": "2017-03-31T05:55:56.133Z",
"state": "enabled"
}
import * as semver from 'semver';
semver.gt(version, '3.3.0');
semver.lte(version, '3.1.0')
// 라우팅 핸들러 설정
router.get('/:platform/:version/', ctrl.index);
// 라우팅 함수
async function index(req, res) {
// 플랫폼과 버전을 받음
const platform: string = req.params.platform;
const version: string = req.params.version;
if (platform !== 'darwin_x64' && platform !== 'win32') {
return res.status(400).end();
}
if (semver.valid(version) === null) {
return res.status(400).end();
}
// 버전 관리자를 통해 S3 로 부터 최신 버전 리스트를 업데이트
const versionManager: IVersionManager = VersionManager.getInstance();
await versionManager.update();
// 최신 버전 리스트에서 요청받은 플랫폼과 버전레 맞춰 response 에 담을 내용을 만들어 냄.
const result = versionManager.getResult(platform, version);
if (result) {
res.json(result);
} else {
res.sendStatus(204);
}
}
export class VersionManager implements IVersionManager {
// 매니저는 단일 객체
public getResult(platfrom: string, version: string) {
const platfromId: string = (platfrom === 'darwin_x64') ? 'darwin' : platfrom;
const versionInPlatform: IVersion[] = this._versionList.filter(v => {
if ((v.getPlatform() === platfromId) && semver.gt(v.getVersion(), version)) {
if (MODE === 'production' || MODE === 'qa') {
return v.getState() === 'enabled';
} else {
return v.getState() !== 'disabled';
}
} else {
return false;
}
});
versionInPlatform.sort((l, r) => semver.gt(l.getVersion(), r.getVersion()) ? -1 : 1);
if (versionInPlatform.length > 0) {
const url = versionInPlatform[0].getFile();
const name = versionInPlatform[0].getVersion();
const notes = this._getNotes(versionInPlatform);
const metadata = versionInPlatform[0].getMetadata();
const pub_date = versionInPlatform[0].getDate();
return {
url,
name,
notes,
metadata,
pub_date
};
} else {
return null;
}
}
public async update(): Promise<void> {
let s3List = null;
try {
s3List = await getS3List();
} catch (e) {
await sendSNS('Autoupdate Server : getS3List 실패', '자동업데이트 서버에서 S3 로 부터 JSON 리스트를 읽어오다가 실패함.');
return;
}
let s3Objects = null;
try {
s3Objects = await getS3Objects(s3List);
} catch (e) {
await sendSNS('Autoupdate Server : getS3Objects 실패', '자동업데이트 서버에서 S3 로 부터 JSON 파일을 읽어오다가 실패함.');
return;
}
this._setVersions(s3List, s3Objects);
}
private _setVersions(s3List: any[], s3Objects: any[]): void {
this._versionList = [];
s3List.forEach((item, index) => {
this._versionList.push(new Version(s3List[index], s3Objects[index]));
});
}
private _getNotes(versionInPlatform): string {
const notes = [];
versionInPlatform.forEach((item, index) => {
if (index < 3) {
notes.push(`## ${item.getVersion()}\n${item.getNote()}\nmetadata:${JSON.stringify(item.getMetadata())}`);
}
});
return notes.join('\n');
}
}
console.log('Cleaning up Output and intermediate directory...');
exec('npm run clean');
console.log('Transpile source codes..');
exec('npm run transpile');
if (flavor === 'production') {
console.log('Packing client codes.. in Release');
exec('npm run webpack-prod');
} else {
console.log('Packing client codes..');
exec('npm run webpack');
}
console.log('Creating intermediate directory...');
mkdir('app');
console.log('Creating distribution specified files...');
cat_write(JSON.stringify({serverMode:ENV}), 'output/server_env.json');
cat_write(JSON.stringify({flavor}), 'output/buildflavor.json');
cp('output/buildflavor/' + flavor + '.js', 'output/buildflavor/current.tmp');
rm_rf('output/buildflavor/*.js');
cp('output/buildflavor/current.tmp', 'output/buildflavor/current.js');
mkdir('bin');
console.log('Packing executable binary file with zeit/pkg...');
if (process.platform === 'win32') {
exec('npm run pkg-win32');
exec('npm run pkg-win64');
} else {
exec('npm run pkg');
}
console.log('Creating distribution specified package.json file...');
cat_write(create_production_packages_json(), 'app/package.json');
console.log('Creating resources to be packed in distribution...');
cp('output', 'app/output');
cp('locale', 'app/locale');
cp('extbin', 'app/extbin');
cp('static', 'app/static');
rm_rf('app/static/index.js');
mv('app/static/index_production.js', 'app/static/index.js');
if (process.platform === 'win32') {
cp('resources', 'app/resources');
}
// 배포본 생성에 포함하지 않을 코드 제거 (enclose 컴파일 했으므로 browser쪽은 불필요)
rm_rf('./app/output/browser');
rm_rf('./app/output/browser-test');
rm_rf('./app/output/common-test');
rm_rf('./app/output/testlib-test');
rm_rf('./app/output/client-test');
rm_rf('./app/output/testlib');
// Removing source map files
for(let mapFile of find('./app/output', /.*\.js\.map$/)) {
rm_rf(mapFile);
}
if (!isDraftBuild) {
console.log('Downloading distribution dependencies...');
cd('app');
exec('npm install');
cd(PROJECT_ROOT);
} else {
console.log('Copying development dependencies... (DRAFT MODE: Fast but bigger dependencies)');
cp('node_modules', 'app/node_modules');
}
if (process.platform === 'win32') {
exec(`node_modules\\.bin\\build.cmd --ia32`);
mv('dist/win-ia32-unpacked/resources/bin/server32.exe', 'dist/win-ia32-unpacked/resources/bin/server.exe');
rm_rf('dist/win-ia32-unpacked/resources/bin/server64.exe');
rm_rf('dist/win-ia32-unpacked/resources/extbin/recorder/ffmpeg-win64.exe');
rm_rf('dist/win-ia32-unpacked/resources/extbin/recorder/ffmpeg');
exec(`node_modules\\.bin\\build.cmd --x64`);
mv('dist/win-unpacked/resources/bin/server64.exe', 'dist/win-unpacked/resources/bin/server.exe');
rm_rf('dist/win-unpacked/resources/bin/server32.exe');
rm_rf('dist/win-unpacked/resources/extbin/recorder/ffmpeg-win32.exe');
rm_rf('dist/win-unpacked/resources/extbin/recorder/ffmpeg');
rm_rf('./dist/win-ia32-unpacked/resources/app-update.yml');
rm_rf('./dist/win-unpacked/resources/app-update.yml');
console.log('Code signing electron files...');
sign_files(path.join(__dirname, '../dist/win-ia32-unpacked'));
sign_files(path.join(__dirname, '../dist/win-ia32-unpacked/resources/bin'));
sign_files(path.join(__dirname, '../dist/win-ia32-unpacked/resources/extbin'));
sign_files(path.join(__dirname, '../dist/win-unpacked'));
sign_files(path.join(__dirname, '../dist/win-unpacked/resources/bin'));
sign_files(path.join(__dirname, '../dist/win-unpacked/resources/extbin'));
console.log('InnoSetup packaging...');
const issPath = path.join(__dirname, '../build/win32/code.iss');
packageInnoSetup(issPath, getInnoSetupDefinition('ia32'), () => {
console.log('Pack finished for ia32 installer binary');
packageInnoSetup(issPath, getInnoSetupDefinition('x64'), () => {
console.log('Pack finished for x64 installer binary');
sign_files(path.join(__dirname, '../dist'));
cleanup();
});
});
} else {
rm_rf('extbin/recorder/ffmpeg-win32.exe');
rm_rf('extbin/recorder/ffmpeg-win64.exe');
cp('bin', 'app/bin');
exec('./node_modules/.bin/build');
rm_rf('./dist/latest-mac.json');
cp('ffmpeg/ffmpeg-win32.exe', 'extbin/recorder/ffmpeg-win32.exe');
cp('ffmpeg/ffmpeg-win64.exe', 'extbin/recorder/ffmpeg-win64.exe');
cleanup();
}
By Woongjae Lee
play.node 2017 발표 자료