UNPKG

@whook/whook

Version:

Build strong and efficient REST web services.

2,260 lines (2,183 loc) 49.9 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, test, beforeEach, jest, expect } from '@jest/globals'; import _inquirer from 'inquirer'; import initCreateCommand from './create.js'; import { definition as getPingDefinition } from '../routes/getPing.js'; import { type OpenAPI } from 'ya-open-api-types'; import { type LogService } from 'common-services'; describe('createCommand', () => { const PROJECT_DIR = '/hom/whoiam/project'; const API: OpenAPI = { openapi: '3.1.0', info: { version: '1.0.0', title: 'Sample OpenAPI', description: 'A sample OpenAPI file for testing purpose.', }, paths: { [getPingDefinition.path]: { [getPingDefinition.method]: getPingDefinition.operation, }, }, tags: [{ name: 'system' }], }; const ensureDir = jest.fn<(path: string) => Promise<void>>(); const writeFile = jest.fn<(path: string, data: string) => Promise<void>>(); const pathExists = jest.fn<(path: string) => Promise<boolean>>(); const inquirer = { prompt: jest.fn<any>() }; const log = jest.fn<LogService>(); beforeEach(() => { ensureDir.mockReset(); writeFile.mockReset(); pathExists.mockReset(); inquirer.prompt.mockReset(); log.mockReset(); }); describe('for routes', () => { test('should work with get and no dependencies', async () => { inquirer.prompt.mockResolvedValueOnce({ services: [], }); inquirer.prompt.mockResolvedValueOnce({ method: 'get', path: '/lol', description: 'yolo', tags: [], }); pathExists.mockResolvedValueOnce(false); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'getHandler', type: 'route', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/routes", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], [ [ { "choices": [ { "name": "get", "value": "get", }, { "name": "put", "value": "put", }, { "name": "post", "value": "post", }, { "name": "delete", "value": "delete", }, { "name": "options", "value": "options", }, { "name": "head", "value": "head", }, { "name": "patch", "value": "patch", }, { "name": "trace", "value": "trace", }, ], "default": "get", "message": "Give the handler method", "name": "method", "type": "list", }, { "message": "Give the handler path", "name": "path", "type": "input", }, { "message": "Give the handler description", "name": "description", "type": "input", }, { "choices": [ { "name": "system", "value": "system", }, ], "message": "Assign one or more tags to the handler", "name": "tags", "type": "checkbox", }, ], ], ], "logCalls": [], "pathExistsCalls": [ [ "/hom/whoiam/project/src/routes/getHandler.ts", ], ], "result": undefined, "writeFileCalls": [ [ "/hom/whoiam/project/src/routes/getHandler.ts", "import { autoService, location } from 'knifecycle'; import { type WhookRouteDefinition, type WhookRouteTypedHandler, } from '@whook/whook'; export const definition = { path: '/lol', method: 'get', operation: { operationId: 'getHandler', summary: 'yolo', tags: [], parameters: [ { name: 'param', in: 'query', required: false, schema: { type: 'number' }, }, ], responses: { 200: { description: 'Success', content: { 'application/json': { schema: { type: 'object', }, }, }, }, }, }, } as const satisfies WhookRouteDefinition; export type HandlerDependencies = {}; async function initGetHandler(_: HandlerDependencies) { const handler: WhookRouteTypedHandler< operations[typeof definition.operation.operationId], typeof definition > = async ({ query: { param }, }) => { return { status: 200, headers: {}, body: { param }, }; }; return handler; } export default location( autoService(initGetHandler), import.meta.url, ); ", ], ], } `); }); test('should work with an existing get and dependencies but no erase allowed', async () => { inquirer.prompt.mockResolvedValueOnce({ services: ['PROJECT_DIR', 'log', 'ENV'], }); inquirer.prompt.mockResolvedValueOnce({ method: 'get', path: '/lol', description: 'yolo', tags: [], }); pathExists.mockResolvedValueOnce(true); inquirer.prompt.mockResolvedValueOnce({ erase: false, }); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'getHandler', type: 'route', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/routes", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], [ [ { "choices": [ { "name": "get", "value": "get", }, { "name": "put", "value": "put", }, { "name": "post", "value": "post", }, { "name": "delete", "value": "delete", }, { "name": "options", "value": "options", }, { "name": "head", "value": "head", }, { "name": "patch", "value": "patch", }, { "name": "trace", "value": "trace", }, ], "default": "get", "message": "Give the handler method", "name": "method", "type": "list", }, { "message": "Give the handler path", "name": "path", "type": "input", }, { "message": "Give the handler description", "name": "description", "type": "input", }, { "choices": [ { "name": "system", "value": "system", }, ], "message": "Assign one or more tags to the handler", "name": "tags", "type": "checkbox", }, ], ], [ [ { "message": "Erase ?", "name": "erase", "type": "confirm", }, ], ], ], "logCalls": [ [ "warning", "⚠️ - The file already exists !", ], ], "pathExistsCalls": [ [ "/hom/whoiam/project/src/routes/getHandler.ts", ], ], "result": undefined, "writeFileCalls": [], } `); }); test('should work with an existing get and dependencies and erase allowed', async () => { inquirer.prompt.mockResolvedValueOnce({ services: ['PROJECT_DIR', 'log', 'ENV'], }); inquirer.prompt.mockResolvedValueOnce({ method: 'get', path: '/lol', description: 'yolo', tags: [], }); pathExists.mockResolvedValueOnce(true); inquirer.prompt.mockResolvedValueOnce({ erase: true, }); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'getHandler', type: 'route', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/routes", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], [ [ { "choices": [ { "name": "get", "value": "get", }, { "name": "put", "value": "put", }, { "name": "post", "value": "post", }, { "name": "delete", "value": "delete", }, { "name": "options", "value": "options", }, { "name": "head", "value": "head", }, { "name": "patch", "value": "patch", }, { "name": "trace", "value": "trace", }, ], "default": "get", "message": "Give the handler method", "name": "method", "type": "list", }, { "message": "Give the handler path", "name": "path", "type": "input", }, { "message": "Give the handler description", "name": "description", "type": "input", }, { "choices": [ { "name": "system", "value": "system", }, ], "message": "Assign one or more tags to the handler", "name": "tags", "type": "checkbox", }, ], ], [ [ { "message": "Erase ?", "name": "erase", "type": "confirm", }, ], ], ], "logCalls": [ [ "warning", "⚠️ - The file already exists !", ], ], "pathExistsCalls": [ [ "/hom/whoiam/project/src/routes/getHandler.ts", ], ], "result": undefined, "writeFileCalls": [ [ "/hom/whoiam/project/src/routes/getHandler.ts", "import { autoService, location } from 'knifecycle'; import { type WhookRouteDefinition, type WhookRouteTypedHandler, } from '@whook/whook'; import { type LogService } from 'common-services'; import { type AppEnvVars, type ProjectDirService } from 'application-services'; export const definition = { path: '/lol', method: 'get', operation: { operationId: 'getHandler', summary: 'yolo', tags: [], parameters: [ { name: 'param', in: 'query', required: false, schema: { type: 'number' }, }, ], responses: { 200: { description: 'Success', content: { 'application/json': { schema: { type: 'object', }, }, }, }, }, }, } as const satisfies WhookRouteDefinition; export type HandlerDependencies = { ENV: AppEnvVars; PROJECT_DIR: ProjectDirService; log: LogService; }; async function initGetHandler({ ENV, PROJECT_DIR, log, }: HandlerDependencies) { const handler: WhookRouteTypedHandler< operations[typeof definition.operation.operationId], typeof definition > = async ({ query: { param }, }) => { return { status: 200, headers: {}, body: { param }, }; }; return handler; } export default location( autoService(initGetHandler), import.meta.url, ); ", ], ], } `); }); }); describe('for services', () => { test('should work with no dependencies', async () => { inquirer.prompt.mockResolvedValueOnce({ services: [], }); pathExists.mockResolvedValueOnce(false); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'aService', type: 'service', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/services", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], ], "logCalls": [], "pathExistsCalls": [ [ "/hom/whoiam/project/src/services/aService.ts", ], ], "result": undefined, "writeFileCalls": [ [ "/hom/whoiam/project/src/services/aService.ts", "import { autoService, location } from 'knifecycle'; export type AServiceService = {}; export type AServiceDependencies = {}; async function initAService(_: AServiceDependencies): Promise<AServiceService> { // Instantiate and return your service return {}; } export default location( autoService(initAService), import.meta.url, ); ", ], ], } `); }); test('should work when existing with dependencies but no erase allowed', async () => { inquirer.prompt.mockResolvedValueOnce({ services: ['PROJECT_DIR', 'log', 'ENV'], }); pathExists.mockResolvedValueOnce(true); inquirer.prompt.mockResolvedValueOnce({ erase: false, }); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'aService', type: 'service', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/services", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], [ [ { "message": "Erase ?", "name": "erase", "type": "confirm", }, ], ], ], "logCalls": [ [ "warning", "⚠️ - The file already exists !", ], ], "pathExistsCalls": [ [ "/hom/whoiam/project/src/services/aService.ts", ], ], "result": undefined, "writeFileCalls": [], } `); }); test('should work when existing with dependencies and erase allowed', async () => { inquirer.prompt.mockResolvedValueOnce({ services: ['PROJECT_DIR', 'log', 'ENV'], }); pathExists.mockResolvedValueOnce(true); inquirer.prompt.mockResolvedValueOnce({ erase: true, }); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'aService', type: 'service', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/services", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], [ [ { "message": "Erase ?", "name": "erase", "type": "confirm", }, ], ], ], "logCalls": [ [ "warning", "⚠️ - The file already exists !", ], ], "pathExistsCalls": [ [ "/hom/whoiam/project/src/services/aService.ts", ], ], "result": undefined, "writeFileCalls": [ [ "/hom/whoiam/project/src/services/aService.ts", "import { autoService, location } from 'knifecycle'; import { type LogService } from 'common-services'; import { type AppEnvVars, type ProjectDirService } from 'application-services'; export type AServiceService = {}; export type AServiceDependencies = { ENV: AppEnvVars; PROJECT_DIR: ProjectDirService; log: LogService; }; async function initAService({ ENV, PROJECT_DIR, log, }: AServiceDependencies): Promise<AServiceService> { // Instantiate and return your service return {}; } export default location( autoService(initAService), import.meta.url, ); ", ], ], } `); }); }); describe('for providers', () => { test('should work with no dependencies', async () => { inquirer.prompt.mockResolvedValueOnce({ services: [], }); pathExists.mockResolvedValueOnce(false); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'aProvider', type: 'provider', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/services", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], ], "logCalls": [], "pathExistsCalls": [ [ "/hom/whoiam/project/src/services/aProvider.ts", ], ], "result": undefined, "writeFileCalls": [ [ "/hom/whoiam/project/src/services/aProvider.ts", "import { autoProvider, location, type Provider } from 'knifecycle'; export type AProviderService = {}; export type AProviderProvider = Provider<AProviderService>; export type AProviderDependencies = {}; async function initAProvider(_: AProviderDependencies): Promise<AProviderProvider> { // Instantiate and return your service return { service: {}, dispose: async () => { // Do any action before the process shutdown // (closing db connections... etc) }, // You can also set a promise for unexpected errors // that shutdown the app when it happens // errorPromise: new Promise(), }; } export default location( autoProvider(initAProvider), import.meta.url, ); ", ], ], } `); }); test('should work when existing with dependencies but no erase allowed', async () => { inquirer.prompt.mockResolvedValueOnce({ services: ['PROJECT_DIR', 'log', 'ENV'], }); pathExists.mockResolvedValueOnce(true); inquirer.prompt.mockResolvedValueOnce({ erase: false, }); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'aProvider', type: 'provider', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/services", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], [ [ { "message": "Erase ?", "name": "erase", "type": "confirm", }, ], ], ], "logCalls": [ [ "warning", "⚠️ - The file already exists !", ], ], "pathExistsCalls": [ [ "/hom/whoiam/project/src/services/aProvider.ts", ], ], "result": undefined, "writeFileCalls": [], } `); }); test('should work when existing with dependencies and erase allowed', async () => { inquirer.prompt.mockResolvedValueOnce({ services: ['PROJECT_DIR', 'log', 'ENV'], }); pathExists.mockResolvedValueOnce(true); inquirer.prompt.mockResolvedValueOnce({ erase: true, }); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'aProvider', type: 'provider', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/services", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], [ [ { "message": "Erase ?", "name": "erase", "type": "confirm", }, ], ], ], "logCalls": [ [ "warning", "⚠️ - The file already exists !", ], ], "pathExistsCalls": [ [ "/hom/whoiam/project/src/services/aProvider.ts", ], ], "result": undefined, "writeFileCalls": [ [ "/hom/whoiam/project/src/services/aProvider.ts", "import { autoProvider, location, type Provider } from 'knifecycle'; import { type LogService } from 'common-services'; import { type AppEnvVars, type ProjectDirService } from 'application-services'; export type AProviderService = {}; export type AProviderProvider = Provider<AProviderService>; export type AProviderDependencies = { ENV: AppEnvVars; PROJECT_DIR: ProjectDirService; log: LogService; }; async function initAProvider({ ENV, PROJECT_DIR, log, }: AProviderDependencies): Promise<AProviderProvider> { // Instantiate and return your service return { service: {}, dispose: async () => { // Do any action before the process shutdown // (closing db connections... etc) }, // You can also set a promise for unexpected errors // that shutdown the app when it happens // errorPromise: new Promise(), }; } export default location( autoProvider(initAProvider), import.meta.url, ); ", ], ], } `); }); }); describe('for commands', () => { test('should work with no dependencies', async () => { inquirer.prompt.mockResolvedValueOnce({ services: [], }); inquirer.prompt.mockResolvedValueOnce({ description: 'yolo', }); pathExists.mockResolvedValueOnce(false); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'aCommand', type: 'command', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/commands", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], [ [ { "message": "Give the command description", "name": "description", "type": "input", }, ], ], ], "logCalls": [], "pathExistsCalls": [ [ "/hom/whoiam/project/src/commands/aCommand.ts", ], ], "result": undefined, "writeFileCalls": [ [ "/hom/whoiam/project/src/commands/aCommand.ts", "import { location, autoService } from 'knifecycle'; import { type WhookCommand, type WhookCommandDefinition, } from '@whook/whook'; export const definition = { name: 'aCommand', description: 'yolo', example: \`whook aCommand --param "value"\`, arguments: [{ name: 'param', required: true, description: 'A parameter', schema: { type: 'string', default: 'A default value', }, }], } as const satisfies WhookCommandDefinition; async function initACommandCommand({ promptArgs, }: { promptArgs: WhookPromptArgs; }): Promise<WhookCommand<{ param: string; }>> { return async ({ param }) => { // Implement your command here } } export default location( autoService(initACommandCommand), import.meta.url, ); ", ], ], } `); }); test('should work when existing with dependencies but no erase allowed', async () => { inquirer.prompt.mockResolvedValueOnce({ services: ['PROJECT_DIR', 'log', 'ENV'], }); inquirer.prompt.mockResolvedValueOnce({ description: 'yolo', }); pathExists.mockResolvedValueOnce(true); inquirer.prompt.mockResolvedValueOnce({ erase: false, }); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'aCommand', type: 'command', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/commands", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], [ [ { "message": "Give the command description", "name": "description", "type": "input", }, ], ], [ [ { "message": "Erase ?", "name": "erase", "type": "confirm", }, ], ], ], "logCalls": [ [ "warning", "⚠️ - The file already exists !", ], ], "pathExistsCalls": [ [ "/hom/whoiam/project/src/commands/aCommand.ts", ], ], "result": undefined, "writeFileCalls": [], } `); }); test('should work when existing with dependencies and erase allowed', async () => { inquirer.prompt.mockResolvedValueOnce({ services: ['PROJECT_DIR', 'log', 'ENV'], }); inquirer.prompt.mockResolvedValueOnce({ description: 'yolo', }); pathExists.mockResolvedValueOnce(true); inquirer.prompt.mockResolvedValueOnce({ erase: true, }); const createCommand = await initCreateCommand({ PROJECT_DIR, API, ensureDir, writeFile, pathExists, inquirer: inquirer as unknown as typeof _inquirer, log, }); const result = await createCommand({ command: 'whook', rest: ['create'], namedArguments: { name: 'aCommand', type: 'command', }, }); expect({ result, ensureDirCalls: ensureDir.mock.calls, writeFileCalls: writeFile.mock.calls, pathExistsCalls: pathExists.mock.calls, inquirerPromptCalls: inquirer.prompt.mock.calls, logCalls: log.mock.calls.filter(([type]) => !type.endsWith('stack')), }).toMatchInlineSnapshot(` { "ensureDirCalls": [ [ "/hom/whoiam/project/src/commands", ], ], "inquirerPromptCalls": [ [ [ { "choices": [ { "value": "codeGenerator", }, { "value": "counter", }, { "value": "delay", }, { "value": "importer", }, { "value": "lock", }, { "value": "log", }, { "value": "random", }, { "value": "resolve", }, { "value": "time", }, { "value": "APP_CONFIG", }, { "value": "ENV", }, { "value": "process", }, { "value": "PROJECT_DIR", }, { "value": "MAIN_FILE_URL", }, { "value": "DEBUG_NODE_ENVS", }, { "value": "BASE_URL", }, { "value": "HOST", }, { "value": "PORT", }, { "value": "DEFINITIONS", }, { "value": "APM", }, ], "message": "Which services do you want to use?", "name": "services", "type": "checkbox", }, ], ], [ [ { "message": "Give the command description", "name": "description", "type": "input", }, ], ], [ [ { "message": "Erase ?", "name": "erase", "type": "confirm", }, ], ], ], "logCalls": [ [ "warning", "⚠️ - The file already exists !", ], ], "pathExistsCalls": [ [ "/hom/whoiam/project/src/commands/aCommand.ts", ], ], "result": undefined, "writeFileCalls": [ [ "/hom/whoiam/project/src/commands/aCommand.ts", "import { location, autoService } from 'knifecycle'; import { type WhookCommand, type WhookCommandDefinition, } from '@whook/whook'; import { type LogService } from 'common-services'; import { type AppEnvVars, type ProjectDirService } from 'application-services'; export const definition = { name: 'aCommand', description: 'yolo', example: \`whook aCommand --param "value"\`, arguments: [{ name: 'param', required: true, description: 'A parameter', schema: { type: 'string', default: 'A default value', }, }], } as const satisfies WhookCommandDefinition; async function initACommandCommand({ ENV, PROJECT_DIR, log, promptArgs, }: { ENV: AppEnvVars; PROJECT_DIR: ProjectDirService; log: LogService; promptArgs: WhookPromptArgs; }): Promise<WhookCommand<{ param: string; }>> { return async ({ param }) => { // Implement your command here } } export default location( autoService(initACommandCommand), import.meta.url, ); ", ], ], } `); }); }); });