import { inspectCodexEnvironmentConfig } from './setup-script-import-codex-environment' import { inspectPackageManagerSetupCandidate } from './setup-script-package-manager-suggestion' import type { SetupScriptImportProvider } from './setup-script-import-providers' import { isSetupScriptImportFieldWithinLimit, isSetupScriptImportTextWithinLimit, SETUP_SCRIPT_IMPORT_MAX_CMUX_COMMANDS, SETUP_SCRIPT_IMPORT_MAX_KEYWORDS, SETUP_SCRIPT_IMPORT_MAX_UNSUPPORTED_FIELDS } from './setup-script-import-limits' import { joinSetupScriptImportCommands, normalizeSetupScriptImportCommand, pushSetupScriptImportUnsupportedField } from './setup-script-import-command-limits' export type SetupScriptImportCandidate = { provider: SetupScriptImportProvider label: string files: string[] setup: string archive?: string unsupportedFields?: string[] } export type SetupScriptImportFileRead = (relativePath: string) => Promise export type SetupScriptImportFileExists = (relativePath: string) => Promise const SUPERSET_CONFIG_PATH = '.superset/config.json' const SUPERSET_LOCAL_CONFIG_PATH = '.superset/config.local.json' const CONDUCTOR_CONFIG_PATH = 'conductor.json' const CMUX_CONFIG_PATHS = ['.cmux/cmux.json', 'cmux.json'] as const export async function inspectSetupScriptImportCandidates( readFile: SetupScriptImportFileRead, options?: { fileExists?: SetupScriptImportFileExists } ): Promise { const boundedReadFile: SetupScriptImportFileRead = async (relativePath) => { const content = await readFile(relativePath) return content !== null && isSetupScriptImportTextWithinLimit(content) ? content : null } const candidates = await Promise.all([ inspectSupersetConfig(boundedReadFile), inspectConductorConfig(boundedReadFile), inspectCodexEnvironmentConfig(boundedReadFile), inspectCmuxConfig(boundedReadFile), inspectPackageManagerSetupCandidate(boundedReadFile, options?.fileExists) ]) return candidates.filter( (candidate): candidate is SetupScriptImportCandidate => candidate != null ) } async function inspectSupersetConfig( readFile: SetupScriptImportFileRead ): Promise { const config = parseJsonObject(await readFile(SUPERSET_CONFIG_PATH)) if (!config) { return null } const localConfig = parseJsonObject(await readFile(SUPERSET_LOCAL_CONFIG_PATH)) const unsupportedFields = collectUnsupportedFields(config, ['run', 'cwd']) const files = localConfig ? [SUPERSET_CONFIG_PATH, SUPERSET_LOCAL_CONFIG_PATH] : [SUPERSET_CONFIG_PATH] if (localConfig) { unsupportedFields.push( ...collectUnsupportedFields(localConfig, ['run', 'cwd']).map( (field) => `config.local.${field}` ) ) } const setup = resolveSupersetScriptValue( config.setup, localConfig?.setup, 'setup', unsupportedFields ) if (!setup) { return null } collectUnsupportedScriptObjectFields(config.setup, 'setup', unsupportedFields) collectUnsupportedScriptObjectFields(config.teardown, 'teardown', unsupportedFields) return { provider: 'superset', label: 'Superset', files, setup, archive: resolveSupersetScriptValue( config.teardown, localConfig?.teardown, 'teardown', unsupportedFields ) || undefined, unsupportedFields } } async function inspectConductorConfig( readFile: SetupScriptImportFileRead ): Promise { const config = parseJsonObject(await readFile(CONDUCTOR_CONFIG_PATH)) const scripts = asRecord(config?.scripts) if (!config || !scripts) { return null } const setup = normalizeSetupScriptImportCommand(scripts.setup) if (!setup) { return null } const unsupportedFields = collectUnsupportedFields(config, [ 'enterpriseDataPrivacy', 'runScriptMode' ]) for (const field of ['run', 'teardown'] as const) { if (normalizeSetupScriptImportCommand(scripts[field])) { unsupportedFields.push(`scripts.${field}`) } } return { provider: 'conductor', label: 'Conductor', files: [CONDUCTOR_CONFIG_PATH], setup, archive: normalizeSetupScriptImportCommand(scripts.archive) || undefined, unsupportedFields } } async function inspectCmuxConfig( readFile: SetupScriptImportFileRead ): Promise { for (const configPath of CMUX_CONFIG_PATHS) { const config = parseJsonObject(await readFile(configPath)) const candidate = config ? buildCmuxSetupCandidate(configPath, config) : null if (candidate) { return candidate } } return null } function parseJsonObject(content: string | null): Record | null { if (!content) { return null } try { return asRecord(JSON.parse(content)) } catch { return null } } function asRecord(value: unknown): Record | null { return value !== null && typeof value === 'object' && !Array.isArray(value) ? (value as Record) : null } function resolveSupersetScriptValue( baseValue: unknown, localValue: unknown, key: 'setup' | 'teardown', unsupportedFields: string[] ): string { const baseCommand = normalizeSetupScriptImportCommand(baseValue) if (localValue === undefined) { return baseCommand } if (typeof localValue === 'string' || Array.isArray(localValue)) { return normalizeSetupScriptImportCommand(localValue) } const localRecord = asRecord(localValue) if (!localRecord) { pushSetupScriptImportUnsupportedField(unsupportedFields, `config.local.${key}`) return baseCommand } for (const field in localRecord) { if (!Object.hasOwn(localRecord, field)) { continue } if (field !== 'before' && field !== 'after') { pushSetupScriptImportUnsupportedField(unsupportedFields, `config.local.${key}.${field}`) if (unsupportedFields.length >= SETUP_SCRIPT_IMPORT_MAX_UNSUPPORTED_FIELDS) { break } } } const beforeCommand = normalizeSetupScriptImportCommand(localRecord.before) const afterCommand = normalizeSetupScriptImportCommand(localRecord.after) return joinSetupScriptImportCommands([beforeCommand, baseCommand, afterCommand].filter(Boolean)) } function buildCmuxSetupCandidate( configPath: string, config: Record ): SetupScriptImportCandidate | null { const commands = Array.isArray(config.commands) ? config.commands : [] if (commands.length > SETUP_SCRIPT_IMPORT_MAX_CMUX_COMMANDS) { return null } for (let index = 0; index < commands.length; index++) { const command = asRecord(commands[index]) if (!command || !isCmuxSetupCommand(command)) { continue } const setup = normalizeSetupScriptImportCommand(command.command) if (!setup) { continue } return { provider: 'cmux', label: 'cmux', files: [configPath], setup, unsupportedFields: collectUnsupportedCmuxCommandFields(command, index) } } return null } function isCmuxSetupCommand(command: Record): boolean { if ( typeof command.command !== 'string' || !isSetupScriptImportFieldWithinLimit(command.command) || !command.command.trim() ) { return false } const name = normalizeMatchText(command.name) const title = normalizeMatchText(command.title) const labels = [name, title].filter(Boolean) if ( labels.some((label) => ['setup', 'project setup', 'workspace setup', 'repository setup'].includes(label) ) ) { return true } const keywords = getStringArray(command.keywords).map(normalizeMatchText) const hasSetupKeyword = keywords.some((keyword) => ['setup', 'init', 'initialize', 'install'].includes(keyword) ) if (!hasSetupKeyword) { return false } const commandText = normalizeMatchText(command.command) return labels.some((label) => label.includes('setup')) || /\bsetup\b/.test(commandText) } function normalizeMatchText(value: unknown): string { return typeof value === 'string' && isSetupScriptImportFieldWithinLimit(value) ? value.trim().toLowerCase().replace(/\s+/g, ' ') : '' } function getStringArray(value: unknown): string[] { return Array.isArray(value) && value.length <= SETUP_SCRIPT_IMPORT_MAX_KEYWORDS ? value.filter((item): item is string => typeof item === 'string') : [] } function collectUnsupportedCmuxCommandFields( command: Record, commandIndex: number ): string[] { const supportedFields = new Set(['name', 'title', 'description', 'keywords', 'command']) const unsupportedFields: string[] = [] for (const field in command) { if (!Object.hasOwn(command, field)) { continue } if (!supportedFields.has(field)) { pushSetupScriptImportUnsupportedField(unsupportedFields, `commands.${commandIndex}.${field}`) if (unsupportedFields.length >= SETUP_SCRIPT_IMPORT_MAX_UNSUPPORTED_FIELDS) { break } } } return unsupportedFields } function collectUnsupportedFields( source: Record, fieldNames: readonly string[] ): string[] { return fieldNames.filter((field) => source[field] !== undefined) } function collectUnsupportedScriptObjectFields( value: unknown, prefix: string, unsupportedFields: string[] ): void { const record = asRecord(value) if (!record) { return } for (const field of ['before', 'after'] as const) { if (record[field] !== undefined) { pushSetupScriptImportUnsupportedField(unsupportedFields, `${prefix}.${field}`) } } }