Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add saber eject-theme command #259

Merged
merged 14 commits into from
Aug 19, 2019
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