Skip to content

Commit

Permalink
feat(robo): support for TypeScript config files (#351)
Browse files Browse the repository at this point in the history
* feat(robo): support for TypeScript config files

* forgot to stage

* patch

* patch

* Please set the distDir to .robo/config

* dn

* \n

* refactor(create-robo): inline config export w/ type

* no ts for watch file

* .

* refactor(robo/compiler): use compiler object directly

Cleaner and higher level than `traverse`.

* refactor(robo/compiler): removed redundant isTypescriptEnabled

This is already covered by `Compiler.isTypeScriptProject()` in a verifiable, cached, synchronous way.

* refactor(robo): avoid compiling config by default

We cannot assume that production environments have the compiler installed. As such, avoid compiling the file unless we know for sure it is being ran from a build-time command.

* refactor(robo/compiler): option to avoid copying uncompiled files

Minimizes overhead and prevents subtle bugs in cases like config builds.

* chore(robo): clean up both plugin config variants

* patch(robo/compiler): use relative config dirs

---------

Co-authored-by: Pkmmte Xeleon <[email protected]>
  • Loading branch information
ArnavK-09 and Pkmmte authored Nov 12, 2024
1 parent 977f3b7 commit 6f91bf0
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 48 deletions.
16 changes: 12 additions & 4 deletions packages/create-robo/src/robo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,19 +762,21 @@ export default class Robo {
devDependencies.push('eslint-plugin-react-refresh')
}

const ext = this._useTypeScript ? 'ts' : 'mjs'

if (features.includes('eslint')) {
devDependencies.push('eslint@9')
devDependencies.push('@eslint/js')
devDependencies.push('globals')
this._packageJson.scripts['lint'] = runPrefix + 'lint:eslint'
this._packageJson.scripts['lint:eslint'] = 'eslint -c config/eslint.mjs .'
this._packageJson.scripts['lint:eslint'] = `eslint -c config/eslint.${ext} .`
let eslintConfig = EslintConfig

if (this._useTypeScript) {
eslintConfig = EslintConfigTypescript
devDependencies.push('typescript-eslint')
}
await fs.writeFile(path.join(this._workingDir, 'config', 'eslint.mjs'), eslintConfig, 'utf-8')
await fs.writeFile(path.join(this._workingDir, 'config', `eslint.${ext}`), eslintConfig, 'utf-8')
}

if (features.includes('prettier')) {
Expand Down Expand Up @@ -807,8 +809,13 @@ export default class Robo {
roboConfig = roboConfig.replace(`type: 'robo'`, `type: 'plugin'`)
}

if (this._useTypeScript) {
const configContent = roboConfig.split('export default {')[1]
roboConfig = `import type { Config } from 'robo.js'\n\nexport default <Config>{` + configContent
}

logger.debug(`Writing Robo config file...`)
await fs.writeFile(path.join(this._workingDir, 'config', 'robo.mjs'), roboConfig, 'utf-8')
await fs.writeFile(path.join(this._workingDir, 'config', `robo.${ext}`), roboConfig, 'utf-8')
logger.debug(`Finished writing Robo config file:\n`, roboConfig)

// Sort keywords, scripts, dependencies, and devDependencies alphabetically (this is important to me)
Expand Down Expand Up @@ -1090,7 +1097,8 @@ export default class Robo {
}

// Normalize plugin path
const pluginPath = path.join(this._workingDir, 'config', 'plugins', ...pluginParts) + '.mjs'
const pluginPath =
path.join(this._workingDir, 'config', 'plugins', ...pluginParts) + this._useTypeScript ? '.ts' : '.mjs'
const pluginConfig = prettyStringify(config) + '\n'

logger.debug(`Writing ${pluginName} config to ${pluginPath}...`)
Expand Down
20 changes: 10 additions & 10 deletions packages/robo/src/cli/commands/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export async function addAction(packages: string[], options: AddCommandOptions)
}

// Check which plugin packages are already registered
const config = await loadConfig()
const config = await loadConfig('robo', true)
const nameMap: Record<string, string> = {}
const pendingRegistration = await Promise.all(
packages
Expand Down Expand Up @@ -94,14 +94,13 @@ export async function addAction(packages: string[], options: AddCommandOptions)
// Check which plugins need to be installed
const packageJsonPath = path.join(process.cwd(), 'package.json')
const packageJson = require(packageJsonPath)
const pendingInstall = packages
.filter((pkg) => {
return (
options.force ||
(!Object.keys(packageJson.dependencies ?? {})?.includes(pkg) &&
!config.plugins?.find((p) => Array.isArray(p) && p[0] === pkg))
)
})
const pendingInstall = packages.filter((pkg) => {
return (
options.force ||
(!Object.keys(packageJson.dependencies ?? {})?.includes(pkg) &&
!config.plugins?.find((p) => Array.isArray(p) && p[0] === pkg))
)
})
logger.debug(`Pending installation add:`, pendingInstall)

// Install plugin packages
Expand Down Expand Up @@ -223,7 +222,8 @@ async function createPluginConfig(pluginName: string, config: Record<string, unk
}

// Normalize plugin path
const pluginPath = path.join(process.cwd(), 'config', 'plugins', ...pluginParts) + '.mjs'
const pluginPath =
path.join(process.cwd(), 'config', 'plugins', ...pluginParts) + (Compiler.isTypescriptProject() ? '.ts' : '.mjs')
const pluginConfig = JSON.stringify(config) + '\n'

logger.debug(`Writing ${pluginName} config to ${pluginPath}...`)
Expand Down
2 changes: 1 addition & 1 deletion packages/robo/src/cli/commands/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export async function buildAction(files: string[], options: BuildCommandOptions)
`The ${color.bold('.config')} directory is deprecated. Use ${color.bold('config')} instead. (without the dot)`
)
}
const config = await loadConfig()
const config = await loadConfig('robo', true)
if (!config) {
logger.warn(`Could not find configuration file.`)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/robo/src/cli/commands/build/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async function pluginAction(_args: string[], options: PluginCommandOptions) {
}

const startTime = Date.now()
const config = await loadConfig()
const config = await loadConfig('robo', true)

// Run the Robo Compiler
const { Compiler } = await import('../../utils/compiler.js')
Expand Down
2 changes: 1 addition & 1 deletion packages/robo/src/cli/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ async function deployAction(_args: string[], options: DeployCommandOptions) {
}

// Sorry, only bots are supported right now!
const config = await loadConfig()
const config = await loadConfig('robo', true)

if (config.experimental?.disableBot) {
logger.warn('Sorry, only bots are supported right now!')
Expand Down
2 changes: 1 addition & 1 deletion packages/robo/src/cli/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async function devAction(_args: string[], options: DevCommandOptions) {
logger.log('')

// Load the configuration before anything else
const config = await loadConfig()
const config = await loadConfig('robo', true)
const configPath = await loadConfigPath()
let configRelative: string

Expand Down
34 changes: 25 additions & 9 deletions packages/robo/src/cli/commands/remove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { createRequire } from 'node:module'
import { exec } from '../utils/utils.js'
import { getPackageManager } from '../utils/runtime-utils.js'
import { Spinner } from '../utils/spinner.js'
import { Compiler } from '../utils/compiler.js'
import { existsSync } from 'node:fs'

const require = createRequire(import.meta.url)

Expand Down Expand Up @@ -44,7 +46,7 @@ export async function removeAction(packages: string[], options: RemoveCommandOpt
}

// Check which plugin packages are already registered
const config = await loadConfig()
const config = await loadConfig('robo', true)
const pendingRegistration = packages.filter((pkg) => {
return (
options.force || config.plugins?.includes(pkg) || config.plugins?.find((p) => Array.isArray(p) && p[0] === pkg)
Expand Down Expand Up @@ -114,15 +116,29 @@ export async function removeAction(packages: string[], options: RemoveCommandOpt
/**
* Deletes the config file for a plugin in the config/plugins directory.
*
* @param pluginName The name of the plugin (e.g. @roboplay/plugin-ai)
* @param pluginName The name of the plugin (e.g. @robojs/ai)
*/
async function removePluginConfig(pluginName: string) {
const pluginParts = pluginName.replace(/^@/, '').split('/')
try {
const pluginParts = pluginName.replace(/^@/, '').split('/')

// Remove plugin config file
const pluginPath = path.join(process.cwd(), 'config', 'plugins', ...pluginParts) + '.mjs'
logger.debug(`Deleting ${pluginName} config from ${pluginPath}...`)
await fs.rm(pluginPath, {
force: true
})
// Remove plugin config file
const pluginJs = path.join(process.cwd(), 'config', 'plugins', ...pluginParts) + '.mjs'
const pluginTs = path.join(process.cwd(), 'config', 'plugins', ...pluginParts) + '.ts'

if (existsSync(pluginJs)) {
logger.debug(`Deleting ${pluginName} config from ${pluginJs}...`)
await fs.rm(pluginJs, {
force: true
})
}
if (existsSync(pluginTs)) {
logger.debug(`Deleting ${pluginName} config from ${pluginTs}...`)
await fs.rm(pluginTs, {
force: true
})
}
} catch (error) {
logger.error(`Failed to remove plugin config:`, error)
}
}
2 changes: 1 addition & 1 deletion packages/robo/src/cli/commands/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ async function syncAction(_args: string[], options: SyncCommandOptions) {
}

// Check past runs to see if we've already handled these plugins
await loadConfig()
await loadConfig('robo', true)
await prepareFlashcore()
const pluginRecord =
(await Flashcore.get<Record<string, boolean>>('plugins', {
Expand Down
2 changes: 1 addition & 1 deletion packages/robo/src/cli/utils/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ export async function registerCommands(
addedContextCommands: string[],
removedContextCommands: string[]
) {
const config = await loadConfig()
const config = await loadConfig('robo', true)
const clientId = env.get('discord.clientId')
const guildId = env.get('discord.guildId')
const token = env.get('discord.token')
Expand Down
27 changes: 18 additions & 9 deletions packages/robo/src/cli/utils/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function traverse(
compilerOptions: CompilerOptions,
transform: typeof SwcTransform
) {
const { excludePaths = [], parallel = 20 } = options
const { distExt = '.js', excludePaths = [], parallel = 20, srcDir = SrcDir } = options
const isIncremental = options.files?.length > 0

// Read directory contents
Expand Down Expand Up @@ -123,7 +123,7 @@ export async function traverse(
})

// Write transformed code to destination directory
const distPath = path.join(distDir, path.relative(SrcDir, filePath.replace(/\.(js|ts|tsx)$/, '.js')))
const distPath = path.join(distDir, path.relative(srcDir, filePath.replace(/\.(js|ts|tsx)$/, distExt)))
await fs.mkdir(path.dirname(distPath), { recursive: true })
await fs.writeFile(distPath, compileResult.code)
})()
Expand All @@ -142,15 +142,20 @@ export async function traverse(

interface BuildCodeOptions {
baseUrl?: string
clean?: boolean
copyOther?: boolean
distDir?: string
distExt?: string
excludePaths?: string[]
files?: string[]
parallel?: number
paths?: Record<string, string[]>
plugin?: boolean
srcDir?: string
}

async function buildCode(options?: BuildCodeOptions) {
const { clean = true, copyOther = true, srcDir = SrcDir } = options ?? {}
const startTime = Date.now()
const distDir = options.distDir
? path.join(process.cwd(), options.distDir)
Expand All @@ -163,21 +168,23 @@ async function buildCode(options?: BuildCodeOptions) {

// Just copy the source directory if not a Typescript project
if (!Compiler.isTypescriptProject()) {
await fs.rm(distDir, { recursive: true, force: true })
if (clean) {
await fs.rm(distDir, { recursive: true, force: true })
}
logger.debug(`Not a TypeScript project. Copying source without compiling...`)
await copyDir(SrcDir, distDir, [], options.excludePaths ?? [])
await copyDir(srcDir, distDir, [], options.excludePaths ?? [])

return Date.now() - startTime
}

// Clear the destination directory before compiling if not an incremental build
if (!options?.files?.length) {
if (clean && !options?.files?.length) {
logger.debug(`Cleaning ${distDir}...`)
await fs.rm(distDir, { recursive: true, force: true })
}

// Traverse the source directory and transform files
logger.debug(`Compiling ${SrcDir} to ${distDir}...`)
logger.debug(`Compiling ${srcDir} to ${distDir}...`)
const tsOptions = await getTypeScriptCompilerOptions()
const baseUrl = tsOptions.baseUrl ?? process.cwd()
const compileOptions = {
Expand All @@ -187,12 +194,14 @@ async function buildCode(options?: BuildCodeOptions) {
}
logger.debug(`Compiler options:`, compileOptions)

await traverse(SrcDir, distDir, compileOptions, tsOptions, transform)
await traverse(srcDir, distDir, compileOptions, tsOptions, transform)
await fs.rm(path.join(process.cwd(), '.swc'), { recursive: true, force: true })

// Copy any non-TypeScript files to the destination directory
logger.debug(`Copying additional non-TypeScript files from ${SrcDir} to ${distDir}...`)
await copyDir(SrcDir, distDir, ['.ts', '.tsx'], options.excludePaths ?? [])
if (copyOther) {
logger.debug(`Copying additional non-TypeScript files from ${srcDir} to ${distDir}...`)
await copyDir(srcDir, distDir, ['.ts', '.tsx'], options.excludePaths ?? [])
}

// Generate declaration files for plugins
if (options?.plugin) {
Expand Down
2 changes: 1 addition & 1 deletion packages/robo/src/cli/utils/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const mergeEvents = (baseEvents: Record<string, EventConfig[]>, newEvents: Recor
}

export async function generateManifest(generatedDefaults: DefaultGen, type: 'plugin' | 'robo'): Promise<Manifest> {
const config = await loadConfig()
const config = await loadConfig('robo', true)
const pluginsManifest = type === 'plugin' ? BASE_MANIFEST : await readPluginManifest(config?.plugins)
const api = await generateEntries<ApiEntry>('api', [])
const commands = await generateEntries<CommandEntry>('commands', Object.keys(generatedDefaults?.commands ?? {}))
Expand Down
5 changes: 3 additions & 2 deletions packages/robo/src/cli/utils/public.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { hasDependency } from './runtime-utils.js'
import { Compiler } from './compiler.js'
import { logger } from '../../core/logger.js'
import { cpSync, existsSync } from 'node:fs'
import { rm } from 'node:fs/promises'
Expand Down Expand Up @@ -51,10 +52,10 @@ export async function buildVite() {
mode: 'production'
}

const configPath = path.join(process.cwd(), 'config', 'vite.mjs')
const configPath = path.join(process.cwd(), 'config', `vite.${Compiler.isTypescriptProject() ? 'ts' : 'mjs'}`)
if (existsSync(configPath)) {
config = (await loadConfigFromFile(configEnv, configPath))?.config
} else if (existsSync(path.join(process.cwd(), 'vite.config.js'))) {
} else if (existsSync(path.join(process.cwd(), `vite.config.${Compiler.isTypescriptProject() ? 'ts' : 'js'}`))) {
config = (await loadConfigFromFile(configEnv))?.config
} else {
logger.debug('No Vite config found. Skipping...')
Expand Down
Loading

0 comments on commit 6f91bf0

Please sign in to comment.