UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

450 lines (417 loc) 10.4 kB
import { defineCommand, runMain, showUsage } from 'citty' import { readFileSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' import colors from 'picocolors' function getPackageVersion() { let dirname if (typeof __dirname !== 'undefined') { // CommonJS dirname = __dirname } else { // ESM dirname = path.dirname(fileURLToPath(import.meta.url)) } const packagePath = path.join(dirname, '..', '..', 'package.json') const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8')) return packageJson.version } const version = getPackageVersion() const DOCS_BASE = 'https://onestack.dev/docs' const docsLinks = { dev: `${DOCS_BASE}/one-dev`, build: `${DOCS_BASE}/one-build`, serve: `${DOCS_BASE}/one-serve`, prebuild: `${DOCS_BASE}/guides-ios-native`, 'run:ios': `${DOCS_BASE}/guides-ios-native`, 'run:android': `${DOCS_BASE}/guides-ios-native`, clean: `${DOCS_BASE}/configuration`, patch: `${DOCS_BASE}/configuration`, 'generate-routes': `${DOCS_BASE}/routing-typed-routes`, typegen: `${DOCS_BASE}/routing-typed-routes`, } as const function withDocsLink(description: string, command: keyof typeof docsLinks): string { return `${description}\n\nDocs: ${docsLinks[command]}` } if (path.sep !== '/') { console.warn( colors.bgYellow('WARNING: UNSUPPORTED OS') + colors.yellow( ' - It appears you’re using Windows, which is currently not supported. You may experience unexpected issues.' ) ) } const modes = { development: 'development', production: 'production', } as const // citty returns an array when the same arg is passed multiple times // use the last value (standard CLI behavior - later args override earlier) function lastValue<T>(arg: T | T[]): T | undefined { if (Array.isArray(arg)) { return arg[arg.length - 1] } return arg } const dev = defineCommand({ meta: { name: 'dev', version: version, description: withDocsLink('Start the dev server', 'dev'), }, args: { clean: { type: 'boolean', }, host: { type: 'string', }, port: { type: 'string', }, https: { type: 'boolean', }, mode: { type: 'string', description: 'If set to "production" you can run the development server but serve the production bundle', }, 'debug-bundle': { type: 'string', description: `Will output the bundle to a temp file and then serve it from there afterwards allowing you to easily edit the bundle to debug problems.`, }, debug: { type: 'string', description: `Pass debug args to Vite`, }, }, async run({ args }) { const { dev } = await import('./cli/dev') await dev({ ...args, port: lastValue(args.port), host: lastValue(args.host), debugBundle: lastValue(args['debug-bundle']), mode: modes[lastValue(args.mode) as keyof typeof modes], }) }, }) const buildCommand = defineCommand({ meta: { name: 'build', version: version, description: withDocsLink('Build your app', 'build'), }, args: { step: { type: 'string', required: false, }, // limit the pages built only: { type: 'string', required: false, }, platform: { type: 'string', description: `One of: web, ios, android`, default: 'web', required: false, }, 'skip-env': { type: 'boolean', description: `Skip loading .env files during build`, required: false, }, }, async run({ args }) { const { build } = await import('./cli/build') const platforms = { ios: 'ios', web: 'web', android: 'android', } as const if (args.platform && !platforms[args.platform]) { throw new Error(`Invalid platform: ${args.platform}`) } const platform = platforms[args.platform as keyof typeof platforms] || 'web' await build({ only: args.only, platform, step: args.step, skipEnv: args['skip-env'], }) // TODO somewhere just before 1787f241b79 this stopped exiting, must have some hanging task process.exit(0) }, }) const serveCommand = defineCommand({ meta: { name: 'serve', version: version, description: withDocsLink('Serve a built app for production', 'serve'), }, args: { host: { type: 'string', }, port: { type: 'string', }, compress: { type: 'boolean', }, loadEnv: { type: 'boolean', }, }, async run({ args }) { const { serve } = await import('./serve') const port = lastValue(args.port) const host = lastValue(args.host) await serve({ port: port ? +port : undefined, host, compress: args.compress, loadEnv: !!args.loadEnv, }) }, }) const prebuild = defineCommand({ meta: { name: 'prebuild', version: version, description: withDocsLink('Prebuild native project', 'prebuild'), }, args: { platform: { type: 'string', description: 'ios or android', }, expo: { type: 'boolean', description: 'expo or non-expo folders', default: true, }, 'no-install': { type: 'boolean', description: 'skip installing native dependencies', default: false, }, }, async run({ args }) { if (args.install === false) args['no-install'] = true // citty seems to convert --no-install to install: false, leaving no-install as default const { run } = await import('./cli/prebuild') await run(args) }, }) const runIos = defineCommand({ meta: { name: 'run:ios', version: version, description: withDocsLink('Run the iOS app', 'run:ios'), }, args: {}, async run({ args }) { const { run } = await import('./cli/runIos') await run(args) }, }) const runAndroid = defineCommand({ meta: { name: 'run:android', version: version, description: withDocsLink('Run the Android app', 'run:android'), }, args: {}, async run({ args }) { const { run } = await import('./cli/runAndroid') await run(args) }, }) const clean = defineCommand({ meta: { name: 'clean', version: '0.0.0', description: withDocsLink('Clean build folders', 'clean'), }, args: {}, async run() { const { clean: vxrnClean } = await import('vxrn') await vxrnClean({ root: process.cwd(), }) }, }) const patch = defineCommand({ meta: { name: 'patch', version: '0.0.0', description: withDocsLink('Apply package patches', 'patch'), }, args: { force: { type: 'boolean', description: 'Force re-apply all patches', }, }, async run({ args }) { const { run } = await import('./cli/patch') await run(args) }, }) const generateRoutes = defineCommand({ meta: { name: 'generate-routes', version: version, description: withDocsLink( 'Generate route type definitions (routes.d.ts)', 'generate-routes' ), }, args: { appDir: { type: 'string', description: 'Path to app directory (default: "app")', }, typed: { type: 'string', description: 'Auto-generate route helpers. Options: "type" (type-only helpers) or "runtime" (runtime helpers)', }, }, async run({ args }) { const { run } = await import('./cli/generateRoutes') await run(args) }, }) const typegen = defineCommand({ meta: { name: 'typegen', version: version, description: withDocsLink( 'Generate routes.d.ts (alias for generate-routes)', 'typegen' ), }, args: { appDir: { type: 'string', description: 'Path to app directory (default: "app")', }, typed: { type: 'string', description: 'Auto-generate route helpers. Options: "type" (type-only helpers) or "runtime" (runtime helpers)', }, }, async run({ args }) { const { run } = await import('./cli/generateRoutes') await run(args) }, }) const daemonCommand = defineCommand({ meta: { name: 'daemon', version: version, description: 'Multi-app development server proxy', }, args: { subcommand: { type: 'positional', description: 'Subcommand: start, stop, status, route (default: start)', required: false, }, port: { type: 'string', description: 'Port to listen on (default: 8081)', }, host: { type: 'string', description: 'Host to bind to (default: 0.0.0.0)', }, app: { type: 'string', description: 'Bundle ID for route command', }, slot: { type: 'string', description: 'Slot number for route command', }, project: { type: 'string', description: 'Project path for route command', }, tui: { type: 'boolean', description: 'Show TUI (default: true if TTY)', }, }, async run({ args }) { const { daemon } = await import('./cli/daemon') await daemon({ ...args, port: lastValue(args.port), host: lastValue(args.host), }) }, }) const subCommands = { dev, clean, build: buildCommand, prebuild, 'run:ios': runIos, 'run:android': runAndroid, patch, serve: serveCommand, 'generate-routes': generateRoutes, typegen, daemon: daemonCommand, } // workaround for having sub-commands but also positional arg for naming in the create flow const subMain = defineCommand({ meta: { name: 'main', version: version, description: 'Welcome to One', }, subCommands, }) const main = defineCommand({ meta: { name: 'main', version: version, description: 'Welcome to One', }, args: { name: { type: 'positional', description: 'Folder name to place the app into', required: false, }, }, async run({ args }) { if (subCommands[args.name]) { // run sub command ourselves runMain(subMain) return } const { cliMain } = await import('./cli/main') await cliMain(args) }, }) // workaround for help with our workaround for sub-command + positional arg const helpIndex = process.argv.indexOf('--help') if (helpIndex > 0) { const subCommandName = process.argv[helpIndex - 1] const subCommand = subCommands[subCommandName] if (subCommand) { showUsage(subCommand) } else { showUsage(subMain) } } else { runMain(main) }