diff --git a/.gitignore b/.gitignore
index fd4f2b0..10d9d84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
node_modules
.DS_Store
+.idea/
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/backend-template.iml b/.idea/backend-template.iml
deleted file mode 100644
index 4a10506..0000000
--- a/.idea/backend-template.iml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index e23f359..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts
index 9641930..82dcbc0 100644
--- a/packages/cli/src/cli.ts
+++ b/packages/cli/src/cli.ts
@@ -1,297 +1,9 @@
-import yargs, {Argv} from 'yargs';
-import * as clack from '@clack/prompts';
-import * as tty from 'tty';
-import { CommandHandler } from './common';
+import { Cli, CliOptions } from './packages/cli-wrapper';
-export interface CommandArgs {
- aliases?: string[];
- command: string;
- parameters: string[];
- describe: string;
- handler: CommandHandler;
- interactiveMode?: boolean;
-}
-
-type PromptValueArgs = [Record, ...never[]]
- | [string, string]
- | [[string, string], ...never[]]
- | [[string, string][], ...never[]];
-
-class DummyWriteStream extends tty.WriteStream {
- private bufferInternal = Buffer.from('');
-
- constructor() {
- super(0);
- }
-
- write(input: Uint8Array | string, _encoding?: BufferEncoding | ((err?: Error) => void), _cb?: (err?: Error) => void): boolean {
- this.bufferInternal = Buffer.concat([this.bufferInternal, Buffer.from(input)]);
- // noop
- return true;
- }
-
- get buffer(): Buffer {
- return this.bufferInternal;
- }
-}
-
-export interface TestModeResult {
- exitCode?: number;
- stdout: DummyWriteStream;
- stderr: DummyWriteStream;
-}
-
-interface InteractiveModeOptions {
- option: string;
- alias: string;
- intro: string;
- cancelled: string;
- outro: string;
- describe: string;
-}
-
-export interface CliOptions {
- name: string;
- interactiveMode?: Partial;
-}
-
-export class Cli {
- private testMode = false;
-
- private promptValues = {} as Record;
-
- private testModeResult: TestModeResult = {
- exitCode: undefined as number | undefined,
- stdout: new DummyWriteStream(),
- stderr: new DummyWriteStream(),
- };
-
- private readonly cli: Argv;
-
- constructor(private readonly options: CliOptions) {
- this.cli = yargs()
- .scriptName(options.name)
- .help()
- .fail(false)
- .strict(false);
- }
-
- private exit(code: number) {
- if (this.testMode) {
- this.testModeResult.exitCode = code;
- return;
- }
- process.exit(code);
- }
-
- private async prompt(messages: Record, interactiveModeOptions: InteractiveModeOptions) {
- const {
- intro,
- cancelled,
- outro,
- } = interactiveModeOptions;
- const messagesEntries = Object.entries(messages);
-
- if (messagesEntries.length > 0) {
- clack.intro(intro);
- const values = await clack.group(
- Object.fromEntries(
- messagesEntries.map(([key, value]) => [key, () => clack.text({ message: value })])
- ),
- {
- onCancel: () => {
- clack.cancel(cancelled);
- this.exit(1);
- }
- }
- );
- clack.outro(outro);
- return values;
- }
-
- return {};
- }
-
- private async generateCommandArgs(
- context: any,
- originalCommandArgs: Record,
- interactive: boolean,
- interactiveModeOptions: InteractiveModeOptions,
- ) {
- if (!interactive) {
- return originalCommandArgs;
- }
-
- // TODO properly filter attributes
- const clackGroup = context.optional
- .filter((optional: { cmd: string[] }) => {
- const {
- '$0': _$0,
- i: _i,
- interactive: _interactive,
- test: _test,
- _: __,
- ...rest
- } = originalCommandArgs;
-
- return !Object.keys(rest).includes(optional.cmd[0]);
- })
- .reduce(
- (group: Record, optional: { cmd: string[], describe: string }) => ({
- ...group,
- [optional.cmd[0]]: optional.describe ?? optional.cmd[0],
- }),
- {} as Record,
- );
-
-
- const promptedArgs = this.testMode
- ? this.promptValues
- : await this.prompt(clackGroup, interactiveModeOptions);
-
- return {
- ...originalCommandArgs,
- ...promptedArgs,
- };
- }
-
- private generateLogger() {
- return {
- log: (message: unknown) => {
- const stdout = this.testMode ? this.testModeResult.stdout : process.stdout;
- stdout.write(`${message?.toString()}\n`);
- },
- warn: (message: unknown) => {
- const stdout = this.testMode ? this.testModeResult.stdout : process.stdout;
- stdout.write(`WARN: ${message?.toString()}\n`);
- },
- debug: (message: unknown) => {
- const stdout = this.testMode ? this.testModeResult.stdout : process.stdout;
- stdout.write(`DEBUG: ${message?.toString()}\n`);
- },
- error: (message: unknown) => {
- const stderr = this.testMode ? this.testModeResult.stderr : process.stderr;
- stderr.write(`${message?.toString()}\n`);
- },
- };
- }
-
- private buildHandler(handlerFn: Function, interactiveModeOptions: InteractiveModeOptions) {
- const thisCli = this;
- return async function handler(this: any, commandArgs: Record) {
- const self = this;
- const { option, alias } = interactiveModeOptions;
- const interactiveLong = option in commandArgs ? Boolean(commandArgs.interactive) : false;
- const interactiveShort = alias in commandArgs ? Boolean(commandArgs.i) : false;
- const interactive = interactiveLong || interactiveShort;
- const buildArgs = await thisCli.generateCommandArgs(
- self,
- commandArgs,
- interactive,
- interactiveModeOptions,
- );
-
- let exited = false;
- const returnCode = await handlerFn({
- self,
- interactive,
- logger: thisCli.generateLogger(),
- send: (code: number) => {
- exited = true;
- thisCli.exit(code);
- },
- args: buildArgs,
- });
-
- if (!exited) {
- thisCli.exit(returnCode ? returnCode : 0);
- }
- };
- }
-
- command({ parameters, command, interactiveMode = true, handler, ...args }: CommandArgs): Cli {
- const {
- option = 'interactive',
- alias = 'i',
- describe = 'Interactive mode',
- intro = 'Please provide the following values:',
- cancelled = 'Operation cancelled.',
- outro = 'Thank you!',
- } = this.options?.interactiveMode ?? {};
-
- const commandArgs = {
- ...args,
- command: `${command} ${parameters.join(' ').replace(//g, ']')}`,
- usage: parameters.map((p) => `${command} ${p}`).join('\n'),
- handler: this.buildHandler(handler, {
- option,
- alias,
- intro,
- cancelled,
- outro,
- describe,
- }),
- } as Record
-
- if (interactiveMode) {
- commandArgs.options = {
- ...(commandArgs.options ?? {}),
- [option]: {
- alias,
- describe,
- type: 'boolean',
- default: false,
- hidden: true,
- },
- };
- }
-
- this.cli.command(commandArgs as unknown as Parameters[0]);
- return this;
- }
-
- async run(args: string[]): Promise {
- if (args.length > 0) {
- await this.cli.parse(args);
- return;
- }
- this.cli.showHelp();
- }
-
- test() {
- this.testMode = true;
- this.promptValues = {};
- const thisCli = this;
- return {
- promptValue(...args: PromptValueArgs) {
- const [testArg] = args;
-
- if (typeof testArg === 'string') {
- const [key, value] = args as [string, unknown];
- thisCli.promptValues[key] = thisCli.promptValues[key] ?? value;
- }
-
- if (Array.isArray(testArg)) {
- const [key, value] = testArg as [string, unknown];
- thisCli.promptValues[key] = thisCli.promptValues[key] ?? value;
- }
-
- if (typeof testArg === 'object' && testArg !== null) {
- Object.entries(testArg).forEach(([key, value]) => {
- thisCli.promptValues[key] = thisCli.promptValues[key] ?? value;
- });
- }
+export const createCli = (options: CliOptions) => {
+ const cli = new Cli(options);
- return this;
- },
- async run(args: string[]): Promise {
- await thisCli.cli.parse(args);
- return thisCli.testModeResult;
- },
- };
- }
-}
+ // TODO setup CLI here
-export const createCli = (options: CliOptions) => {
- return new Cli(options);
+ return cli;
};
diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts
index 592ff8c..549cd6a 100644
--- a/packages/cli/src/commands.ts
+++ b/packages/cli/src/commands.ts
@@ -1,10 +1,10 @@
-import { Cli } from './cli';
+import { Cli } from './packages/cli-wrapper';
import { AdderController, AdderControllerImpl } from './modules/adder';
export const addCommands = (cli: Cli) => {
const adderController: AdderController = new AdderControllerImpl();
- cli
+ return cli
.command({
aliases: ['a'],
command: 'add',
@@ -19,6 +19,4 @@ export const addCommands = (cli: Cli) => {
describe: 'Subtract two numbers',
handler: adderController.subtractNumbers,
});
-
- return cli;
};
diff --git a/packages/cli/src/common.ts b/packages/cli/src/common.ts
deleted file mode 100644
index a08e0b4..0000000
--- a/packages/cli/src/common.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-export interface Logger {
- log: (message: unknown) => void;
- error: (message: unknown) => void;
- warn: (message: unknown) => void;
- debug: (message: unknown) => void;
-}
-
-export interface CommandHandlerArgs {
- self: any;
- interactive: boolean;
- send: (message: number) => void;
- logger: Logger;
- args: any;
-}
-
-export type CommandHandler = (args: CommandHandlerArgs) => void | number | Promise | Promise;
diff --git a/packages/cli/src/modules/adder/adder.controller.ts b/packages/cli/src/modules/adder/adder.controller.ts
index 99e8e70..1431117 100644
--- a/packages/cli/src/modules/adder/adder.controller.ts
+++ b/packages/cli/src/modules/adder/adder.controller.ts
@@ -4,7 +4,7 @@ import {
ArgumentOutOfRangeError,
InvalidArgumentTypeError,
} from './adder.service';
-import {CommandHandler} from '@/common';
+import { CommandHandler } from '../../packages/cli-wrapper';
export interface AdderController {
addNumbers: CommandHandler;
diff --git a/packages/cli/src/packages/cli-wrapper.ts b/packages/cli/src/packages/cli-wrapper.ts
new file mode 100644
index 0000000..cbadfd5
--- /dev/null
+++ b/packages/cli/src/packages/cli-wrapper.ts
@@ -0,0 +1,309 @@
+import tty from 'tty';
+import yargs, {Argv} from 'yargs';
+import * as clack from '@clack/prompts';
+
+export interface Logger {
+ log: (message: unknown) => void;
+ error: (message: unknown) => void;
+ warn: (message: unknown) => void;
+ debug: (message: unknown) => void;
+}
+
+export interface CommandHandlerArgs {
+ self: any;
+ interactive: boolean;
+ send: (message: number) => void;
+ logger: Logger;
+ args: any;
+}
+
+export type CommandHandler = (args: CommandHandlerArgs) => void | number | Promise | Promise;
+
+export interface CommandArgs {
+ aliases?: string[];
+ command: string;
+ parameters: string[];
+ describe: string;
+ handler: CommandHandler;
+ interactiveMode?: boolean;
+}
+
+type PromptValueArgs = [Record, ...never[]]
+ | [string, string]
+ | [[string, string], ...never[]]
+ | [[string, string][], ...never[]];
+
+class DummyWriteStream extends tty.WriteStream {
+ private bufferInternal = Buffer.from('');
+
+ constructor() {
+ super(0);
+ }
+
+ write(input: Uint8Array | string, _encoding?: BufferEncoding | ((err?: Error) => void), _cb?: (err?: Error) => void): boolean {
+ this.bufferInternal = Buffer.concat([this.bufferInternal, Buffer.from(input)]);
+ // noop
+ return true;
+ }
+
+ get buffer(): Buffer {
+ return this.bufferInternal;
+ }
+}
+
+export interface TestModeResult {
+ exitCode?: number;
+ stdout: DummyWriteStream;
+ stderr: DummyWriteStream;
+}
+
+interface InteractiveModeOptions {
+ option: string;
+ alias: string;
+ intro: string;
+ cancelled: string;
+ outro: string;
+ describe: string;
+}
+
+export interface CliOptions {
+ name: string;
+ interactiveMode?: Partial;
+}
+
+export class Cli {
+ private testMode = false;
+
+ private promptValues = {} as Record;
+
+ private testModeResult: TestModeResult = {
+ exitCode: undefined as number | undefined,
+ stdout: new DummyWriteStream(),
+ stderr: new DummyWriteStream(),
+ };
+
+ private readonly cli: Argv;
+
+ constructor(private readonly options: CliOptions) {
+ this.cli = yargs()
+ .scriptName(options.name)
+ .help()
+ .fail(false)
+ .strict(false);
+ }
+
+ private exit(code: number) {
+ if (this.testMode) {
+ this.testModeResult.exitCode = code;
+ return;
+ }
+ process.exit(code);
+ }
+
+ private async prompt(messages: Record, interactiveModeOptions: InteractiveModeOptions) {
+ const {
+ intro,
+ cancelled,
+ outro,
+ } = interactiveModeOptions;
+ const messagesEntries = Object.entries(messages);
+
+ if (messagesEntries.length > 0) {
+ clack.intro(intro);
+ const values = await clack.group(
+ Object.fromEntries(
+ messagesEntries.map(([key, value]) => [key, () => clack.text({ message: value })])
+ ),
+ {
+ onCancel: () => {
+ clack.cancel(cancelled);
+ this.exit(1);
+ }
+ }
+ );
+ clack.outro(outro);
+ return values;
+ }
+
+ return {};
+ }
+
+ private async generateCommandArgs(
+ context: any,
+ originalCommandArgs: Record,
+ interactive: boolean,
+ interactiveModeOptions: InteractiveModeOptions,
+ ) {
+ if (!interactive) {
+ return originalCommandArgs;
+ }
+
+ // TODO properly filter attributes
+ const clackGroup = context.optional
+ .filter((optional: { cmd: string[] }) => {
+ const {
+ '$0': _$0,
+ i: _i,
+ interactive: _interactive,
+ test: _test,
+ _: __,
+ ...rest
+ } = originalCommandArgs;
+
+ return !Object.keys(rest).includes(optional.cmd[0]);
+ })
+ .reduce(
+ (group: Record, optional: { cmd: string[], describe: string }) => ({
+ ...group,
+ [optional.cmd[0]]: optional.describe ?? optional.cmd[0],
+ }),
+ {} as Record,
+ );
+
+
+ const promptedArgs = this.testMode
+ ? this.promptValues
+ : await this.prompt(clackGroup, interactiveModeOptions);
+
+ return {
+ ...originalCommandArgs,
+ ...promptedArgs,
+ };
+ }
+
+ private generateLogger() {
+ return {
+ log: (message: unknown) => {
+ const stdout = this.testMode ? this.testModeResult.stdout : process.stdout;
+ stdout.write(`${message?.toString()}\n`);
+ },
+ warn: (message: unknown) => {
+ const stdout = this.testMode ? this.testModeResult.stdout : process.stdout;
+ stdout.write(`WARN: ${message?.toString()}\n`);
+ },
+ debug: (message: unknown) => {
+ const stdout = this.testMode ? this.testModeResult.stdout : process.stdout;
+ stdout.write(`DEBUG: ${message?.toString()}\n`);
+ },
+ error: (message: unknown) => {
+ const stderr = this.testMode ? this.testModeResult.stderr : process.stderr;
+ stderr.write(`${message?.toString()}\n`);
+ },
+ };
+ }
+
+ private buildHandler(handlerFn: Function, interactiveModeOptions: InteractiveModeOptions) {
+ const thisCli = this;
+ return async function handler(this: any, commandArgs: Record) {
+ const self = this;
+ const { option, alias } = interactiveModeOptions;
+ const interactiveLong = option in commandArgs ? Boolean(commandArgs.interactive) : false;
+ const interactiveShort = alias in commandArgs ? Boolean(commandArgs.i) : false;
+ const interactive = interactiveLong || interactiveShort;
+ const buildArgs = await thisCli.generateCommandArgs(
+ self,
+ commandArgs,
+ interactive,
+ interactiveModeOptions,
+ );
+
+ let exited = false;
+ const returnCode = await handlerFn({
+ self,
+ interactive,
+ logger: thisCli.generateLogger(),
+ send: (code: number) => {
+ exited = true;
+ thisCli.exit(code);
+ },
+ args: buildArgs,
+ });
+
+ if (!exited) {
+ thisCli.exit(returnCode ? returnCode : 0);
+ }
+ };
+ }
+
+ command({ parameters, command, interactiveMode = true, handler, ...args }: CommandArgs): Cli {
+ const {
+ option = 'interactive',
+ alias = 'i',
+ describe = 'Interactive mode',
+ intro = 'Please provide the following values:',
+ cancelled = 'Operation cancelled.',
+ outro = 'Thank you!',
+ } = this.options?.interactiveMode ?? {};
+
+ const commandArgs = {
+ ...args,
+ command: `${command} ${parameters.join(' ').replace(//g, ']')}`,
+ usage: parameters.map((p) => `${command} ${p}`).join('\n'),
+ handler: this.buildHandler(handler, {
+ option,
+ alias,
+ intro,
+ cancelled,
+ outro,
+ describe,
+ }),
+ } as Record
+
+ if (interactiveMode) {
+ commandArgs.options = {
+ ...(commandArgs.options ?? {}),
+ [option]: {
+ alias,
+ describe,
+ type: 'boolean',
+ default: false,
+ hidden: true,
+ },
+ };
+ }
+
+ this.cli.command(commandArgs as unknown as Parameters[0]);
+ return this;
+ }
+
+ async run(args: string[]): Promise {
+ if (args.length > 0) {
+ await this.cli.parse(args);
+ return;
+ }
+ this.cli.showHelp();
+ }
+
+ test() {
+ this.testMode = true;
+ this.promptValues = {};
+ const thisCli = this;
+ return {
+ promptValue(...args: PromptValueArgs) {
+ const [testArg] = args;
+
+ if (typeof testArg === 'string') {
+ const [key, value] = args as [string, unknown];
+ thisCli.promptValues[key] = thisCli.promptValues[key] ?? value;
+ }
+
+ if (Array.isArray(testArg)) {
+ const [key, value] = testArg as [string, unknown];
+ thisCli.promptValues[key] = thisCli.promptValues[key] ?? value;
+ }
+
+ if (typeof testArg === 'object' && testArg !== null) {
+ Object.entries(testArg).forEach(([key, value]) => {
+ thisCli.promptValues[key] = thisCli.promptValues[key] ?? value;
+ });
+ }
+
+ return this;
+ },
+ async run(args: string[]): Promise {
+ await thisCli.cli.parse(args);
+ return thisCli.testModeResult;
+ },
+ };
+ }
+}
diff --git a/packages/web-api/src/routes.ts b/packages/web-api/src/routes.ts
index 8140ec9..58b7a1e 100644
--- a/packages/web-api/src/routes.ts
+++ b/packages/web-api/src/routes.ts
@@ -4,11 +4,10 @@ import { AdderController, AdderControllerImpl } from '@/modules/adder';
export const addRoutes = (server: FastifyInstance) => {
const adderController: AdderController = new AdderControllerImpl();
- server.route({
- method: 'POST',
- url: '/',
- handler: adderController.addNumbers,
- });
-
- return server;
+ return server
+ .route({
+ method: 'POST',
+ url: '/',
+ handler: adderController.addNumbers,
+ });
};