-
-
Notifications
You must be signed in to change notification settings - Fork 432
better CLI #170
better CLI #170
Changes from 2 commits
5289fc1
d9d93f4
0f8c04b
a66ac00
47b50f2
ffaacb4
bc23200
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,114 @@ | ||
import * as path from 'path'; | ||
import * as child_process from 'child_process'; | ||
import sade from 'sade'; | ||
import mri from 'mri'; | ||
import chalk from 'chalk'; | ||
import prettyMs from 'pretty-ms'; | ||
import help from './help.md'; | ||
import build from './build'; | ||
import exporter from './export'; | ||
import dev from './dev'; | ||
import upgrade from './upgrade'; | ||
import * as port_utils from './port-utils'; | ||
import { exists } from '../utils'; | ||
import * as pkg from '../../package.json'; | ||
|
||
const opts = mri(process.argv.slice(2), { | ||
alias: { | ||
h: 'help' | ||
} | ||
}); | ||
const prog = sade('sapper'); | ||
|
||
if (opts.help) { | ||
const rendered = help | ||
.replace('<@version@>', pkg.version) | ||
.replace(/^(.+)/gm, (m: string, $1: string) => /[#>]/.test(m) ? $1 : ` ${$1}`) | ||
.replace(/^# (.+)/gm, (m: string, $1: string) => chalk.bold.underline($1)) | ||
.replace(/^> (.+)/gm, (m: string, $1: string) => chalk.cyan($1)); | ||
prog.version(pkg.version); | ||
|
||
console.log(`\n${rendered}\n`); | ||
process.exit(0); | ||
} | ||
prog.command('dev') | ||
.describe('Start a development server') | ||
.option('-p, --port', 'Specify a port') | ||
.action(async ({ port }: { port: number }) => { | ||
if (port) { | ||
if (!await port_utils.check(port)) { | ||
console.log(chalk.bold.red(`> Port ${port} is unavailable`)); | ||
return; | ||
} | ||
} else { | ||
port = await port_utils.find(3000); | ||
} | ||
|
||
const [cmd] = opts._; | ||
dev(port); | ||
}); | ||
|
||
const start = Date.now(); | ||
prog.command('build [dest]') | ||
.describe('Create a production-ready version of your app') | ||
.action((dest = 'build') => { | ||
console.log(`> Building...`); | ||
|
||
switch (cmd) { | ||
case 'build': | ||
process.env.NODE_ENV = 'production'; | ||
process.env.SAPPER_DEST = opts._[1] || 'build'; | ||
process.env.SAPPER_DEST = dest; | ||
|
||
const start = Date.now(); | ||
|
||
build() | ||
.then(() => { | ||
const elapsed = Date.now() - start; | ||
console.error(`built in ${elapsed}ms`); // TODO beautify this, e.g. 'built in 4.7 seconds' | ||
console.error(`\n> Finished in ${prettyMs(elapsed)}. Type ${chalk.bold.cyan(dest === 'build' ? 'npx sapper start' : `npx sapper start ${dest}`)} to run the app.`); | ||
}) | ||
.catch(err => { | ||
console.error(err ? err.details || err.stack || err.message || err : 'Unknown error'); | ||
}); | ||
}); | ||
|
||
prog.command('start [dir]') | ||
.describe('Start your app') | ||
.option('-p, --port', 'Specify a port') | ||
.action(async (dir = 'build', { port }: { port: number }) => { | ||
const resolved = path.resolve(dir); | ||
const server = path.resolve(dir, 'server.js'); | ||
|
||
if (!exists(server)) { | ||
console.log(chalk.bold.red(`> ${dir}/server.js does not exist — type ${chalk.bold.cyan(dir === 'build' ? `npx sapper build` : `npx sapper build ${dir}`)} to create it`)); | ||
return; | ||
} | ||
|
||
if (port) { | ||
if (!await port_utils.check(port)) { | ||
console.log(chalk.bold.red(`> Port ${port} is unavailable`)); | ||
return; | ||
} | ||
} else { | ||
port = await port_utils.find(3000); | ||
} | ||
|
||
child_process.fork(server, [], { | ||
cwd: process.cwd(), | ||
env: Object.assign({ | ||
NODE_ENV: 'production', | ||
PORT: port, | ||
SAPPER_DEST: dir | ||
}, process.env) | ||
}); | ||
}); | ||
|
||
break; | ||
prog.command('export [dest]') | ||
.describe('Export your app as static files (if possible)') | ||
.action((dest = 'export') => { | ||
console.log(`> Building...`); | ||
|
||
case 'export': | ||
process.env.NODE_ENV = 'production'; | ||
process.env.SAPPER_DEST = '.sapper/.export'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More for my own curiosity, but why not set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's used by |
||
|
||
const export_dir = opts._[1] || 'export'; | ||
const start = Date.now(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when |
||
|
||
build() | ||
.then(() => exporter(export_dir)) | ||
.then(() => { | ||
const elapsed = Date.now() - start; | ||
console.error(`extracted in ${elapsed}ms`); // TODO beautify this, e.g. 'built in 4.7 seconds' | ||
console.error(`\n> Built in ${prettyMs(elapsed)}. Exporting...`); | ||
}) | ||
.then(() => exporter(dest)) | ||
.then(() => { | ||
const elapsed = Date.now() - start; | ||
console.error(`\n> Finished in ${prettyMs(elapsed)}. Type ${chalk.bold.cyan(`npx serve ${dest}`)} to run the app.`); | ||
}) | ||
.catch(err => { | ||
console.error(err ? err.details || err.stack || err.message || err : 'Unknown error'); | ||
}); | ||
}); | ||
|
||
break; | ||
|
||
case 'dev': | ||
dev(); | ||
break; | ||
|
||
case 'upgrade': | ||
upgrade(); | ||
break; | ||
|
||
case 'start': | ||
const dir = path.resolve(opts._[1] || 'build'); | ||
|
||
child_process.fork(`${dir}/server.js`, [], { | ||
cwd: process.cwd(), | ||
env: Object.assign({ | ||
NODE_ENV: 'production', | ||
SAPPER_DEST: dir | ||
}, process.env) | ||
}); | ||
|
||
break; | ||
// TODO upgrade | ||
|
||
default: | ||
console.log(`unrecognized command ${cmd} — try \`sapper --help\` for more information`); | ||
} | ||
prog.parse(process.argv); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely personal preference, but I like to recommend assigning all global aspects into the initial assignment. New commands get their own blocks, and that consistent spacing helps separate things with larger CLIs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, nice. I didn't realise it was chainable, I suppose that makes sense!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aye. You can chain the whole thing if you want, but that'll get messy