Skip to content

Commit

Permalink
feat: Add saber eject-theme command (#259)
Browse files Browse the repository at this point in the history
* feat: add saber eject

* docs: elaborate eject command

* deps: replace sharp with jimp

* feat: add dependency merging

* upgrade from master

* fix: linting

* fix: full compliance with package.json repository

* tweaks: use path.resolve

* tweak the logic for installing dependencies

* use npm if package-lock.json is present

* rename saber eject to saber eject-theme
  • Loading branch information
krmax44 authored and egoist committed Aug 19, 2019
1 parent a498aa1 commit b45908f
Show file tree
Hide file tree
Showing 10 changed files with 800 additions and 103 deletions.
27 changes: 27 additions & 0 deletions packages/saber/lib/cli-commands/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { setNodeEnv, handleError } = require('./utils')

module.exports = function(cli) {
cli
.command(
'build [app-path]',
'Compile the application and generate static HTML files'
)
.alias('generate')
.option('--skip-compilation', 'Skip the webpack compilation process')
.option('--inspect-webpack', 'Inspect webpack config in your editor')
.action((cwd = '.', options) => {
setNodeEnv('production')

if (cli.matchedCommandName === 'generate') {
require('saber-log').log.warn(
`The "generate" command is now deprecated, please use "build" instead.`
)
}

const { skipCompilation } = options
delete options.skipCompilation
return require('..')(Object.assign({ cwd, dev: false }, options))
.build({ skipCompilation })
.catch(handleError)
})
}
35 changes: 35 additions & 0 deletions packages/saber/lib/cli-commands/dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const { setNodeEnv, handleError } = require('./utils')

module.exports = function(cli) {
cli
.command('[app-path]', 'Run the application in dev mode', {
ignoreOptionDefaultValue: true
})
.alias('dev')
.option('--lazy', 'Enable lazy page compilation')
.option('--port <port>', 'Server port', { default: 3000 })
.option('--host <host>', 'Server host', { default: '0.0.0.0' })
.option('--ssr', 'Enable server-side rendering')
.option('--inspect-webpack', 'Inspect webpack config in your editor')
.action((cwd = '.', options) => {
setNodeEnv('development')

const { host, port, lazy, ssr } = options
delete options.host
delete options.port
delete options.lazy
delete options.ssr
return require('..')(Object.assign({ cwd, dev: true }, options), {
server: {
host,
port,
ssr
},
build: {
lazy
}
})
.serve()
.catch(handleError)
})
}
159 changes: 159 additions & 0 deletions packages/saber/lib/cli-commands/eject-theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
const path = require('path')
const { spawnSync } = require('child_process')
const { fs } = require('saber-utils')
const { log } = require('saber-log')
const normalizeRepo = require('normalize-repo')
const downloadGitRepo = require('download-git-repo')
const configLoader = require('../utils/configLoader')
const resolvePackage = require('../utils/resolvePackage')
const { handleError, spawn } = require('./utils')

const downloadRepo = (url, dest, opts) =>
new Promise(resolve => downloadGitRepo(url, dest, opts, resolve))

module.exports = function(cli) {
cli
.command(
'eject-theme [app-path]',
`Copy the currently used theme's source code to a local folder.`
)
.option(
'--git',
'Pull code from Git, instead of node_modules, and add the theme as a submodule',
{ default: false }
)
.option(
'--merge-dependencies',
"Copy over the theme's dependencies to your project's package.json.",
{ default: true }
)
.option('--path <path>', 'Ejected theme destination', {
default: './theme'
})
.action(async (cwd = '.', options) => {
cwd = path.resolve(cwd)
const { git } = options
const mergeDependencies = options['merge-dependencies']

const config =
configLoader.load({ cwd, files: configLoader.CONFIG_FILES }).data || {}
if (!config.theme) {
handleError('No theme specified in config.')
}

const destPath = path.join(cwd, options.path)
const relativeDest = path.relative(cwd, destPath)
if (await fs.pathExists(destPath)) {
handleError(
`The path ${options.path} already exists. Please specify a different one using "--path".`
)
}

const themePath = resolvePackage(config.theme, {
prefix: 'saber-theme-',
cwd
})
if (!themePath) {
handleError(
`Theme "${config.theme}" could not be found in your node_modules.`
)
}

const themePackage = configLoader.load({
cwd: themePath,
files: ['package.json']
}).data

if (git) {
const repo = themePackage.repository

if (repo && (!repo.type || repo.type === 'git')) {
const tmp = path.join(cwd, '.saber', 'theme-tmp')

const { shortcut, url } = normalizeRepo(themePackage.repository)
const downloadError = await downloadRepo(shortcut || url, tmp, {
clone: Boolean(shortcut)
})

if (downloadError) {
handleError(downloadError)
}

try {
await fs.move(
repo.directory ? path.join(tmp, repo.directory) : tmp,
destPath
)

await fs.remove(tmp)
} catch (error) {
handleError(error)
}

log.success('Downloaded theme source via Git.')
} else {
handleError(
'The theme has no git repository specified within its package.json.'
)
}
} else {
try {
await fs.copy(themePath, destPath, {
filter: src => !src.endsWith('/node_modules')
})

log.info('Copied theme from node_modules.')
} catch (error) {
handleError(error)
}
}

if (mergeDependencies) {
const dependencies = themePackage.dependencies || {}
const devDependencies = themePackage.devDependencies || {}

const projectPackage = configLoader.load({
cwd,
files: ['package.json']
}).data

try {
await fs.writeJson(
path.join(cwd, 'package.json'),
{
...projectPackage,
dependencies: {
...projectPackage.dependencies,
...dependencies,
[themePackage.name]: undefined // remove theme from dependencies
},
devDependencies: {
...projectPackage.devDependencies,
...devDependencies,
[themePackage.name]: undefined // remove theme from dev dependencies
}
},
{ spaces: 2 }
)

const { status } = spawnSync('yarnpkg', ['--version']) // test if yarn is present before allowing it to use the same stdio
const hasNpmLock = await fs.pathExists(
path.join(cwd, 'package-lock.json')
)
if (status === 0 && !hasNpmLock) {
await spawn('yarn', ['install'], { stdio: 'inherit' })
} else {
await spawn('npm', ['install'], { stdio: 'inherit' })
}

log.success('Merged theme dependencies.')
} catch (error) {
handleError(error)
}
}

log.info(
`Please change "theme" in your Saber config to "./${relativeDest}".`
)
})
}
5 changes: 5 additions & 0 deletions packages/saber/lib/cli-commands/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const commands = ['dev', 'build', 'serve', 'eject-theme']

module.exports = function(cli) {
commands.forEach(command => require(`./${command}`)(cli))
}
23 changes: 23 additions & 0 deletions packages/saber/lib/cli-commands/serve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { setNodeEnv, handleError } = require('./utils')

module.exports = function(cli) {
cli
.command('serve [app-path]', 'Serve the output directory')
.option('--host <host>', 'Server host', { default: '0.0.0.0' })
.option('--port <port>', 'Server port', { default: 3000 })
.action((cwd = '.', options) => {
setNodeEnv('production')

const { host, port } = options
delete options.host
delete options.port
return require('..')(Object.assign({ cwd, dev: true }, options), {
server: {
host,
port
}
})
.serveOutDir()
.catch(handleError)
})
}
21 changes: 21 additions & 0 deletions packages/saber/lib/cli-commands/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const { spawn } = require('child_process')

module.exports = {
setNodeEnv(env) {
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = env
}
},
handleError(err) {
if (typeof err === 'string') err = new Error(err)
require('saber-log').log.error(err.stack)
process.exit(1) // eslint-disable-line
},
spawn(...args) {
return new Promise((resolve, reject) => {
const childProcess = spawn(...args)
childProcess.on('close', resolve)
childProcess.on('error', reject)
})
}
}
88 changes: 1 addition & 87 deletions packages/saber/lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,7 @@
const cac = require('cac')

const cli = cac()

cli
.command('[app]', 'Run the application in dev mode', {
ignoreOptionDefaultValue: true
})
.alias('dev')
.option('--lazy', 'Enable lazy page compilation')
.option('--port <port>', 'Server port', { default: 3000 })
.option('--host <host>', 'Server host', { default: '0.0.0.0' })
.option('--ssr', 'Enable server-side rendering')
.option('--inspect-webpack', 'Inspect webpack config in your editor')
.action((cwd = '.', options) => {
setNodeEnv('development')

const { host, port, lazy, ssr } = options
delete options.host
delete options.port
delete options.lazy
delete options.ssr
return require('..')(Object.assign({ cwd, dev: true }, options), {
server: {
host,
port,
ssr
},
build: {
lazy
}
})
.serve()
.catch(handleError)
})

cli
.command(
'build [app]',
'Compile the application and generate static HTML files'
)
.alias('generate')
.option('--skip-compilation', 'Skip the webpack compilation process')
.option('--inspect-webpack', 'Inspect webpack config in your editor')
.action((cwd = '.', options) => {
setNodeEnv('production')

if (cli.matchedCommandName === 'generate') {
require('saber-log').log.warn(
`The "generate" command is now deprecated, please use "build" instead.`
)
}

const { skipCompilation } = options
delete options.skipCompilation
return require('..')(Object.assign({ cwd, dev: false }, options))
.build({ skipCompilation })
.catch(handleError)
})

cli
.command('serve [app]', 'Serve the output directory')
.option('--host <host>', 'Server host', { default: '0.0.0.0' })
.option('--port <port>', 'Server port', { default: 3000 })
.action((cwd = '.', options) => {
setNodeEnv('production')

const { host, port } = options
delete options.host
delete options.port
return require('..')(Object.assign({ cwd, dev: true }, options), {
server: {
host,
port
}
})
.serveOutDir()
.catch(handleError)
})
require('./cli-commands')(cli)

cli.option('-V, --verbose', 'Output verbose logs')
cli.option('--no-progress', 'Disable progress bar')
Expand All @@ -88,17 +13,6 @@ cli.help()

cli.parse()

function handleError(err) {
require('saber-log').log.error(err.stack)
process.exit(1)
}

function setNodeEnv(env) {
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = env
}
}

process.on('SIGINT', () => {
const { log } = require('saber-log')
log.log('')
Expand Down
2 changes: 2 additions & 0 deletions packages/saber/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"css-loader": "^2.1.0",
"cssnano": "^4.1.8",
"devalue": "^2.0.0",
"download-git-repo": "^2.0.0",
"fast-deep-equal": "^2.0.1",
"file-loader": "^4.0.0",
"get-port": "^5.0.0",
Expand All @@ -39,6 +40,7 @@
"lodash.merge": "^4.6.1",
"log-update": "^3.2.0",
"mini-css-extract-plugin": ">=0.7.0",
"normalize-repo": "^1.0.0",
"object-assign": "^4.1.1",
"open": "^6.4.0",
"polka": "^0.5.1",
Expand Down
Loading

0 comments on commit b45908f

Please sign in to comment.