You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
333 lines
10 KiB
333 lines
10 KiB
2 years ago
|
import np from 'path'
|
||
|
import nfs from 'fs'
|
||
|
import {performance} from 'perf_hooks'
|
||
|
import log from '../src/utils/logger'
|
||
|
import {program as cli} from 'commander'
|
||
|
import FileSystem from '../src/bundler/fs'
|
||
|
import Runner from '../src/bundler/runner'
|
||
|
import Bundler from '../src/bundler/bundle'
|
||
|
import Cache from '../src/bundler/cache'
|
||
|
import {resolveConfig,resolveFile,resolvePackage,getCacheDir, resolvePath} from '../src/bundler/utils'
|
||
|
import {resolvePresets,merge as extendConfig} from '../src/bundler/config'
|
||
|
import { spawn } from 'child_process'
|
||
|
import { viteServerConfigFile, resolveWithFallbacks, ensurePackagesInstalled, vitestSetupPath } from '../src/utils/vite'
|
||
|
import create from './create.imba'
|
||
|
import * as dotenv from 'dotenv'
|
||
|
|
||
|
import tmp from 'tmp'
|
||
|
import getport from 'get-port'
|
||
|
|
||
|
const fmt = {
|
||
|
int: do(val) parseInt(val)
|
||
|
i: do(val) val == 'max' ? 0 : parseInt(val)
|
||
|
v: do(dummy,prev) prev + 1
|
||
|
}
|
||
|
|
||
|
let imbapkg = resolvePackage(np.resolve(__dirname,'..')) or {}
|
||
|
|
||
|
const overrides = {}
|
||
|
let argv = process.argv.slice(0)
|
||
|
|
||
|
const overrideAliases = {
|
||
|
M: {minify: false}
|
||
|
m: {minify: true}
|
||
|
S: {sourcemap: false}
|
||
|
s: {sourcemap: true}
|
||
|
}
|
||
|
|
||
|
const valueMap = {
|
||
|
'true': true
|
||
|
'false': false
|
||
|
'null': null
|
||
|
'undefined': undefined
|
||
|
}
|
||
|
|
||
|
for item,i in argv
|
||
|
continue unless item
|
||
|
|
||
|
if item.match(/^\-\-(\w+)(\.\w+)+$/)
|
||
|
let val = argv[i+1]
|
||
|
let path = item.slice(2).split('.')
|
||
|
let cfg = overrides
|
||
|
argv[i] = null
|
||
|
while path[1]
|
||
|
cfg = cfg[path[0]] ||= {}
|
||
|
path.shift!
|
||
|
|
||
|
let aliased = overrideAliases[path[0]]
|
||
|
if aliased
|
||
|
Object.assign(cfg,aliased)
|
||
|
else
|
||
|
if val.indexOf(' ') >= 0
|
||
|
val = val.split(/\,\s*|\s+/g)
|
||
|
|
||
|
val = valueMap[val] or val
|
||
|
|
||
|
cfg[path[0]] = val
|
||
|
argv[i] = null
|
||
|
argv[i+1] = null
|
||
|
|
||
|
argv = argv.filter do $1 !== null
|
||
|
|
||
|
def parseOptions options, extras = []
|
||
|
|
||
|
return options if options.#parsed
|
||
|
|
||
|
let command = options._name
|
||
|
|
||
|
options = options.opts! if options.opts isa Function
|
||
|
|
||
|
let cwd = options.cwd ||= process.cwd!
|
||
|
options.imbaPath ||= np.resolve(__dirname,'..')
|
||
|
options.command = command
|
||
|
options.extras = extras
|
||
|
|
||
|
options.config = resolveConfig(cwd,options.config or 'imbaconfig.json')
|
||
|
options.package = resolvePackage(cwd) or {}
|
||
|
options.dotenv = resolveFile('.env',cwd)
|
||
|
options.nodeModulesPath = resolvePath('node_modules',cwd)
|
||
|
|
||
|
if options.dotenv
|
||
|
options.dotvars = dotenv.parse(options.dotenv.body)
|
||
|
|
||
|
if options.esm
|
||
|
options.as ??= 'esm'
|
||
|
|
||
|
if options.verbose > 1
|
||
|
options.loglevel ||= 'debug'
|
||
|
|
||
|
elif options.verbose
|
||
|
options.loglevel ||= 'info'
|
||
|
|
||
|
if options.development
|
||
|
options.minify ??= no
|
||
|
options.sourcemap ??= yes
|
||
|
options.watch ??= yes
|
||
|
options.hmr = yes
|
||
|
options.mode = 'development'
|
||
|
|
||
|
if options.production
|
||
|
options.minify ??= yes
|
||
|
options.sourcemap ??= no
|
||
|
options.hmr = no
|
||
|
options.mode = 'production'
|
||
|
|
||
|
if command == 'build'
|
||
|
options.minify ??= yes
|
||
|
options.mode ??= 'production'
|
||
|
options.sourcemap ??= no
|
||
|
options.loglevel ||= 'info'
|
||
|
options.outdir ||= 'dist'
|
||
|
|
||
|
if options.web and command != 'build'
|
||
|
command = options.command = 'serve'
|
||
|
# console.log 'changing to serve!!'
|
||
|
|
||
|
if options.web and command != 'serve'
|
||
|
# if we are serving - the entrypoint will be redirected to a server-script
|
||
|
options.as ??= 'web'
|
||
|
|
||
|
if command == 'serve'
|
||
|
options.watch = yes
|
||
|
options.hmr = yes
|
||
|
|
||
|
if options.watch
|
||
|
options.loglevel ||= 'info'
|
||
|
if options.mode == 'development'
|
||
|
options.hmr = yes
|
||
|
|
||
|
if options.force
|
||
|
options.mtime = Date.now!
|
||
|
else
|
||
|
let statFiles = [
|
||
|
__filename
|
||
|
np.resolve(__dirname,'..','workers.imba.js')
|
||
|
np.resolve(__dirname,'..','dist','compiler.cjs')
|
||
|
]
|
||
|
# also check mtime of project?
|
||
|
options.mtime = Math.max(...statFiles.map(do nfs.statSync($1).mtimeMs))
|
||
|
|
||
|
options.loglevel ||= 'warning'
|
||
|
|
||
|
options.cachedir = getCacheDir(options)
|
||
|
global.#IMBA_OPTIONS = options
|
||
|
options.#parsed = yes
|
||
|
return options
|
||
|
|
||
|
def eject(o)
|
||
|
o = parseOptions(o)
|
||
|
const configPath = "vite.config.server.js"
|
||
|
if nfs.existsSync(configPath) and !o.force
|
||
|
return console.log "You already have a vite.config.server in your project. Delete it or use `imba eject --force` to overwrite"
|
||
|
const configContent = nfs.readFileSync(viteServerConfigFile, 'utf-8')
|
||
|
nfs.writeFileSync(configPath, configContent.replace(/\/\/eject\s/g, ''))
|
||
|
console.log "✅ vite.config.server.mjs has been successfully {o.force ? 'overwritten': 'created'}"
|
||
|
const setupPath = "test-setup.js"
|
||
|
if nfs.existsSync(setupPath) and !o.force
|
||
|
return console.log "You already have a test-setup.js in your project. Delete it or use `imba eject --force` to overwrite"
|
||
|
const setupContent = nfs.readFileSync(vitestSetupPath, 'utf-8')
|
||
|
nfs.writeFileSync(setupPath, setupContent)
|
||
|
console.log "✅ test-setup.js has been successfully {o.force ? 'overwritten': 'created'}"
|
||
|
console.log "💎 You can still run the project using imba <server.imba> --vite and it will pick your config"
|
||
|
console.log "💎 Run `vite build -c vite.config.server.js` to create your build"
|
||
|
console.log "⚠️ You might need to change the entry from server.imba to the name of your entry file"
|
||
|
console.log "✨ Visit https://vitejs.dev/ to check the docs or join https://imba.io/community if you get stuck or simply have a question"
|
||
|
|
||
|
def test o
|
||
|
await ensurePackagesInstalled(['vitest', '@testing-library/dom', '@testing-library/jest-dom', 'jsdom'], process.cwd())
|
||
|
const vitest-path = np.join(process.cwd(), "node_modules/.bin", "vitest")
|
||
|
let configFile = resolveWithFallbacks(viteServerConfigFile, ["vitest.config.ts", "vitest.config.js", "vite.config.ts", "vite.config.js", "vite.config.server.js"])
|
||
|
if configFile == viteServerConfigFile
|
||
|
const original-setup-file = np.join(__dirname, "./test-setup.js")
|
||
|
# pick test setup file path
|
||
|
let setupFile = resolveWithFallbacks("test-setup", ["imba", "ts", "js", "mjs", "cjs"], {ext:"js"})
|
||
|
if setupFile == "test-setup.js"
|
||
|
setupFile = np.resolve original-setup-file
|
||
|
# create a temporary vite config file
|
||
|
const tmp-config = np.join __dirname, "temp-config.vite.js"
|
||
|
const body = nfs.readFileSync(viteServerConfigFile, "utf-8")
|
||
|
# inject the user's test setup file or the default one we provide
|
||
|
nfs.writeFileSync tmp-config, body.replace(/\/\*pholder\*\//g, "'{np.resolve(setupFile)}'")
|
||
|
configFile = tmp-config
|
||
|
const params = ["--config", configFile, "--root", process.cwd(), "--dir", process.cwd(), ...o.args]
|
||
|
const options =
|
||
|
cwd: process.cwd()
|
||
|
env: {
|
||
|
...process.env
|
||
|
FORCE_COLOR: yes
|
||
|
}
|
||
|
stdio: "inherit"
|
||
|
const vitest = spawn vitest-path, params, options
|
||
|
|
||
|
def run entry, o, extras
|
||
|
return cli.help! unless o.args.length > 0
|
||
|
let [path,q] = entry.split('?')
|
||
|
|
||
|
path = np.resolve(path)
|
||
|
|
||
|
let prog = o = parseOptions(o,extras)
|
||
|
|
||
|
o.cache = new Cache(o)
|
||
|
o.fs = new FileSystem(o.cwd,o)
|
||
|
|
||
|
if o.vite
|
||
|
await ensurePackagesInstalled(['vite', 'vite-node', 'vite-plugin-imba'], process.cwd())
|
||
|
|
||
|
# TODO support multiple entrypoints - especially for html
|
||
|
|
||
|
extendConfig(prog.config.options,overrides)
|
||
|
|
||
|
if !o.outdir
|
||
|
if o.command == 'build'
|
||
|
o.outdir = 'dist'
|
||
|
else
|
||
|
tmp.setGracefulCleanup!
|
||
|
let tmpdir = tmp.dirSync(unsafeCleanup: yes)
|
||
|
o.outdir = o.tmpdir = nfs.realpathSync(tmpdir.name)
|
||
|
# fake loader
|
||
|
|
||
|
let file = o.fs.lookup(path)
|
||
|
|
||
|
if q
|
||
|
o.as = q.replace(/^as=/,'')
|
||
|
elif file.ext == '.html'
|
||
|
o.as = 'html'
|
||
|
|
||
|
unless o.command == 'build'
|
||
|
o.as = 'node'
|
||
|
|
||
|
let params = resolvePresets(prog.config,{entryPoints: [file.rel]},o.as or 'node')
|
||
|
|
||
|
unless o.command == 'build'
|
||
|
o.port ||= await getport(port: getport.makeRange(3000, 3100))
|
||
|
|
||
|
let bundle = new Bundler(o,params)
|
||
|
let out
|
||
|
out = await bundle.build! unless o.vite
|
||
|
|
||
|
if o.command == 'build'
|
||
|
if o.vite
|
||
|
let Vite = await import("vite")
|
||
|
const configFile = resolveWithFallbacks(viteServerConfigFile, ["vite.config.ts", "vite.config.js", "vite.config.ts", "vite.config.js", "vite.config.server.js"])
|
||
|
await Vite.build
|
||
|
# configFile: configFile
|
||
|
configFile: configFile
|
||
|
build:
|
||
|
rollupOptions:
|
||
|
input: entry
|
||
|
|
||
|
return
|
||
|
let run = do
|
||
|
o.name ||= entry
|
||
|
let runner = new Runner(bundle,o)
|
||
|
if o.vite
|
||
|
await runner.initVite!
|
||
|
runner.start!
|
||
|
if o.vite
|
||
|
run()
|
||
|
elif out..main
|
||
|
run()
|
||
|
elif o.watch
|
||
|
bundle.once('built',run)
|
||
|
return
|
||
|
|
||
|
let binary = cli.storeOptionsAsProperties(false).version(imbapkg.version).name('imba')
|
||
|
|
||
|
def common cmd
|
||
|
cmd
|
||
|
.option("-o, --outdir <dir>", "Directory to output files")
|
||
|
.option("-w, --watch", "Continously build and watch project")
|
||
|
.option("-v, --verbose", "verbosity (repeat to increase)",fmt.v,0)
|
||
|
.option("-s, --sourcemap", "verbosity (repeat to increase)",fmt.v,0)
|
||
|
.option("-m, --minify", "Minify generated files")
|
||
|
.option("-M, --no-minify", "Disable minifying")
|
||
|
.option("-f, --force", "Disregard previously cached outputs")
|
||
|
.option("-k, --keep", "Keep existing files in output directory")
|
||
|
.option("-S, --no-sourcemap", "Omit sourcemaps")
|
||
|
.option("-d, --development","Use defaults for development")
|
||
|
.option("-p, --production","Use defaults for production")
|
||
|
.option("--vite", "Use Vite as a bundler for the server")
|
||
|
.option("--skipReloadingFor <glob>", "Skip reloading server code for these globs (micromatch format)")
|
||
|
.option("--bundle", "Try to bundle all external dependencies")
|
||
|
.option("--base <url>", "Base url for your generated site","/")
|
||
|
.option("--assets-dir <url>", "Base dir for assets","assets")
|
||
|
.option("--web","Build entrypoints for the browser")
|
||
|
.option("--esm","Output module files")
|
||
|
|
||
|
|
||
|
common(cli.command('run [script]', { isDefault: true }).description('Imba'))
|
||
|
.option("-i, --instances [count]", "Number of instances to start",fmt.i,1)
|
||
|
.option("--inspect", "Debug")
|
||
|
.action(run)
|
||
|
|
||
|
common(cli.command('build <script>').description('Build an imba/js/html entrypoint and their dependencies'))
|
||
|
.option("--platform <platform>", "Platform for entry","browser")
|
||
|
.action(run)
|
||
|
# .option("--as <preset>", "Configuration preset","node")
|
||
|
|
||
|
# watch should be implied?
|
||
|
common(cli.command('serve <script>').description('Spawn a webserver for an imba/js/html entrypoint'))
|
||
|
.option("-i, --instances [count]", "Number of instances to start",fmt.i,1)
|
||
|
.action(run)
|
||
|
|
||
|
cli
|
||
|
.command('eject').description('Output the default vite config file to allow customizing it (no worries, you can delete and imba will use the default one)')
|
||
|
.option("-f, --force", "Overwrite vite.config.server.js file when it exists")
|
||
|
.action(eject)
|
||
|
|
||
|
cli
|
||
|
.command('test').description('Run tests: This is a wrapper on top of vitest')
|
||
|
.option("-h, --help", "Display help (Link to https://vitest.dev/)")
|
||
|
.action(test)
|
||
|
|
||
|
cli
|
||
|
.command('create [name]')
|
||
|
.description('Create a new imba project')
|
||
|
.option('-t, --template [template]', 'Specify a template instead of selecting one interactively')
|
||
|
.option('-y, --yes', 'Say yes to any confirmation prompts')
|
||
|
.action(do create($1, $2.opts!))
|
||
|
|
||
|
log.ts 'parse options'
|
||
|
|
||
|
binary.parse(argv)
|