Skip to main content
Glama
cli.test.ts14.5 kB
import { TextDecoder, TextEncoder } from 'node:util'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; // Polyfill TextEncoder/TextDecoder for Node versions < 19 if (!(globalThis as any).TextEncoder) { (globalThis as any).TextEncoder = TextEncoder; } if (!(globalThis as any).TextDecoder) { (globalThis as any).TextDecoder = TextDecoder; } // Keep references to the original argv so we can restore it after each test const ORIGINAL_ARGV = [...process.argv]; // Helper to reset process.argv for each test const setProcessArgv = (args: string[]) => { process.argv = ['node', 'intlayer', ...args]; }; // Dynamic mock placeholders let buildMock: ReturnType<typeof vi.fn>; let pushMock: ReturnType<typeof vi.fn>; let pullMock: ReturnType<typeof vi.fn>; let fillMock: ReturnType<typeof vi.fn>; let getConfigMock: ReturnType<typeof vi.fn>; let pushConfigMock: ReturnType<typeof vi.fn>; let listContentDeclarationMock: ReturnType<typeof vi.fn>; let translateDocMock: ReturnType<typeof vi.fn>; let reviewDocMock: ReturnType<typeof vi.fn>; let liveSyncMock: ReturnType<typeof vi.fn>; // Hoisted mocks – evaluated before the tested module is imported vi.mock('./build', () => ({ build: (...args: any[]) => buildMock(...args), })); vi.mock('./push/push', () => ({ push: (...args: any[]) => pushMock(...args), })); vi.mock('./pull', () => ({ pull: (...args: any[]) => pullMock(...args), })); vi.mock('./fill/fill', () => ({ fill: (...args: any[]) => fillMock(...args), })); vi.mock('./config', () => ({ getConfig: (...args: any[]) => getConfigMock(...args), })); vi.mock('./pushConfig', () => ({ pushConfig: (...args: any[]) => pushConfigMock(...args), })); vi.mock('./listContentDeclaration', () => ({ listContentDeclaration: (...args: any[]) => listContentDeclarationMock(...args), })); vi.mock('./translateDoc', () => ({ translateDoc: (...args: any[]) => translateDocMock(...args), })); vi.mock('./reviewDoc', () => ({ reviewDoc: (...args: any[]) => reviewDocMock(...args), })); vi.mock('./liveSync', () => ({ liveSync: (...args: any[]) => liveSyncMock(...args), })); // Mock getParentPackageJSON utility vi.mock('./utils/getParentPackageJSON', () => ({ getParentPackageJSON: vi.fn(() => ({ name: '@intlayer/cli', version: '1.0.0', description: 'Intlayer CLI', })), })); // Mock Node.js modules vi.mock('node:path', () => ({ join: (...args: string[]) => args.join('/'), dirname: (path: string) => path.split('/').slice(0, -1).join('/'), })); vi.mock('node:fs', () => ({ existsSync: vi.fn(() => false), })); vi.mock('node:url', () => ({ fileURLToPath: vi.fn((url: string) => url.replace('file://', '')), })); vi.mock('node:module', () => ({ createRequire: vi.fn(() => require), })); // Mock configuration import that the CLI relies on for defaults vi.mock('@intlayer/config/built', () => ({ __esModule: true, default: {}, })); vi.mock('@intlayer/config', () => ({ __esModule: true, isESModule: false, // Minimal constants used by downstream packages spinnerFrames: ['-', '\\', '|', '/'], ANSIColors: { RESET: '', GREY: '', GREY_DARK: '', BLUE: '', RED: '', GREEN: '', YELLOW: '', MAGENTA: '', BEIGE: '', ORANGE: '', CYAN: '', WHITE: '', }, v: '✓', x: '✗', colorize: (s: string) => String(s), getExtension: vi.fn(() => 'cjs'), ESMxCJSRequire: require, // Minimal configuration getter getConfiguration: vi.fn(() => ({ content: { mainDir: process.cwd(), resultDir: '.intlayer', }, ai: {}, log: { mode: 'disabled', prefix: '' }, })), })); describe.skip('Intlayer CLI', () => { beforeEach(() => { // Reset module cache to load a fresh CLI instance for each test vi.resetModules(); // Re-initialise mocks for each test run buildMock = vi.fn(); pushMock = vi.fn(); pullMock = vi.fn(); fillMock = vi.fn(); getConfigMock = vi.fn(); pushConfigMock = vi.fn(); listContentDeclarationMock = vi.fn(); translateDocMock = vi.fn(); reviewDocMock = vi.fn(); liveSyncMock = vi.fn(); }); afterEach(() => { // Restore the original process arguments after each test process.argv = [...ORIGINAL_ARGV]; vi.clearAllMocks(); }); describe.skip('build command', () => { it('triggers build with the --watch flag', async () => { setProcessArgv(['build', '--watch']); const { setAPI } = await import('./cli'); setAPI(); expect(buildMock).toHaveBeenCalledTimes(1); expect(buildMock).toHaveBeenCalledWith( expect.objectContaining({ watch: true }) ); }); it('triggers build with verbose and custom prefix', async () => { setProcessArgv(['build', '--verbose', '--prefix', 'TEST']); const { setAPI } = await import('./cli'); setAPI(); expect(buildMock).toHaveBeenCalledTimes(1); expect(buildMock).toHaveBeenCalledWith( expect.objectContaining({ verbose: true, prefix: 'TEST', configOptions: expect.objectContaining({ override: expect.objectContaining({ log: expect.objectContaining({ verbose: true, prefix: '', }), }), }), }) ); }); it('triggers build with environment and base directory options', async () => { setProcessArgv([ 'build', '--env', 'production', '--base-dir', '/custom/path', ]); const { setAPI } = await import('./cli'); setAPI(); expect(buildMock).toHaveBeenCalledTimes(1); expect(buildMock).toHaveBeenCalledWith( expect.objectContaining({ env: 'production', baseDir: '/custom/path', configOptions: expect.objectContaining({ env: 'production', baseDir: '/custom/path', }), }) ); }); }); describe.skip('push command', () => { it('triggers push with dictionaries and delete flag', async () => { setProcessArgv([ 'push', '--dictionaries', 'id1', 'id2', '--deleteLocaleDictionary', ]); const { setAPI } = await import('./cli'); setAPI(); expect(pushMock).toHaveBeenCalledTimes(1); const pushOptions = pushMock.mock.calls[0][0] as Record<string, unknown>; expect(pushOptions.dictionaries).toEqual(['id1', 'id2']); expect(pushOptions.deleteLocaleDictionary).toBe(true); }); it('triggers push with keep flag and git options', async () => { setProcessArgv([ 'push', '--keepLocaleDictionary', '--uncommitted', '--git-diff-base', 'main', ]); const { setAPI } = await import('./cli'); setAPI(); expect(pushMock).toHaveBeenCalledTimes(1); const pushOptions = pushMock.mock.calls[0][0] as Record<string, unknown>; expect(pushOptions.keepLocaleDictionary).toBe(true); expect(pushOptions.gitOptions).toEqual( expect.objectContaining({ mode: ['uncommitted'], baseRef: 'main', absolute: true, }) ); }); }); describe.skip('pull command', () => { it('triggers pull with specific dictionaries', async () => { setProcessArgv(['pull', '--dictionaries', 'dict1', 'dict2']); const { setAPI } = await import('./cli'); setAPI(); expect(pullMock).toHaveBeenCalledTimes(1); expect(pullMock).toHaveBeenCalledWith( expect.objectContaining({ dictionaries: ['dict1', 'dict2'], }) ); }); it('triggers pull with custom path for new dictionaries', async () => { setProcessArgv(['pull', '--newDictionariesPath', '/custom/dict/path']); const { setAPI } = await import('./cli'); setAPI(); expect(pullMock).toHaveBeenCalledTimes(1); expect(pullMock).toHaveBeenCalledWith( expect.objectContaining({ newDictionariesPath: '/custom/dict/path', }) ); }); }); describe.skip('config commands', () => { it('triggers config get with environment options', async () => { setProcessArgv(['config', 'get', '--env-file', '.env.local']); const { setAPI } = await import('./cli'); setAPI(); expect(getConfigMock).toHaveBeenCalledTimes(1); expect(getConfigMock).toHaveBeenCalledWith( expect.objectContaining({ envFile: '.env.local', configOptions: expect.objectContaining({ envFile: '.env.local', }), }) ); }); it('triggers config push', async () => { setProcessArgv(['config', 'push']); const { setAPI } = await import('./cli'); setAPI(); expect(pushConfigMock).toHaveBeenCalledTimes(1); }); }); describe.skip('fill command', () => { it('triggers fill with complete mode and AI options', async () => { setProcessArgv([ 'fill', '--mode', 'complete', '--provider', 'openai', '--model', 'gpt-4', '--temperature', '0.7', ]); const { setAPI } = await import('./cli'); setAPI(); expect(fillMock).toHaveBeenCalledTimes(1); expect(fillMock).toHaveBeenCalledWith( expect.objectContaining({ mode: 'complete', provider: 'openai', model: 'gpt-4', temperature: '0.7', aiOptions: expect.objectContaining({ provider: 'openai', model: 'gpt-4', temperature: '0.7', }), }) ); }); it('triggers fill with file filters and locale options', async () => { setProcessArgv([ 'fill', '--file', 'dict1.ts', 'dict2.ts', '--source-locale', 'en', '--output-locales', 'fr', 'es', '--keys', 'button', 'title', ]); const { setAPI } = await import('./cli'); setAPI(); expect(fillMock).toHaveBeenCalledTimes(1); expect(fillMock).toHaveBeenCalledWith( expect.objectContaining({ file: ['dict1.ts', 'dict2.ts'], sourceLocale: 'en', outputLocales: ['fr', 'es'], keys: ['button', 'title'], }) ); }); it('triggers fill with path filters and excluded keys', async () => { setProcessArgv([ 'fill', '--path-filter', 'src/**/*.ts', '--excluded-keys', 'debug', 'test', ]); const { setAPI } = await import('./cli'); setAPI(); expect(fillMock).toHaveBeenCalledTimes(1); expect(fillMock).toHaveBeenCalledWith( expect.objectContaining({ pathFilter: ['src/**/*.ts'], excludedKeys: ['debug', 'test'], }) ); }); }); describe.skip('doc commands', () => { it('triggers doc translate with AI and locale options', async () => { setProcessArgv([ 'doc', 'translate', '--doc-pattern', 'docs/**/*.md', '--locales', 'fr', 'es', '--base-locale', 'en', '--api-key', 'test-key', ]); const { setAPI } = await import('./cli'); setAPI(); expect(translateDocMock).toHaveBeenCalledTimes(1); expect(translateDocMock).toHaveBeenCalledWith( expect.objectContaining({ docPattern: ['docs/**/*.md'], locales: ['fr', 'es'], baseLocale: 'en', aiOptions: expect.objectContaining({ apiKey: 'test-key', }), }) ); }); it('triggers doc review with excluded patterns and processing options', async () => { setProcessArgv([ 'doc', 'review', '--excluded-glob-pattern', 'node_modules/**', '--nb-simultaneous-file-processed', '5', '--custom-prompt', 'Review for clarity', ]); const { setAPI } = await import('./cli'); setAPI(); expect(reviewDocMock).toHaveBeenCalledTimes(1); expect(reviewDocMock).toHaveBeenCalledWith( expect.objectContaining({ excludedGlobPattern: ['node_modules/**'], nbSimultaneousFileProcessed: '5', aiOptions: expect.objectContaining({ customPrompt: 'Review for clarity', }), }) ); }); }); describe.skip('content list command', () => { it('triggers content list', async () => { setProcessArgv(['content', 'list']); const { setAPI } = await import('./cli'); setAPI(); expect(listContentDeclarationMock).toHaveBeenCalledTimes(1); }); }); describe.skip('git options integration', () => { it('correctly parses git diff options', async () => { setProcessArgv([ 'fill', '--git-diff', '--git-diff-base', 'origin/main', '--git-diff-current', 'HEAD', '--untracked', ]); const { setAPI } = await import('./cli'); setAPI(); expect(fillMock).toHaveBeenCalledTimes(1); expect(fillMock).toHaveBeenCalledWith( expect.objectContaining({ gitOptions: expect.objectContaining({ mode: ['gitDiff', 'untracked'], baseRef: 'origin/main', currentRef: 'HEAD', absolute: true, }), }) ); }); }); describe.skip('alias commands', () => { it('supports dictionary alias for build', async () => { setProcessArgv(['dictionary', 'build', '--watch']); const { setAPI } = await import('./cli'); setAPI(); expect(buildMock).toHaveBeenCalledTimes(1); expect(buildMock).toHaveBeenCalledWith( expect.objectContaining({ watch: true }) ); }); it('supports dic alias for push', async () => { setProcessArgv(['dic', 'push', '--dictionaries', 'test']); const { setAPI } = await import('./cli'); setAPI(); expect(pushMock).toHaveBeenCalledTimes(1); expect(pushMock).toHaveBeenCalledWith( expect.objectContaining({ dictionaries: ['test'], }) ); }); it('supports conf alias for config get', async () => { setProcessArgv(['conf', 'get']); const { setAPI } = await import('./cli'); setAPI(); expect(getConfigMock).toHaveBeenCalledTimes(1); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server