import { confirm, intro, log, outro } from '@clack/prompts'; import { Args, Command } from '@oclif/core'; import fs from 'node:fs/promises'; import path from 'node:path'; import picocolors from 'picocolors'; import { suggestCloudSupportCommand, suggestLintCommand } from '../utils/command-suggestions'; import { getPackageJson, updatePackageJson } from '../utils/package'; import { ensureN8nPackage, getCommandHeader, onCancel, withCancelHandler } from '../utils/prompts'; export default class CloudSupport extends Command { static override description = 'Enable or disable cloud support for this node'; static override examples = [ '<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> enable', '<%= config.bin %> <%= command.id %> disable', ]; static override args = { action: Args.string({ description: 'Action to perform (defaults to showing current status)', required: false, options: ['enable', 'disable'], }), }; async run(): Promise { const { args } = await this.parse(CloudSupport); await ensureN8nPackage('cloud-support'); const workingDir = process.cwd(); if (args.action === 'enable') { await this.enableCloudSupport(workingDir); } else if (args.action === 'disable') { await this.disableCloudSupport(workingDir); } else { await this.showCloudSupportStatus(workingDir); } } private async enableCloudSupport(workingDir: string): Promise { intro(await getCommandHeader('n8n-node cloud-support enable')); await this.updateEslintConfig(workingDir, true); log.success(`Updated ${picocolors.cyan('eslint.config.mjs')} to use default config`); await this.updateStrictMode(workingDir, true); log.success(`Enabled strict mode in ${picocolors.cyan('package.json')}`); const lintCommand = await suggestLintCommand(); outro( `Cloud support enabled. Run "${lintCommand}" to check compliance - your node must pass linting to be eligible for n8n Cloud publishing.`, ); } private async disableCloudSupport(workingDir: string): Promise { intro(await getCommandHeader('n8n-node cloud-support disable')); log.warning(`This will make your node ineligible for n8n Cloud verification! The following changes will be made: • Switch to ${picocolors.magenta('configWithoutCloudSupport')} in ${picocolors.cyan('eslint.config.mjs')} • Disable strict mode in ${picocolors.cyan('package.json')}`); const confirmed = await withCancelHandler( confirm({ message: 'Are you sure you want to disable cloud support?', initialValue: false, }), ); if (!confirmed) { onCancel('Cloud support unchanged'); return; } // 1. Update eslint.config.mjs await this.updateEslintConfig(workingDir, false); log.success( `Updated ${picocolors.cyan('eslint.config.mjs')} to use ${picocolors.magenta('configWithoutCloudSupport')}`, ); // 2. Disable strict mode in package.json await this.updateStrictMode(workingDir, false); log.success(`Disabled strict mode in ${picocolors.cyan('package.json')}`); outro( "Cloud support disabled. Your node may pass linting but it won't pass verification for n8n Cloud.", ); } private async updateEslintConfig(workingDir: string, enableCloud: boolean): Promise { const eslintConfigPath = path.resolve(workingDir, 'eslint.config.mjs'); const newConfig = enableCloud ? `import { config } from '@n8n/node-cli/eslint'; export default config; ` : `import { configWithoutCloudSupport } from '@n8n/node-cli/eslint'; export default configWithoutCloudSupport; `; await fs.writeFile(eslintConfigPath, newConfig, 'utf-8'); } private async updateStrictMode(workingDir: string, enableStrict: boolean): Promise { await updatePackageJson(workingDir, (packageJson) => { packageJson.n8n = packageJson.n8n ?? {}; packageJson.n8n.strict = enableStrict; return packageJson; }); } private async showCloudSupportStatus(workingDir: string): Promise { intro(await getCommandHeader('n8n-node cloud-support')); try { const packageJson = await getPackageJson(workingDir); const eslintConfigPath = path.resolve(workingDir, 'eslint.config.mjs'); // Check strict mode const isStrictMode = packageJson?.n8n?.strict === true; // Check eslint config let isUsingDefaultConfig = false; try { const eslintConfig = await fs.readFile(eslintConfigPath, 'utf-8'); const normalizedConfig = eslintConfig.replace(/\s+/g, ' ').trim(); const expectedDefault = "import { config } from '@n8n/node-cli/eslint'; export default config;"; isUsingDefaultConfig = normalizedConfig === expectedDefault; } catch { // eslint config doesn't exist or can't be read } const isCloudSupported = isStrictMode && isUsingDefaultConfig; if (isCloudSupported) { log.success(`✅ Cloud support is ${picocolors.green('ENABLED')} • Strict mode: ${picocolors.green('enabled')} • ESLint config: ${picocolors.green('using default config')} • Status: ${picocolors.green('eligible')} for n8n Cloud verification ${picocolors.dim('(if lint passes)')}`); } else { log.warning(`⚠️ Cloud support is ${picocolors.yellow('DISABLED')} • Strict mode: ${isStrictMode ? picocolors.green('enabled') : picocolors.red('disabled')} • ESLint config: ${isUsingDefaultConfig ? picocolors.green('using default config') : picocolors.red('using custom config')} • Status: ${picocolors.red('NOT eligible')} for n8n Cloud verification`); } const enableCommand = await suggestCloudSupportCommand('enable'); const disableCommand = await suggestCloudSupportCommand('disable'); const lintCommand = await suggestLintCommand(); log.info(`Available commands: • ${enableCommand} - Enable cloud support • ${disableCommand} - Disable cloud support • ${lintCommand} - Check compliance for cloud publishing`); outro('Use the commands above to change cloud support settings or check compliance'); } catch (error) { log.error('Failed to read package.json or determine cloud support status'); outro('Make sure you are in the root directory of your node package'); } } }