From 2c2ec7268fc3319524e387f61dcb45075070e0d4 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Thu, 5 Feb 2026 14:50:41 -0300 Subject: [PATCH 01/17] Add mimic config --- packages/cli/src/commands/build.ts | 18 +++++++- packages/cli/src/commands/compile.ts | 17 +++++++- packages/cli/src/commands/functions.ts | 58 ++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 packages/cli/src/commands/functions.ts diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index a9b617ce..f51f4b5f 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -4,6 +4,9 @@ import { FlagsType } from '../types' import Codegen from './codegen' import Compile from './compile' +import Functions, { FunctionConfig } from './functions' +import path from 'path/win32' +import log from '../log' export type BuildFlags = FlagsType @@ -15,13 +18,26 @@ export default class Build extends Command { ] static override flags = { + ...Functions.flags, ...Codegen.flags, ...Compile.flags, } public async run(): Promise { const { flags } = await this.parse(Build) - await Build.build(this, flags) + await Build.buildFunctions(this, Functions.filterFunctions(this, flags), flags) + } + + public static async buildFunctions(cmd: Command, functions: FunctionConfig[], flags: BuildFlags): Promise { + for (const func of functions) { + log.startAction(`Starting building for function ${func.name}`) + await Build.build(cmd, { + ...flags, + function: func.function, + 'build-directory': path.join(flags['build-directory'], func.name), + manifest: func.manifest, + }) + } } public static async build(cmd: Command, flags: BuildFlags): Promise { diff --git a/packages/cli/src/commands/compile.ts b/packages/cli/src/commands/compile.ts index ef6130ca..26dc0c0b 100644 --- a/packages/cli/src/commands/compile.ts +++ b/packages/cli/src/commands/compile.ts @@ -7,6 +7,8 @@ import { execBinCommand } from '../lib/packageManager' import log from '../log' import { FlagsType } from '../types' +import Functions, { FunctionConfig } from './functions' + export type CompileFlags = FlagsType export default class Compile extends Command { @@ -17,6 +19,7 @@ export default class Compile extends Command { ] static override flags = { + ...Functions.flags, function: Flags.string({ char: 'f', description: 'Function to compile', default: 'src/function.ts' }), manifest: Flags.string({ char: 'm', description: 'Manifest to validate', default: 'manifest.yaml' }), 'build-directory': Flags.string({ char: 'b', description: 'Output directory for compilation', default: './build' }), @@ -24,7 +27,19 @@ export default class Compile extends Command { public async run(): Promise { const { flags } = await this.parse(Compile) - await Compile.compile(this, flags) + await Compile.compileFunctions(this, Functions.filterFunctions(this, flags), flags) + } + + public static async compileFunctions(cmd: Command, functions: FunctionConfig[], flags: CompileFlags): Promise { + for (const func of functions) { + log.startAction(`Starting compilation for function ${func.name}`) + await this.compile(cmd, { + ...flags, + function: func.function, + 'build-directory': path.join(flags['build-directory'], func.name), + manifest: func.manifest, + }) + } } public static async compile( diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts new file mode 100644 index 00000000..d9fd12f5 --- /dev/null +++ b/packages/cli/src/commands/functions.ts @@ -0,0 +1,58 @@ +import { Command, Flags } from '@oclif/core' + +import { FlagsType } from '../types' + +export type FunctionsFlags = FlagsType + +export type FunctionConfig = { + name: string + manifest: string + function: string + buildDirectory: string +} + +export default class Functions extends Command { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + run(): Promise { + throw new Error('Method not implemented.') + } + + static override description = 'Authenticate with Mimic by storing your API key locally' + + static override examples = [ + '<%= config.bin %> <%= command.id %>', + '<%= config.bin %> <%= command.id %> --profile staging', + '<%= config.bin %> <%= command.id %> --profile production --api-key YOUR_API_KEY', + ] + + static MIMIC_CONFIG_FILE = 'mimic.yaml' + + static flags = { + 'config-file': Flags.string({ + char: 'f', + description: `Path to the ${Functions.MIMIC_CONFIG_FILE} file, this overrides other parameters like build-directory and function`, + default: Functions.MIMIC_CONFIG_FILE, + }), + include: Flags.string({ + description: `When ${Functions.MIMIC_CONFIG_FILE} exists, only run tasks with these names (space-separated)`, + multiple: true, + exclusive: ['exclude'], + }), + exclude: Flags.string({ + description: `When ${Functions.MIMIC_CONFIG_FILE} exists, exclude tasks with these names (space-separated)`, + multiple: true, + exclusive: ['include'], + }), + } + + public static filterFunctions(cmd: Command, flags: FunctionsFlags): FunctionConfig[] { + return [ + { + name: 'Pepe', + manifest: 'pepito.yaml', + function: 'src/pepe.ts', + buildDirectory: 'build/pepe', + }, + ] + } +} From 71c9b6d761d11b7d1214be5a31ffc0971fd386f3 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Thu, 5 Feb 2026 17:35:21 -0300 Subject: [PATCH 02/17] Implement multi task --- packages/cli/src/commands/build.ts | 18 +------ packages/cli/src/commands/codegen.ts | 13 +++-- packages/cli/src/commands/compile.ts | 26 ++++------ packages/cli/src/commands/deploy.ts | 4 +- packages/cli/src/commands/functions.ts | 69 +++++++++++++++++++------- packages/cli/src/commands/test.ts | 4 +- 6 files changed, 78 insertions(+), 56 deletions(-) diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index f51f4b5f..96518fa8 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -4,9 +4,7 @@ import { FlagsType } from '../types' import Codegen from './codegen' import Compile from './compile' -import Functions, { FunctionConfig } from './functions' -import path from 'path/win32' -import log from '../log' +import Functions from './functions' export type BuildFlags = FlagsType @@ -25,19 +23,7 @@ export default class Build extends Command { public async run(): Promise { const { flags } = await this.parse(Build) - await Build.buildFunctions(this, Functions.filterFunctions(this, flags), flags) - } - - public static async buildFunctions(cmd: Command, functions: FunctionConfig[], flags: BuildFlags): Promise { - for (const func of functions) { - log.startAction(`Starting building for function ${func.name}`) - await Build.build(cmd, { - ...flags, - function: func.function, - 'build-directory': path.join(flags['build-directory'], func.name), - manifest: func.manifest, - }) - } + await Functions.runFunctions(this, flags, Build.build, 'build') } public static async build(cmd: Command, flags: BuildFlags): Promise { diff --git a/packages/cli/src/commands/codegen.ts b/packages/cli/src/commands/codegen.ts index 7e3594cc..e359c2af 100644 --- a/packages/cli/src/commands/codegen.ts +++ b/packages/cli/src/commands/codegen.ts @@ -7,6 +7,8 @@ import { AbisInterfaceGenerator, InputsInterfaceGenerator, ManifestHandler } fro import log from '../log' import { FlagsType, Manifest } from '../types' +import Functions, { DefaultFunctionConfig } from './functions' + export type CodegenFlags = FlagsType export default class Codegen extends Command { @@ -17,11 +19,16 @@ export default class Codegen extends Command { ] static override flags = { - manifest: Flags.string({ char: 'm', description: 'Specify a custom manifest file path', default: 'manifest.yaml' }), + ...Functions.flags, + manifest: Flags.string({ + char: 'm', + description: 'Specify a custom manifest file path', + default: DefaultFunctionConfig.manifest, + }), 'types-directory': Flags.string({ char: 't', description: 'Output directory for generated types', - default: './src/types', + default: DefaultFunctionConfig['types-directory'], }), clean: Flags.boolean({ char: 'c', @@ -32,7 +39,7 @@ export default class Codegen extends Command { public async run(): Promise { const { flags } = await this.parse(Codegen) - await Codegen.codegen(this, flags) + await Functions.runFunctions(this, flags, Codegen.codegen, 'code generation') } public static async codegen(cmd: Command, flags: CodegenFlags): Promise { diff --git a/packages/cli/src/commands/compile.ts b/packages/cli/src/commands/compile.ts index 26dc0c0b..ee2f08dc 100644 --- a/packages/cli/src/commands/compile.ts +++ b/packages/cli/src/commands/compile.ts @@ -7,7 +7,7 @@ import { execBinCommand } from '../lib/packageManager' import log from '../log' import { FlagsType } from '../types' -import Functions, { FunctionConfig } from './functions' +import Functions, { DefaultFunctionConfig } from './functions' export type CompileFlags = FlagsType @@ -20,26 +20,18 @@ export default class Compile extends Command { static override flags = { ...Functions.flags, - function: Flags.string({ char: 'f', description: 'Function to compile', default: 'src/function.ts' }), - manifest: Flags.string({ char: 'm', description: 'Manifest to validate', default: 'manifest.yaml' }), - 'build-directory': Flags.string({ char: 'b', description: 'Output directory for compilation', default: './build' }), + function: Flags.string({ char: 'f', description: 'Function to compile', default: DefaultFunctionConfig.function }), + manifest: Flags.string({ char: 'm', description: 'Manifest to validate', default: DefaultFunctionConfig.manifest }), + 'build-directory': Flags.string({ + char: 'b', + description: 'Output directory for compilation', + default: DefaultFunctionConfig['build-directory'], + }), } public async run(): Promise { const { flags } = await this.parse(Compile) - await Compile.compileFunctions(this, Functions.filterFunctions(this, flags), flags) - } - - public static async compileFunctions(cmd: Command, functions: FunctionConfig[], flags: CompileFlags): Promise { - for (const func of functions) { - log.startAction(`Starting compilation for function ${func.name}`) - await this.compile(cmd, { - ...flags, - function: func.function, - 'build-directory': path.join(flags['build-directory'], func.name), - manifest: func.manifest, - }) - } + await Functions.runFunctions(this, flags, Compile.compile, 'compilation') } public static async compile( diff --git a/packages/cli/src/commands/deploy.ts b/packages/cli/src/commands/deploy.ts index d25ca9b3..f8d62b71 100644 --- a/packages/cli/src/commands/deploy.ts +++ b/packages/cli/src/commands/deploy.ts @@ -11,6 +11,7 @@ import { FlagsType } from '../types' import Authenticate from './authenticate' import Build from './build' +import Functions from './functions' const MIMIC_REGISTRY_DEFAULT = 'https://api-protocol.mimic.fi' @@ -27,6 +28,7 @@ export default class Deploy extends Command { ] static override flags = { + ...Functions.flags, ...Authenticate.flags, ...Build.flags, 'build-directory': Flags.string({ @@ -40,7 +42,7 @@ export default class Deploy extends Command { public async run(): Promise { const { flags } = await this.parse(Deploy) - await Deploy.deploy(this, flags) + await Functions.runFunctions(this, flags, Deploy.deploy, 'deployment') } public static async deploy(cmd: Command, flags: DeployFlags): Promise { diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index d9fd12f5..0965510c 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -1,14 +1,26 @@ import { Command, Flags } from '@oclif/core' +import * as fs from 'fs' +import * as yaml from 'js-yaml' +import log from '../log' import { FlagsType } from '../types' export type FunctionsFlags = FlagsType +export const DefaultFunctionConfig = { + name: '', + manifest: 'manifest.yaml', + function: 'src/function.ts', + 'build-directory': './build', + 'types-directory': './src/types', +} as const + export type FunctionConfig = { name: string manifest: string function: string - buildDirectory: string + 'build-directory': string + 'types-directory': string } export default class Functions extends Command { @@ -17,19 +29,12 @@ export default class Functions extends Command { throw new Error('Method not implemented.') } - static override description = 'Authenticate with Mimic by storing your API key locally' - - static override examples = [ - '<%= config.bin %> <%= command.id %>', - '<%= config.bin %> <%= command.id %> --profile staging', - '<%= config.bin %> <%= command.id %> --profile production --api-key YOUR_API_KEY', - ] + static override description = 'Filters tasks based on a mimic.yaml configuration file' static MIMIC_CONFIG_FILE = 'mimic.yaml' static flags = { 'config-file': Flags.string({ - char: 'f', description: `Path to the ${Functions.MIMIC_CONFIG_FILE} file, this overrides other parameters like build-directory and function`, default: Functions.MIMIC_CONFIG_FILE, }), @@ -45,14 +50,42 @@ export default class Functions extends Command { }), } - public static filterFunctions(cmd: Command, flags: FunctionsFlags): FunctionConfig[] { - return [ - { - name: 'Pepe', - manifest: 'pepito.yaml', - function: 'src/pepe.ts', - buildDirectory: 'build/pepe', - }, - ] + public static async runFunctions>( + cmd: Command, + flags: T, + cmdLogic: (cmd: Command, flags: T) => Promise, + cmdActions: string + ): Promise { + const functions = Functions.filterFunctions(cmd, flags) + for (const func of functions) { + log.startAction(`Starting ${cmdActions} for function ${func.name}`) + await cmdLogic(cmd, { ...flags, ...func } as T) + } + } + + public static filterFunctions(cmd: Command, flags: FunctionsFlags & Partial): FunctionConfig[] { + if (!fs.existsSync(flags['config-file'])) { + // If doesn't exists return the default with the flags the user added + return [{ ...DefaultFunctionConfig, ...flags }] + } + + // Read and parse YAML file + const fileContents = fs.readFileSync(flags['config-file'], 'utf8') + const config = yaml.load(fileContents) as { tasks: FunctionConfig[] } + + // Get all tasks + let tasks = config.tasks || [] + + // Apply include filter if specified + if (flags.include && flags.include.length > 0) { + tasks = tasks.filter((task) => flags.include!.includes(task.name)) + } + + // Apply exclude filter if specified + if (flags.exclude && flags.exclude.length > 0) { + tasks = tasks.filter((task) => !flags.exclude!.includes(task.name)) + } + + return tasks } } diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index e742f8e8..b12a6f39 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -5,6 +5,7 @@ import { execBinCommand } from '../lib/packageManager' import { FlagsType } from '../types' import Build from './build' +import Functions from './functions' export type TestFlags = FlagsType @@ -14,6 +15,7 @@ export default class Test extends Command { static override examples = ['<%= config.bin %> <%= command.id %> --directory ./tests'] static override flags = { + ...Functions.flags, ...Build.flags, directory: Flags.string({ char: 'd', description: 'Path to the testing directory', default: './tests' }), 'skip-build': Flags.boolean({ description: 'Skip build before testing', default: false }), @@ -21,7 +23,7 @@ export default class Test extends Command { public async run(): Promise { const { flags } = await this.parse(Test) - await Test.test(this, flags) + await Functions.runFunctions(this, flags, Test.test, 'testing') } public static async test(cmd: Command, flags: TestFlags): Promise { From b86be303bea407f3e4777e16536ea1f19b071004 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Fri, 6 Feb 2026 09:20:28 -0300 Subject: [PATCH 03/17] Fix tests --- packages/cli/src/commands/codegen.ts | 4 ++-- packages/cli/src/commands/deploy.ts | 2 +- packages/cli/src/commands/init.ts | 4 ++-- packages/cli/src/commands/login.ts | 2 +- packages/cli/src/commands/test.ts | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/commands/codegen.ts b/packages/cli/src/commands/codegen.ts index e359c2af..7255efc9 100644 --- a/packages/cli/src/commands/codegen.ts +++ b/packages/cli/src/commands/codegen.ts @@ -66,8 +66,8 @@ export default class Codegen extends Command { } if (!fs.existsSync(typesDir)) fs.mkdirSync(typesDir, { recursive: true }) - this.generateAbisCode(manifest, typesDir, manifestDir) - this.generateInputsCode(manifest, typesDir) + Codegen.generateAbisCode(manifest, typesDir, manifestDir) + Codegen.generateInputsCode(manifest, typesDir) log.stopAction() } diff --git a/packages/cli/src/commands/deploy.ts b/packages/cli/src/commands/deploy.ts index f8d62b71..d62f006d 100644 --- a/packages/cli/src/commands/deploy.ts +++ b/packages/cli/src/commands/deploy.ts @@ -71,7 +71,7 @@ export default class Deploy extends Command { } log.startAction('Uploading to Mimic Registry') - const CID = await this.uploadToRegistry(cmd, neededFiles, credentials, registryUrl) + const CID = await Deploy.uploadToRegistry(cmd, neededFiles, credentials, registryUrl) console.log(`IPFS CID: ${log.highlightText(CID)}`) log.stopAction() diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index c6b0f6ef..ac25fe29 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -79,8 +79,8 @@ export default class Init extends Command { cmd.error(`Failed to clone template repository. Details: ${error}`) } - this.installDependencies(absDir) - this.runCodegen(absDir) + Init.installDependencies(absDir) + Init.runCodegen(absDir) log.stopAction() console.log('New project initialized!') } diff --git a/packages/cli/src/commands/login.ts b/packages/cli/src/commands/login.ts index 3ddd9df2..47e372a8 100644 --- a/packages/cli/src/commands/login.ts +++ b/packages/cli/src/commands/login.ts @@ -74,7 +74,7 @@ export default class Login extends Command { } } - this.saveAndConfirm(cmd, profileName || CredentialsManager.getDefaultProfileName(), apiKey, flags['force-login']) + Login.saveAndConfirm(cmd, profileName || CredentialsManager.getDefaultProfileName(), apiKey, flags['force-login']) } private static async saveAndConfirm( diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index b12a6f39..44a7bcaa 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -23,7 +23,7 @@ export default class Test extends Command { public async run(): Promise { const { flags } = await this.parse(Test) - await Functions.runFunctions(this, flags, Test.test, 'testing') + await Test.test(this, flags) } public static async test(cmd: Command, flags: TestFlags): Promise { @@ -31,7 +31,7 @@ export default class Test extends Command { const baseDir = path.resolve('./') const testPath = path.join(baseDir, directory) - if (!skipBuild) await Build.build(cmd, flags) + if (!skipBuild) await Functions.runFunctions(cmd, flags, Build.build, 'building') const result = execBinCommand('tsx', ['./node_modules/mocha/bin/mocha.js', `${testPath}/**/*.spec.ts`], baseDir) cmd.exit(result.status ?? 1) From b8a602748e8c639630afbdca94300a9bca07383a Mon Sep 17 00:00:00 2001 From: Agustincito Date: Fri, 6 Feb 2026 13:21:01 -0300 Subject: [PATCH 04/17] Improve parsing and add test --- packages/cli/src/commands/functions.ts | 47 ++- packages/cli/tests/commands/functions.spec.ts | 365 ++++++++++++++++++ 2 files changed, 399 insertions(+), 13 deletions(-) create mode 100644 packages/cli/tests/commands/functions.spec.ts diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index 0965510c..91d99d95 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -1,12 +1,25 @@ import { Command, Flags } from '@oclif/core' import * as fs from 'fs' import * as yaml from 'js-yaml' +import { z } from 'zod' import log from '../log' import { FlagsType } from '../types' export type FunctionsFlags = FlagsType +export const FunctionConfigSchema = z.object({ + name: z.string().min(1, 'Function name is required'), + manifest: z.string().min(1, 'Manifest path is required'), + function: z.string().min(1, 'Function path is required'), + 'build-directory': z.string().min(1, 'Build directory is required'), + 'types-directory': z.string().min(1, 'Types directory is required'), +}) + +export const MimicConfigSchema = z.object({ + tasks: z.array(FunctionConfigSchema).min(1, 'At least one task is required'), +}) + export const DefaultFunctionConfig = { name: '', manifest: 'manifest.yaml', @@ -42,11 +55,13 @@ export default class Functions extends Command { description: `When ${Functions.MIMIC_CONFIG_FILE} exists, only run tasks with these names (space-separated)`, multiple: true, exclusive: ['exclude'], + char: 'i', }), exclude: Flags.string({ description: `When ${Functions.MIMIC_CONFIG_FILE} exists, exclude tasks with these names (space-separated)`, multiple: true, exclusive: ['include'], + char: 'e', }), } @@ -69,23 +84,29 @@ export default class Functions extends Command { return [{ ...DefaultFunctionConfig, ...flags }] } - // Read and parse YAML file const fileContents = fs.readFileSync(flags['config-file'], 'utf8') - const config = yaml.load(fileContents) as { tasks: FunctionConfig[] } + const rawConfig = yaml.load(fileContents) - // Get all tasks - let tasks = config.tasks || [] + try { + const config = MimicConfigSchema.parse(rawConfig) - // Apply include filter if specified - if (flags.include && flags.include.length > 0) { - tasks = tasks.filter((task) => flags.include!.includes(task.name)) - } + let tasks = config.tasks || [] - // Apply exclude filter if specified - if (flags.exclude && flags.exclude.length > 0) { - tasks = tasks.filter((task) => !flags.exclude!.includes(task.name)) - } + if (flags.include && flags.include.length > 0) { + tasks = tasks.filter((task) => flags.include!.includes(task.name)) + } + + if (flags.exclude && flags.exclude.length > 0) { + tasks = tasks.filter((task) => !flags.exclude!.includes(task.name)) + } - return tasks + return tasks + } catch (error) { + if (error instanceof z.ZodError) { + const errors = error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('\n') + cmd.error(`Invalid ${Functions.MIMIC_CONFIG_FILE} configuration:\n${errors}`, { code: 'InvalidConfig' }) + } + throw error + } } } diff --git a/packages/cli/tests/commands/functions.spec.ts b/packages/cli/tests/commands/functions.spec.ts new file mode 100644 index 00000000..cbe52cc6 --- /dev/null +++ b/packages/cli/tests/commands/functions.spec.ts @@ -0,0 +1,365 @@ +import { Command } from '@oclif/core' +import { expect } from 'chai' +import * as fs from 'fs' +import * as sinon from 'sinon' + +import Functions, { FunctionConfigSchema, MimicConfigSchema } from '../../src/commands/functions' + +describe('Functions', () => { + const basePath = `${__dirname}/../fixtures` + const configFilePath = `${basePath}/mimic.yaml` + const validConfig = { + tasks: [ + { + name: 'task1', + manifest: 'manifest.yaml', + function: 'src/function.ts', + 'build-directory': './build', + 'types-directory': './src/types', + }, + { + name: 'task2', + manifest: 'src/task2/manifest.yaml', + function: 'src/task2/function.ts', + 'build-directory': './build/task2', + 'types-directory': './src/task2/types', + }, + ], + } + + describe('FunctionConfigSchema', () => { + context('when all required fields are present', () => { + it('validates successfully', () => { + const config = validConfig.tasks[0] + expect(() => FunctionConfigSchema.parse(config)).to.not.throw() + }) + }) + + context('when required fields are missing', () => { + context('when name is missing', () => { + it('throws error', () => { + const config = { ...validConfig.tasks[0], name: '' } + expect(() => FunctionConfigSchema.parse(config)).to.throw() + }) + }) + + context('when manifest is missing', () => { + it('throws error', () => { + const config = { ...validConfig.tasks[0], manifest: '' } + expect(() => FunctionConfigSchema.parse(config)).to.throw() + }) + }) + context('when function is missing', () => { + it('throws error', () => { + const config = { ...validConfig.tasks[0], function: '' } + expect(() => FunctionConfigSchema.parse(config)).to.throw() + }) + }) + context('when build-directory is missing', () => { + it('throws error', () => { + const config = { ...validConfig.tasks[0], 'build-directory': '' } + expect(() => FunctionConfigSchema.parse(config)).to.throw() + }) + }) + context('when types-directory is missing', () => { + it('throws error', () => { + const config = { ...validConfig.tasks[0], 'types-directory': '' } + expect(() => FunctionConfigSchema.parse(config)).to.throw() + }) + }) + }) + }) + + describe('MimicConfigSchema', () => { + context('when config has valid tasks array', () => { + it('validates successfully with single task', () => { + const config = { tasks: [validConfig.tasks[0]] } + expect(() => MimicConfigSchema.parse(config)).to.not.throw() + }) + + it('validates successfully with multiple tasks', () => { + expect(() => MimicConfigSchema.parse(validConfig)).to.not.throw() + }) + }) + + context('when tasks array is empty', () => { + it('throws validation error', () => { + const config = { tasks: [] } + expect(() => MimicConfigSchema.parse(config)).to.throw() + }) + }) + + context('when tasks array is missing', () => { + it('throws validation error', () => { + const config = {} + expect(() => MimicConfigSchema.parse(config)).to.throw() + }) + }) + + context('when a task in the array is invalid', () => { + it('throws validation error for invalid task', () => { + const config = { + tasks: [ + validConfig.tasks[0], + { ...validConfig.tasks[1], name: '' }, // Invalid: empty name + ], + } + expect(() => MimicConfigSchema.parse(config)).to.throw() + }) + }) + }) + + describe('filterFunctions', () => { + let cmdStub: sinon.SinonStubbedInstance + + beforeEach(() => { + cmdStub = sinon.createStubInstance(Command) + }) + + context('when config file does not exist', () => { + const flags = { + 'config-file': `${basePath}/nonexistent-mimic.yaml`, + include: [], + exclude: [], + } + + context('when no flags are provided', () => { + it('returns default', () => { + const result = Functions.filterFunctions(cmdStub, flags) + + expect(result).to.have.lengthOf(1) + expect(result[0].name).to.equal('') + expect(result[0].manifest).to.equal('manifest.yaml') + expect(result[0].function).to.equal('src/function.ts') + expect(result[0]['build-directory']).to.equal('./build') + expect(result[0]['types-directory']).to.equal('./src/types') + }) + }) + + context('when flags are provided', () => { + it('returns default config with overridden manifest', () => { + const result = Functions.filterFunctions(cmdStub, { + 'config-file': `${basePath}/nonexistent-mimic.yaml`, + manifest: 'custom-manifest.yaml', + include: [], + exclude: [], + }) + + expect(result[0].manifest).to.equal('custom-manifest.yaml') + }) + + it('returns default config with overridden types-directory', () => { + const result = Functions.filterFunctions(cmdStub, { + 'config-file': `${basePath}/nonexistent-mimic.yaml`, + 'types-directory': './custom/types', + include: [], + exclude: [], + }) + + expect(result[0]['types-directory']).to.equal('./custom/types') + }) + + it('returns default config with overridden build-directory', () => { + const result = Functions.filterFunctions(cmdStub, { + 'config-file': `${basePath}/nonexistent-mimic.yaml`, + 'build-directory': './custom/build', + include: [], + exclude: [], + }) + + expect(result[0]['build-directory']).to.equal('./custom/build') + }) + + it('returns default config with overridden function', () => { + const result = Functions.filterFunctions(cmdStub, { + 'config-file': `${basePath}/nonexistent-mimic.yaml`, + function: 'src/custom/function.ts', + include: [], + exclude: [], + }) + + expect(result[0].function).to.equal('src/custom/function.ts') + }) + }) + }) + + context('when config file exists', () => { + beforeEach(() => { + fs.mkdirSync(basePath, { recursive: true }) + fs.writeFileSync( + configFilePath, + ` +tasks: + - name: task1 + manifest: manifest.yaml + function: src/function.ts + build-directory: ./build + types-directory: ./src/types + - name: task2 + manifest: src/task2/manifest.yaml + function: src/task2/function.ts + build-directory: ./build/task2 + types-directory: ./src/task2/types + ` + ) + }) + + afterEach(() => { + if (fs.existsSync(configFilePath)) fs.unlinkSync(configFilePath) + }) + + context('when config is valid', () => { + it('returns all tasks', () => { + const flags = { + 'config-file': configFilePath, + include: [], + exclude: [], + } + + const result = Functions.filterFunctions(cmdStub, flags) + + expect(result).to.have.lengthOf(2) + expect(result[0].name).to.equal('task1') + expect(result[1].name).to.equal('task2') + }) + + context('when include filter is provided', () => { + it('returns only included tasks', () => { + const flags = { + 'config-file': configFilePath, + include: ['task1'], + exclude: [], + } + + const result = Functions.filterFunctions(cmdStub, flags) + + expect(result).to.have.lengthOf(1) + expect(result[0].name).to.equal('task1') + }) + + it('returns multiple included tasks', () => { + const flags = { + 'config-file': configFilePath, + include: ['task1', 'task2'], + exclude: [], + } + + const result = Functions.filterFunctions(cmdStub, flags) + + expect(result).to.have.lengthOf(2) + }) + + it('returns empty array when included task does not exist', () => { + const flags = { + 'config-file': configFilePath, + include: ['nonexistent'], + exclude: [], + } + + const result = Functions.filterFunctions(cmdStub, flags) + + expect(result).to.have.lengthOf(0) + }) + }) + + context('when exclude filter is provided', () => { + it('excludes specified tasks', () => { + const flags = { + 'config-file': configFilePath, + include: [], + exclude: ['task1'], + } + + const result = Functions.filterFunctions(cmdStub, flags) + + expect(result).to.have.lengthOf(1) + expect(result[0].name).to.equal('task2') + }) + + it('excludes multiple tasks', () => { + const flags = { + 'config-file': configFilePath, + include: [], + exclude: ['task1', 'task2'], + } + + const result = Functions.filterFunctions(cmdStub, flags) + + expect(result).to.have.lengthOf(0) + }) + + it('returns all tasks when excluding non-existent task', () => { + const flags = { + 'config-file': configFilePath, + include: [], + exclude: ['nonexistent'], + } + + const result = Functions.filterFunctions(cmdStub, flags) + + expect(result).to.have.lengthOf(2) + }) + }) + + context('when config is invalid', () => { + beforeEach(() => { + fs.writeFileSync( + configFilePath, + `tasks: + - name: task1 + manifest: manifest.yaml` + ) + }) + + it('throws error with validation message', () => { + const flags = { + 'config-file': configFilePath, + include: [], + exclude: [], + } + + expect(() => Functions.filterFunctions(cmdStub, flags)).to.throw() + expect(cmdStub.error.calledOnce).to.be.true + }) + + it('displays helpful error message for missing fields', () => { + const flags = { + 'config-file': configFilePath, + include: [], + exclude: [], + } + + try { + Functions.filterFunctions(cmdStub, flags) + } catch { + expect(cmdStub.error.calledOnce).to.be.true + const errorCall = cmdStub.error.getCall(0) + expect(errorCall.args[0]).to.include('Invalid mimic.yaml configuration') + } + }) + }) + + context('when YAML is malformed', () => { + beforeEach(() => { + fs.writeFileSync( + configFilePath, + `tasks: + - name: task1 + invalid yaml: [` + ) + }) + + it('throws error when parsing YAML', () => { + const flags = { + 'config-file': configFilePath, + include: [], + exclude: [], + } + + expect(() => Functions.filterFunctions(cmdStub, flags)).to.throw() + }) + }) + }) + }) + }) +}) From b3157ad922a292d77a67a5845ae1aa4671b4ba67 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Fri, 6 Feb 2026 15:52:56 -0300 Subject: [PATCH 05/17] Add --no-config --- packages/cli/src/commands/functions.ts | 12 +++++++ packages/cli/tests/commands/functions.spec.ts | 36 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index 91d99d95..22c6666d 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -51,6 +51,10 @@ export default class Functions extends Command { description: `Path to the ${Functions.MIMIC_CONFIG_FILE} file, this overrides other parameters like build-directory and function`, default: Functions.MIMIC_CONFIG_FILE, }), + 'no-config': Flags.boolean({ + description: `Do not read ${Functions.MIMIC_CONFIG_FILE}; use defaults and explicit flags instead`, + default: false, + }), include: Flags.string({ description: `When ${Functions.MIMIC_CONFIG_FILE} exists, only run tasks with these names (space-separated)`, multiple: true, @@ -79,7 +83,15 @@ export default class Functions extends Command { } public static filterFunctions(cmd: Command, flags: FunctionsFlags & Partial): FunctionConfig[] { + if (flags['no-config']) { + return [{ ...DefaultFunctionConfig, ...flags }] + } + if (!fs.existsSync(flags['config-file'])) { + if (flags['config-file'] !== Functions.MIMIC_CONFIG_FILE) { + cmd.error(`Could not find ${flags['config-file']}`, { code: 'ConfigNotFound' }) + } + // If doesn't exists return the default with the flags the user added return [{ ...DefaultFunctionConfig, ...flags }] } diff --git a/packages/cli/tests/commands/functions.spec.ts b/packages/cli/tests/commands/functions.spec.ts index cbe52cc6..886f2d2e 100644 --- a/packages/cli/tests/commands/functions.spec.ts +++ b/packages/cli/tests/commands/functions.spec.ts @@ -5,7 +5,7 @@ import * as sinon from 'sinon' import Functions, { FunctionConfigSchema, MimicConfigSchema } from '../../src/commands/functions' -describe('Functions', () => { +describe.only('Functions', () => { const basePath = `${__dirname}/../fixtures` const configFilePath = `${basePath}/mimic.yaml` const validConfig = { @@ -181,6 +181,20 @@ describe('Functions', () => { expect(result[0].function).to.equal('src/custom/function.ts') }) }) + + context('when a non-default config path is provided', () => { + it('throws an error', () => { + const customFlags = { + 'config-file': `${basePath}/custom-mimic.yaml`, + include: [], + exclude: [], + } + + cmdStub.error.throws(new Error('ConfigNotFound')) + expect(() => Functions.filterFunctions(cmdStub, customFlags)).to.throw('ConfigNotFound') + expect(cmdStub.error.calledOnce).to.be.true + }) + }) }) context('when config file exists', () => { @@ -209,6 +223,26 @@ tasks: }) context('when config is valid', () => { + context('when --no-config is provided', () => { + it('returns default config without reading the file', () => { + const flags = { + 'config-file': configFilePath, + 'no-config': true, + include: [], + exclude: [], + } + + const result = Functions.filterFunctions(cmdStub, flags) + + expect(result).to.have.lengthOf(1) + expect(result[0].name).to.equal('') + expect(result[0].manifest).to.equal('manifest.yaml') + expect(result[0].function).to.equal('src/function.ts') + expect(result[0]['build-directory']).to.equal('./build') + expect(result[0]['types-directory']).to.equal('./src/types') + }) + }) + it('returns all tasks', () => { const flags = { 'config-file': configFilePath, From a4e4b5d2ecf5e8562949c3d4d484a0e1cb830f2f Mon Sep 17 00:00:00 2001 From: Agustincito Date: Fri, 6 Feb 2026 15:54:23 -0300 Subject: [PATCH 06/17] Remove .only --- packages/cli/tests/commands/functions.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/tests/commands/functions.spec.ts b/packages/cli/tests/commands/functions.spec.ts index 886f2d2e..ab0f31d5 100644 --- a/packages/cli/tests/commands/functions.spec.ts +++ b/packages/cli/tests/commands/functions.spec.ts @@ -5,7 +5,7 @@ import * as sinon from 'sinon' import Functions, { FunctionConfigSchema, MimicConfigSchema } from '../../src/commands/functions' -describe.only('Functions', () => { +describe('Functions', () => { const basePath = `${__dirname}/../fixtures` const configFilePath = `${basePath}/mimic.yaml` const validConfig = { From 0c34cf72b7b832ed651d261ddc340e128cb07b39 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Sun, 8 Feb 2026 22:30:50 -0300 Subject: [PATCH 07/17] Rename task for functions --- packages/cli/src/commands/functions.ts | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index 22c6666d..76950874 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -16,8 +16,10 @@ export const FunctionConfigSchema = z.object({ 'types-directory': z.string().min(1, 'Types directory is required'), }) +export type FunctionConfig = z.infer + export const MimicConfigSchema = z.object({ - tasks: z.array(FunctionConfigSchema).min(1, 'At least one task is required'), + functions: z.array(FunctionConfigSchema).min(1, 'At least one function is required'), }) export const DefaultFunctionConfig = { @@ -28,14 +30,6 @@ export const DefaultFunctionConfig = { 'types-directory': './src/types', } as const -export type FunctionConfig = { - name: string - manifest: string - function: string - 'build-directory': string - 'types-directory': string -} - export default class Functions extends Command { // eslint-disable-next-line @typescript-eslint/no-explicit-any run(): Promise { @@ -100,19 +94,17 @@ export default class Functions extends Command { const rawConfig = yaml.load(fileContents) try { - const config = MimicConfigSchema.parse(rawConfig) - - let tasks = config.tasks || [] + let { functions } = MimicConfigSchema.parse(rawConfig) if (flags.include && flags.include.length > 0) { - tasks = tasks.filter((task) => flags.include!.includes(task.name)) + functions = functions.filter((task) => flags.include!.includes(task.name)) } if (flags.exclude && flags.exclude.length > 0) { - tasks = tasks.filter((task) => !flags.exclude!.includes(task.name)) + functions = functions.filter((task) => !flags.exclude!.includes(task.name)) } - return tasks + return functions } catch (error) { if (error instanceof z.ZodError) { const errors = error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('\n') From f1c3b7d604a902d72252293f25e95deb4536a537 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Sun, 8 Feb 2026 22:35:56 -0300 Subject: [PATCH 08/17] Use function path when name is not defined --- packages/cli/src/commands/functions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index 76950874..eda75dac 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -71,7 +71,7 @@ export default class Functions extends Command { ): Promise { const functions = Functions.filterFunctions(cmd, flags) for (const func of functions) { - log.startAction(`Starting ${cmdActions} for function ${func.name}`) + log.startAction(`Starting ${cmdActions} for function ${func.name ? func.name : func.function}`) await cmdLogic(cmd, { ...flags, ...func } as T) } } From 305f58163a421c0aa9d3e2d7b72fcdeee3d2adfe Mon Sep 17 00:00:00 2001 From: Agustincito Date: Sun, 8 Feb 2026 22:44:46 -0300 Subject: [PATCH 09/17] Fix const and add warnings --- packages/cli/src/commands/functions.ts | 32 +++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index eda75dac..748ddc79 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -30,6 +30,8 @@ export const DefaultFunctionConfig = { 'types-directory': './src/types', } as const +const MIMIC_CONFIG_FILE = 'mimic.yaml' + export default class Functions extends Command { // eslint-disable-next-line @typescript-eslint/no-explicit-any run(): Promise { @@ -38,25 +40,23 @@ export default class Functions extends Command { static override description = 'Filters tasks based on a mimic.yaml configuration file' - static MIMIC_CONFIG_FILE = 'mimic.yaml' - static flags = { 'config-file': Flags.string({ - description: `Path to the ${Functions.MIMIC_CONFIG_FILE} file, this overrides other parameters like build-directory and function`, - default: Functions.MIMIC_CONFIG_FILE, + description: `Path to the ${MIMIC_CONFIG_FILE} file, this overrides other parameters like build-directory and function`, + default: MIMIC_CONFIG_FILE, }), 'no-config': Flags.boolean({ - description: `Do not read ${Functions.MIMIC_CONFIG_FILE}; use defaults and explicit flags instead`, + description: `Do not read ${MIMIC_CONFIG_FILE}; use defaults and explicit flags instead`, default: false, }), include: Flags.string({ - description: `When ${Functions.MIMIC_CONFIG_FILE} exists, only run tasks with these names (space-separated)`, + description: `When ${MIMIC_CONFIG_FILE} exists, only run tasks with these names (space-separated)`, multiple: true, exclusive: ['exclude'], char: 'i', }), exclude: Flags.string({ - description: `When ${Functions.MIMIC_CONFIG_FILE} exists, exclude tasks with these names (space-separated)`, + description: `When ${MIMIC_CONFIG_FILE} exists, exclude tasks with these names (space-separated)`, multiple: true, exclusive: ['include'], char: 'e', @@ -82,7 +82,7 @@ export default class Functions extends Command { } if (!fs.existsSync(flags['config-file'])) { - if (flags['config-file'] !== Functions.MIMIC_CONFIG_FILE) { + if (flags['config-file'] !== MIMIC_CONFIG_FILE) { cmd.error(`Could not find ${flags['config-file']}`, { code: 'ConfigNotFound' }) } @@ -96,19 +96,29 @@ export default class Functions extends Command { try { let { functions } = MimicConfigSchema.parse(rawConfig) + const functionNames = new Set(functions.map((fn) => fn.name)) + if (flags.include && flags.include.length > 0) { - functions = functions.filter((task) => flags.include!.includes(task.name)) + const missingIncludes = flags.include.filter((name) => !functionNames.has(name)) + if (missingIncludes.length > 0) { + cmd.warn(`Functions not found in ${MIMIC_CONFIG_FILE}: ${missingIncludes.join(', ')}`) + } + functions = functions.filter((fn) => flags.include!.includes(fn.name)) } if (flags.exclude && flags.exclude.length > 0) { - functions = functions.filter((task) => !flags.exclude!.includes(task.name)) + const missingExcludes = flags.exclude.filter((name) => !functionNames.has(name)) + if (missingExcludes.length > 0) { + cmd.warn(`Functions not found in ${MIMIC_CONFIG_FILE}: ${missingExcludes.join(', ')}`) + } + functions = functions.filter((fn) => !flags.exclude!.includes(fn.name)) } return functions } catch (error) { if (error instanceof z.ZodError) { const errors = error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('\n') - cmd.error(`Invalid ${Functions.MIMIC_CONFIG_FILE} configuration:\n${errors}`, { code: 'InvalidConfig' }) + cmd.error(`Invalid ${MIMIC_CONFIG_FILE} configuration:\n${errors}`, { code: 'InvalidConfig' }) } throw error } From 1baf333c73cc5408fdc6560bcac317cf7fd6dc6c Mon Sep 17 00:00:00 2001 From: Agustincito Date: Sun, 8 Feb 2026 22:45:04 -0300 Subject: [PATCH 10/17] Fix typo --- packages/cli/src/commands/functions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index 748ddc79..d50478ce 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -86,7 +86,7 @@ export default class Functions extends Command { cmd.error(`Could not find ${flags['config-file']}`, { code: 'ConfigNotFound' }) } - // If doesn't exists return the default with the flags the user added + // If doesn't exist return the default with the flags the user added return [{ ...DefaultFunctionConfig, ...flags }] } From e6d9fb57df5c4e714f126d94e9e5d54d3efdeb70 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Sun, 8 Feb 2026 22:49:18 -0300 Subject: [PATCH 11/17] Improve testing command descriptions for include and exclude --- packages/cli/src/commands/test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index 44a7bcaa..45222871 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -15,7 +15,17 @@ export default class Test extends Command { static override examples = ['<%= config.bin %> <%= command.id %> --directory ./tests'] static override flags = { - ...Functions.flags, + ...{ + ...Functions.flags, + include: { + ...Functions.flags.include, + description: Functions.flags.include.description + '. Only for building, it does not affect testing', + }, + exclude: { + ...Functions.flags.exclude, + description: Functions.flags.exclude.description + '. Only for building, it does not affect testing', + }, + }, ...Build.flags, directory: Flags.string({ char: 'd', description: 'Path to the testing directory', default: './tests' }), 'skip-build': Flags.boolean({ description: 'Skip build before testing', default: false }), From 3f8122d92e73fcbf57038269b376208444487243 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Sun, 8 Feb 2026 22:50:13 -0300 Subject: [PATCH 12/17] Fix functions description --- packages/cli/src/commands/functions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index d50478ce..4532481e 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -38,7 +38,7 @@ export default class Functions extends Command { throw new Error('Method not implemented.') } - static override description = 'Filters tasks based on a mimic.yaml configuration file' + static override description = `Filters functions based on a ${MIMIC_CONFIG_FILE} configuration file` static flags = { 'config-file': Flags.string({ From 280435f55be8215a171989820ac566f7f0bacb32 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Sun, 8 Feb 2026 23:00:45 -0300 Subject: [PATCH 13/17] Fux test --- packages/cli/tests/commands/functions.spec.ts | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/packages/cli/tests/commands/functions.spec.ts b/packages/cli/tests/commands/functions.spec.ts index ab0f31d5..627913c7 100644 --- a/packages/cli/tests/commands/functions.spec.ts +++ b/packages/cli/tests/commands/functions.spec.ts @@ -9,20 +9,20 @@ describe('Functions', () => { const basePath = `${__dirname}/../fixtures` const configFilePath = `${basePath}/mimic.yaml` const validConfig = { - tasks: [ + functions: [ { - name: 'task1', + name: 'function1', manifest: 'manifest.yaml', function: 'src/function.ts', 'build-directory': './build', 'types-directory': './src/types', }, { - name: 'task2', - manifest: 'src/task2/manifest.yaml', - function: 'src/task2/function.ts', - 'build-directory': './build/task2', - 'types-directory': './src/task2/types', + name: 'function2', + manifest: 'src/function2/manifest.yaml', + function: 'src/function2/function.ts', + 'build-directory': './build/function2', + 'types-directory': './src/function2/types', }, ], } @@ -30,7 +30,7 @@ describe('Functions', () => { describe('FunctionConfigSchema', () => { context('when all required fields are present', () => { it('validates successfully', () => { - const config = validConfig.tasks[0] + const config = validConfig.functions[0] expect(() => FunctionConfigSchema.parse(config)).to.not.throw() }) }) @@ -38,32 +38,32 @@ describe('Functions', () => { context('when required fields are missing', () => { context('when name is missing', () => { it('throws error', () => { - const config = { ...validConfig.tasks[0], name: '' } + const config = { ...validConfig.functions[0], name: '' } expect(() => FunctionConfigSchema.parse(config)).to.throw() }) }) context('when manifest is missing', () => { it('throws error', () => { - const config = { ...validConfig.tasks[0], manifest: '' } + const config = { ...validConfig.functions[0], manifest: '' } expect(() => FunctionConfigSchema.parse(config)).to.throw() }) }) context('when function is missing', () => { it('throws error', () => { - const config = { ...validConfig.tasks[0], function: '' } + const config = { ...validConfig.functions[0], function: '' } expect(() => FunctionConfigSchema.parse(config)).to.throw() }) }) context('when build-directory is missing', () => { it('throws error', () => { - const config = { ...validConfig.tasks[0], 'build-directory': '' } + const config = { ...validConfig.functions[0], 'build-directory': '' } expect(() => FunctionConfigSchema.parse(config)).to.throw() }) }) context('when types-directory is missing', () => { it('throws error', () => { - const config = { ...validConfig.tasks[0], 'types-directory': '' } + const config = { ...validConfig.functions[0], 'types-directory': '' } expect(() => FunctionConfigSchema.parse(config)).to.throw() }) }) @@ -71,37 +71,37 @@ describe('Functions', () => { }) describe('MimicConfigSchema', () => { - context('when config has valid tasks array', () => { - it('validates successfully with single task', () => { - const config = { tasks: [validConfig.tasks[0]] } + context('when config has valid functions array', () => { + it('validates successfully with single function', () => { + const config = { functions: [validConfig.functions[0]] } expect(() => MimicConfigSchema.parse(config)).to.not.throw() }) - it('validates successfully with multiple tasks', () => { + it('validates successfully with multiple functions', () => { expect(() => MimicConfigSchema.parse(validConfig)).to.not.throw() }) }) - context('when tasks array is empty', () => { + context('when functions array is empty', () => { it('throws validation error', () => { - const config = { tasks: [] } + const config = { functions: [] } expect(() => MimicConfigSchema.parse(config)).to.throw() }) }) - context('when tasks array is missing', () => { + context('when functions array is missing', () => { it('throws validation error', () => { const config = {} expect(() => MimicConfigSchema.parse(config)).to.throw() }) }) - context('when a task in the array is invalid', () => { - it('throws validation error for invalid task', () => { + context('when a function in the array is invalid', () => { + it('throws validation error for invalid function', () => { const config = { - tasks: [ - validConfig.tasks[0], - { ...validConfig.tasks[1], name: '' }, // Invalid: empty name + functions: [ + validConfig.functions[0], + { ...validConfig.functions[1], name: '' }, // Invalid: empty name ], } expect(() => MimicConfigSchema.parse(config)).to.throw() @@ -203,17 +203,17 @@ describe('Functions', () => { fs.writeFileSync( configFilePath, ` -tasks: - - name: task1 +functions: + - name: function1 manifest: manifest.yaml function: src/function.ts build-directory: ./build types-directory: ./src/types - - name: task2 - manifest: src/task2/manifest.yaml - function: src/task2/function.ts - build-directory: ./build/task2 - types-directory: ./src/task2/types + - name: function2 + manifest: src/function2/manifest.yaml + function: src/function2/function.ts + build-directory: ./build/function2 + types-directory: ./src/function2/types ` ) }) @@ -243,7 +243,7 @@ tasks: }) }) - it('returns all tasks', () => { + it('returns all functions', () => { const flags = { 'config-file': configFilePath, include: [], @@ -253,28 +253,28 @@ tasks: const result = Functions.filterFunctions(cmdStub, flags) expect(result).to.have.lengthOf(2) - expect(result[0].name).to.equal('task1') - expect(result[1].name).to.equal('task2') + expect(result[0].name).to.equal('function1') + expect(result[1].name).to.equal('function2') }) context('when include filter is provided', () => { - it('returns only included tasks', () => { + it('returns only included functions', () => { const flags = { 'config-file': configFilePath, - include: ['task1'], + include: ['function1'], exclude: [], } const result = Functions.filterFunctions(cmdStub, flags) expect(result).to.have.lengthOf(1) - expect(result[0].name).to.equal('task1') + expect(result[0].name).to.equal('function1') }) - it('returns multiple included tasks', () => { + it('returns multiple included functions', () => { const flags = { 'config-file': configFilePath, - include: ['task1', 'task2'], + include: ['function1', 'function2'], exclude: [], } @@ -283,7 +283,7 @@ tasks: expect(result).to.have.lengthOf(2) }) - it('returns empty array when included task does not exist', () => { + it('returns empty array when included function does not exist', () => { const flags = { 'config-file': configFilePath, include: ['nonexistent'], @@ -297,24 +297,24 @@ tasks: }) context('when exclude filter is provided', () => { - it('excludes specified tasks', () => { + it('excludes specified functions', () => { const flags = { 'config-file': configFilePath, include: [], - exclude: ['task1'], + exclude: ['function1'], } const result = Functions.filterFunctions(cmdStub, flags) expect(result).to.have.lengthOf(1) - expect(result[0].name).to.equal('task2') + expect(result[0].name).to.equal('function2') }) - it('excludes multiple tasks', () => { + it('excludes multiple functions', () => { const flags = { 'config-file': configFilePath, include: [], - exclude: ['task1', 'task2'], + exclude: ['function1', 'function2'], } const result = Functions.filterFunctions(cmdStub, flags) @@ -322,7 +322,7 @@ tasks: expect(result).to.have.lengthOf(0) }) - it('returns all tasks when excluding non-existent task', () => { + it('returns all functions when excluding non-existent function', () => { const flags = { 'config-file': configFilePath, include: [], @@ -339,8 +339,8 @@ tasks: beforeEach(() => { fs.writeFileSync( configFilePath, - `tasks: - - name: task1 + `functions: + - name: function1 manifest: manifest.yaml` ) }) @@ -377,8 +377,8 @@ tasks: beforeEach(() => { fs.writeFileSync( configFilePath, - `tasks: - - name: task1 + `functions: + - name: function1 invalid yaml: [` ) }) From 67ea2945b0d96f1d5c504966723b2fa1db8f8ac2 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Sun, 8 Feb 2026 23:00:55 -0300 Subject: [PATCH 14/17] Improve empty file error --- packages/cli/src/commands/functions.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index 4532481e..cc576910 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -93,6 +93,10 @@ export default class Functions extends Command { const fileContents = fs.readFileSync(flags['config-file'], 'utf8') const rawConfig = yaml.load(fileContents) + if (!rawConfig || (typeof rawConfig === 'object' && Object.keys(rawConfig).length === 0)) { + cmd.error(`Invalid ${MIMIC_CONFIG_FILE} configuration: file is empty.`, { code: 'InvalidConfig' }) + } + try { let { functions } = MimicConfigSchema.parse(rawConfig) From 3a8d731e96065062251a95c390cff4f2500d9e50 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Sun, 8 Feb 2026 23:04:48 -0300 Subject: [PATCH 15/17] Improve error message --- packages/cli/src/commands/functions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index cc576910..b657863e 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -94,7 +94,7 @@ export default class Functions extends Command { const rawConfig = yaml.load(fileContents) if (!rawConfig || (typeof rawConfig === 'object' && Object.keys(rawConfig).length === 0)) { - cmd.error(`Invalid ${MIMIC_CONFIG_FILE} configuration: file is empty.`, { code: 'InvalidConfig' }) + cmd.error(`Invalid ${MIMIC_CONFIG_FILE} configuration: file is empty.`) } try { From bcc3ad3bbd53bc106e9a31d5de65af7f2eb842e6 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Mon, 9 Feb 2026 09:24:05 -0300 Subject: [PATCH 16/17] Hide helper commands --- packages/cli/src/commands/authenticate.ts | 11 ++--------- packages/cli/src/commands/functions.ts | 5 ++--- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/commands/authenticate.ts b/packages/cli/src/commands/authenticate.ts index 83f88f2d..c8621450 100644 --- a/packages/cli/src/commands/authenticate.ts +++ b/packages/cli/src/commands/authenticate.ts @@ -8,18 +8,11 @@ import { FlagsType } from '../types' export type AuthenticateFlags = FlagsType export default class Authenticate extends Command { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - run(): Promise { + run(): Promise { throw new Error('Method not implemented.') } - static override description = 'Authenticate with Mimic by storing your API key locally' - - static override examples = [ - '<%= config.bin %> <%= command.id %>', - '<%= config.bin %> <%= command.id %> --profile staging', - '<%= config.bin %> <%= command.id %> --profile production --api-key YOUR_API_KEY', - ] + static override hidden = true static flags = { profile: Flags.string({ diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index b657863e..4b7cab0d 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -33,12 +33,11 @@ export const DefaultFunctionConfig = { const MIMIC_CONFIG_FILE = 'mimic.yaml' export default class Functions extends Command { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - run(): Promise { + run(): Promise { throw new Error('Method not implemented.') } - static override description = `Filters functions based on a ${MIMIC_CONFIG_FILE} configuration file` + static override hidden = true static flags = { 'config-file': Flags.string({ From 0ca6c50deac2eeda0bf58e2dcf682c139f574aed Mon Sep 17 00:00:00 2001 From: Agustincito Date: Mon, 9 Feb 2026 15:20:20 -0300 Subject: [PATCH 17/17] Fix comments --- packages/cli/src/commands/functions.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/commands/functions.ts b/packages/cli/src/commands/functions.ts index 4b7cab0d..85f205ac 100644 --- a/packages/cli/src/commands/functions.ts +++ b/packages/cli/src/commands/functions.ts @@ -70,7 +70,7 @@ export default class Functions extends Command { ): Promise { const functions = Functions.filterFunctions(cmd, flags) for (const func of functions) { - log.startAction(`Starting ${cmdActions} for function ${func.name ? func.name : func.function}`) + log.startAction(`\nStarting ${cmdActions} for function ${func.name ? func.name : func.function}`) await cmdLogic(cmd, { ...flags, ...func } as T) } } @@ -99,21 +99,13 @@ export default class Functions extends Command { try { let { functions } = MimicConfigSchema.parse(rawConfig) - const functionNames = new Set(functions.map((fn) => fn.name)) - if (flags.include && flags.include.length > 0) { - const missingIncludes = flags.include.filter((name) => !functionNames.has(name)) - if (missingIncludes.length > 0) { - cmd.warn(`Functions not found in ${MIMIC_CONFIG_FILE}: ${missingIncludes.join(', ')}`) - } + Functions.checkMissingFunctions(cmd, functions, flags.include) functions = functions.filter((fn) => flags.include!.includes(fn.name)) } if (flags.exclude && flags.exclude.length > 0) { - const missingExcludes = flags.exclude.filter((name) => !functionNames.has(name)) - if (missingExcludes.length > 0) { - cmd.warn(`Functions not found in ${MIMIC_CONFIG_FILE}: ${missingExcludes.join(', ')}`) - } + Functions.checkMissingFunctions(cmd, functions, flags.exclude) functions = functions.filter((fn) => !flags.exclude!.includes(fn.name)) } @@ -126,4 +118,16 @@ export default class Functions extends Command { throw error } } + + private static checkMissingFunctions( + cmd: Command, + functions: FunctionConfig[], + filteredFunctionNames: string[] + ): void { + const functionNames = new Set(functions.map((fn) => fn.name)) + const missingFunctions = filteredFunctionNames.filter((name) => !functionNames.has(name)) + if (missingFunctions.length > 0) { + cmd.warn(`Functions not found in ${MIMIC_CONFIG_FILE}: ${missingFunctions.join(', ')}`) + } + } }