Skip to content

Commit

Permalink
First nextron
Browse files Browse the repository at this point in the history
  • Loading branch information
saltyshiomix committed May 2, 2018
1 parent 13ce24e commit 31d4627
Show file tree
Hide file tree
Showing 32 changed files with 8,510 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ typings/
# dotenv environment variables file
.env

# nextron
.DS_Store
dist
4 changes: 4 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.gitignore
src
tsconfig.json
webpack.config.js
65 changes: 65 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "nextron",
"version": "0.1.0",
"description": "Fully next.js electron app.",
"repository": "git@github.com:saltyshiomix/nextron.git",
"author": "Yoshihide Shiono <shiono.yoshihide@gmail.com>",
"license": "MIT",
"main": "dist/main.js",
"bin": {
"nextron": "dist/bin/nextron.js"
},
"scripts": {
"build": "webpack"
},
"dependencies": {
"app-root-path": "^2.0.1",
"archiver": "^2.1.1",
"asar": "^0.14.3",
"capitalize": "^1.0.0",
"chalk": "^2.4.1",
"chokidar": "^2.0.3",
"commander": "^2.15.1",
"dashify": "^1.0.0",
"dot-prop": "^4.2.0",
"electron": "^1.8.6",
"electron-installer-dmg": "^0.2.1",
"electron-is-dev": "^0.3.0",
"electron-windows-installer": "^1.7.8",
"fs-extra": "^6.0.0",
"globby": "^8.0.1",
"inquirer": "^5.2.0",
"klaw": "^2.1.1",
"next": "^6.0.0",
"ora": "^2.1.0",
"plist": "^3.0.1",
"react": "^16.3.2",
"react-dom": "^16.3.2",
"respawn": "^2.5.0"
},
"devDependencies": {
"@types/archiver": "^2.1.1",
"@types/capitalize": "^1.0.1",
"@types/chalk": "^2.2.0",
"@types/chokidar": "^1.7.5",
"@types/commander": "^2.12.2",
"@types/dashify": "^1.0.0",
"@types/dot-prop": "^4.2.0",
"@types/fs-extra": "^5.0.2",
"@types/globby": "^6.1.0",
"@types/inquirer": "^0.0.41",
"@types/klaw": "^2.1.1",
"@types/next": "^2.4.9",
"@types/node": "^8.10.11",
"@types/ora": "^1.3.4",
"@types/react": "^16.3.13",
"@types/react-dom": "^16.0.5",
"ts-loader": "^4.2.0",
"typescript": "^2.8.3",
"webpack": "^4.6.0",
"webpack-cli": "^2.0.15",
"webpack-merge": "^4.1.2",
"webpack-node-externals": "^1.7.2",
"webpack-shell-plugin": "^0.5.0"
}
}
16 changes: 16 additions & 0 deletions src/bin/build/clean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { pathExists, remove } from 'fs-extra'
import * as spinner from '../../spinner'

export default async function clean(directory: string): Promise<void> {
if (!await pathExists(directory)) {
return
}

spinner.create('Cleaning up previous build')

try {
await remove(directory)
} catch (err) {
spinner.fail('Not able to clean up output directory')
}
}
33 changes: 33 additions & 0 deletions src/bin/build/compress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as fs from 'fs-extra'
import { join, basename } from 'path'
import * as archiver from 'archiver'
import * as spinner from '../../spinner'

const compress = (outputDir: string, target: string, config: any) => new Promise<void>(async resolve => {
if (process.platform !== 'darwin') {
resolve()
return
}

spinner.create('Wrapping bundle into a ZIP archive')

const { slug, version } = config

const name = `${slug}-${version}-mac.zip`
const path = join(outputDir, name)
const output = fs.createWriteStream(path)

const archive = archiver('zip', {
zlib: {
level: 9
}
})

output.on('close', resolve)

archive.pipe(output)
archive.directory(target, basename(target))
archive.finalize()
})

export default compress
129 changes: 129 additions & 0 deletions src/bin/build/create-bundle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { join, relative, sep } from 'path'
import { exec as defaultExec } from 'child_process'
import { promisify } from 'util'
import * as fs from 'fs-extra'
import * as asar from 'asar'
import * as globby from 'globby'
import os from './os'
import * as spinner from '../../spinner'

const pack = (dest, files) => new Promise(async resolve => {
const stats = {}

for (const file of files) {
const stat = await fs.stat(file)

stats[file] = {
type: stat.isDirectory() ? 'directory': 'file',
stat
}
}

asar.createPackageFromFiles(process.cwd(), dest, files, stats, {}, resolve)
})

const clear = async (directory, files) => {
const removers = []

for (const file of files) {
const location = join(directory, file)
removers.push(fs.remove(location))
}

return Promise.all(removers)
}

const getDependencies = async (cwd: string): Promise<Set<string>> => {
// We need to ensure to make the process always
// succeed, since npm is putting a lot of useless
// errors into stderr.
const command = 'npm ls --prod --parseable --silent || exit 0'
const exec = promisify(defaultExec)

let stdout

try {
({ stdout } = await exec(command, { cwd }))
} catch (err) {
spinner.fail('Not able to run `npm ls` properly')
return
}

const list = stdout.split('\n').map(dependency => {
const path = relative(cwd, dependency)
const parts = path.split(sep)
const { length } = parts

if (length <= 2) {
return path
}

if (path.includes('@') && length <= 3) {
return parts.slice(0, 2).join(sep)
}

return false
})

return new Set(list.filter(Boolean))
}

export default async function createBundle(workingDir, outputDir, config) {
spinner.create(`Bundling application for ${os}`)

const include: string[] = [
'main',
`renderer${sep}out`,
'package.json',
...await getDependencies(workingDir)
]

const remove = [
'default_app.asar'
]

const isWin = process.platform === 'win32'
const main = join(outputDir, isWin ? 'electron' : `${config.name}.app`)
const parent = join(main, isWin ? 'resources' : 'Contents/Resources')
const target = join(parent, config.asar ? 'app.asar' : 'app')

if (config.asar) {
// The items within this collection won't be walked. Not their
// contents will be included – only their actual representation
// in the file system.
const files = [
'renderer',
'node_modules',
...await globby(include, { nodir: false })
]

// Create the `.asar` bundle with all necessary files
await pack(target, files)
} else {
const copiers = []

for (const path of include) {
const from = join(workingDir, path)
const to = join(target, path)

copiers.push(fs.copy(from, to))
}

// Copy all files into place, without an `.asar` archive
await Promise.all(copiers)
}

if (!isWin) {
const icon = {
origin: config.macOS.icon,
target: join(parent, `${config.slug}.icns`)
}

await fs.copy(icon.origin, icon.target)
}

// Remove any useless files from the bundle
await clear(parent, remove)

return main
}
76 changes: 76 additions & 0 deletions src/bin/build/create-installer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { join, resolve } from 'path'
import * as generateDMG from 'electron-installer-dmg'
import * as generateEXE from 'electron-windows-installer'
import * as spinner from '../../spinner'

const createDMG = (outputDir, appPath, config) => new Promise(resolve => {
const { name, slug, version } = config
const background = join(__dirname, '../../assets/background.png')
const dmgPath = join(outputDir, `${slug}-${version}.dmg`)

const contents = [
{
x: 420,
y: 150,
type: 'link',
path: '/Applications'
},
{
x: 125,
y: 150,
type: 'file',
path: appPath
}
]

generateDMG({
name,
appPath,
dmgPath,
background,
overwrite: true,
contents
}, err => {
if (err) {
spinner.fail('Not able to generate installer')
}

resolve()
})
})

const createEXE = (outputDirectory, appDirectory, config, cwd) => {
const { name: title, slug, version, windows } = config

const setup: any = {
appDirectory,
outputDirectory,
setupIcon: windows.setupIcon,
iconUrl: windows.icon,
authors: 'ACME, Inc.',
version,
setupExe: `${slug}-${version}-setup.exe`,
title,
exe: 'electron.exe'
}

if (!windows.msi) {
setup.noMsi = true
}

if (windows.loadingGIF) {
setup.loadingGif = resolve(cwd, windows.loadingGIF)
}

return generateEXE(setup)
}

export default async function createInstaller(outputDir, appPath, config, cwd) {
spinner.create('Generating installation wizard')

if (process.platform === 'darwin') {
return createDMG(outputDir, appPath, config)
}

return createEXE(outputDir, appPath, config, cwd)
}
41 changes: 41 additions & 0 deletions src/bin/build/export-renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { join } from 'path'
import { promisify } from 'util'
import { exec as defaultExec } from 'child_process'
import * as spinner from '../../spinner'

export default async function exportRenderer(): Promise<void> {
spinner.create('Building renderer code')

const cwd = process.cwd()
const renderer = join(cwd, 'renderer')
const exec = promisify(defaultExec)

let stderr

try {
({ stderr } = await exec(`npx next build ${renderer}`, { cwd }))
} catch (err) {
spinner.fail('Not able to build renderer code')
return
}

if (stderr && !stderr.includes('must be of type')) {
console.error(stderr)
process.exit(1)
}

spinner.create('Generating static bundle from renderer code')

try {
({ stderr } = await exec(`next export ${renderer}`, { cwd }))
// ({ stderr } = await exec(`npx next export ${renderer}`, { cwd }))
} catch (err) {
spinner.fail('Not able to export renderer code')
return
}

if (stderr && !stderr.includes('must be of type')) {
console.error(stderr)
process.exit(1)
}
}
Loading

0 comments on commit 31d4627

Please sign in to comment.