@sanity/cli
Version:
Sanity CLI tool for managing Sanity installations, managing plugins, schemas and datasets
173 lines (148 loc) • 4.72 kB
text/typescript
/* eslint-disable no-sync */
/**
* Reads the Sanity CLI config from one of the following files (in preferred order):
* - sanity.cli.js
* - sanity.cli.ts
*
* Note: There are two ways of using this:
* a) `getCliConfig(cwd)`
* b) `getCliConfig(cwd, {forked: true})`
*
* Approach a is generally a bit faster as it avoids the forking startup time, while
* approach b could be considered "safer" since any side-effects of running the config
* file will not bleed into the current CLI process directly.
*/
import fs from 'node:fs'
import path from 'node:path'
import {Worker} from 'node:worker_threads'
import {type CliConfig, type SanityJson} from '../types'
import {getCliWorkerPath} from './cliWorker'
import {dynamicRequire} from './dynamicRequire'
export type CliMajorVersion = 2 | 3
export type CliConfigResult =
| {config: SanityJson; path: string; version: 2}
| {config: CliConfig; path: string; version: 3}
| {config: null; path: string; version: CliMajorVersion}
export async function getCliConfig(
cwd: string,
{forked}: {forked?: boolean} = {},
): Promise<CliConfigResult | null> {
if (forked) {
try {
return await getCliConfigForked(cwd)
} catch {
// Intentional noop - try unforked variant
}
}
const {unregister} = __DEV__
? {unregister: () => undefined}
: // eslint-disable-next-line @typescript-eslint/no-require-imports
require('esbuild-register/dist/node').register({supported: {'dynamic-import': true}})
try {
const v3Config = getSanityCliConfig(cwd)
if (v3Config) {
return v3Config
}
return getSanityJsonConfig(cwd)
} catch (err) {
throw err
} finally {
unregister()
}
}
export function getCliConfigSync(cwd: string): CliConfigResult | null {
const v3Config = getSanityCliConfig(cwd)
return v3Config ? v3Config : getSanityJsonConfig(cwd)
}
async function getCliConfigForked(cwd: string): Promise<CliConfigResult | null> {
const workerPath = await getCliWorkerPath('getCliConfig')
return new Promise((resolve, reject) => {
const worker = new Worker(workerPath, {
workerData: cwd,
env: process.env,
})
worker.on('message', (message) => {
if (message.type === 'config') {
resolve(message.config)
} else {
const error = new Error(message.error)
;(error as any).type = message.errorType
reject(new Error(message.error))
}
})
worker.on('error', reject)
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${code}`))
}
})
})
}
function getSanityJsonConfig(cwd: string): CliConfigResult | null {
const configPath = path.join(cwd, 'sanity.json')
if (!fs.existsSync(configPath)) {
return null
}
return {
config: loadJsonConfig(configPath),
path: configPath,
version: 2,
}
}
function getSanityCliConfig(cwd: string): CliConfigResult | null {
const jsConfigPath = path.join(cwd, 'sanity.cli.js')
const tsConfigPath = path.join(cwd, 'sanity.cli.ts')
const [js, ts] = [fs.existsSync(jsConfigPath), fs.existsSync(tsConfigPath)]
if (!js && !ts) {
return null
}
if (!js && ts) {
return {
config: importConfig(tsConfigPath),
path: tsConfigPath,
version: 3,
}
}
if (js && ts) {
warn('Found both `sanity.cli.js` and `sanity.cli.ts` - using sanity.cli.js')
}
return {
config: importConfig(jsConfigPath),
path: jsConfigPath,
version: 3,
}
}
function loadJsonConfig(filePath: string): SanityJson | null {
try {
const content = fs.readFileSync(filePath, 'utf8')
return JSON.parse(content)
} catch (err) {
console.error(`Error reading "${filePath}": ${err.message}`)
return null
}
}
function importConfig(filePath: string): CliConfig | null {
try {
const config = dynamicRequire<CliConfig | {default: CliConfig} | null>(filePath)
if (config === null || typeof config !== 'object') {
throw new Error('Module export is not a configuration object')
}
return 'default' in config ? config.default : config
} catch (err) {
// If attempting to import `defineCliConfig` or similar from `sanity/cli`,
// accept the fact that it might not be installed. Instead, let the CLI
// give a warning about the `sanity` module not being installed
if (err.code === 'MODULE_NOT_FOUND' && err.message.includes('sanity/cli')) {
return null
}
console.error(`Error reading "${filePath}": ${err.message}`)
return null
}
}
function warn(warning: string) {
if (typeof process.send === 'function') {
process.send({type: 'warning', warning})
} else {
console.warn(warning)
}
}