From 02e559dc5a4241e1f23e1f9da67789ad7569842a Mon Sep 17 00:00:00 2001
From: Oskar Dudycz <oskar.dudycz@gmail.com>
Date: Thu, 12 Sep 2024 10:36:27 +0200
Subject: [PATCH] Added command to generate or print sample config file

---
 src/packages/pongo/package.json               |   4 +-
 src/packages/pongo/src/cli.ts                 |   3 +-
 src/packages/pongo/src/commandLine/config.ts  |   0
 .../pongo/src/commandLine/configFile.ts       | 122 ++++++++++++++++--
 src/packages/pongo/src/commandLine/migrate.ts |   5 +-
 5 files changed, 120 insertions(+), 14 deletions(-)
 create mode 100644 src/packages/pongo/src/commandLine/config.ts

diff --git a/src/packages/pongo/package.json b/src/packages/pongo/package.json
index e7b42d3..ff862d2 100644
--- a/src/packages/pongo/package.json
+++ b/src/packages/pongo/package.json
@@ -16,7 +16,9 @@
     "test:int:watch": "glob -c \"node --import tsx --test --watch\" **/*.int.spec.ts",
     "test:e2e:watch": "glob -c \"node --import tsx --test --watch\" **/*.e2e.spec.ts",
     "cli:sql:print": "tsx src/cli.ts migrate sql --collection users",
-    "cli:migrate:dryRun": "tsx src/cli.ts migrate run --config src/e2e/cli-config.ts -cs postgresql://postgres:postgres@localhost:5432/postgres "
+    "cli:migrate:dryRun": "tsx src/cli.ts migrate run --config src/e2e/cli-config.ts -cs postgresql://postgres:postgres@localhost:5432/postgres",
+    "cli:config:print": "tsx src/cli.ts config sample --print",
+    "cli:config:generate": "tsx src/cli.ts config sample --generate --file ./pongoConfig.ts "
   },
   "repository": {
     "type": "git",
diff --git a/src/packages/pongo/src/cli.ts b/src/packages/pongo/src/cli.ts
index 2c15a51..880236b 100644
--- a/src/packages/pongo/src/cli.ts
+++ b/src/packages/pongo/src/cli.ts
@@ -1,11 +1,12 @@
 #!/usr/bin/env node
 import { Command } from 'commander';
-import { migrateCommand } from './commandLine';
+import { configCommand, migrateCommand } from './commandLine';
 
 const program = new Command();
 
 program.name('pongo').description('CLI tool for Pongo');
 
+program.addCommand(configCommand);
 program.addCommand(migrateCommand);
 
 program.parse(process.argv);
diff --git a/src/packages/pongo/src/commandLine/config.ts b/src/packages/pongo/src/commandLine/config.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/packages/pongo/src/commandLine/configFile.ts b/src/packages/pongo/src/commandLine/configFile.ts
index 61569b7..ab7766e 100644
--- a/src/packages/pongo/src/commandLine/configFile.ts
+++ b/src/packages/pongo/src/commandLine/configFile.ts
@@ -1,26 +1,58 @@
+import { Command } from 'commander';
+import fs from 'node:fs';
 import { objectEntries, type PongoSchemaConfig } from '../core';
 import {
   toDbSchemaMetadata,
   type PongoDbSchemaMetadata,
 } from '../core/typing/schema';
 
-const sampleConfig = `import { pongoSchema } from '@event-driven-io/pongo';
+const formatTypeName = (input: string): string => {
+  if (input.length === 0) {
+    return input;
+  }
+
+  let formatted = input.charAt(0).toUpperCase() + input.slice(1);
+
+  if (formatted.endsWith('s')) {
+    formatted = formatted.slice(0, -1);
+  }
+
+  return formatted;
+};
 
-type User = { name: string };
+const sampleConfig = (collectionNames: string[] = ['users']) => {
+  const types = collectionNames
+    .map(
+      (name) =>
+        `type ${formatTypeName(name)} = { name: string, description: string, date: Date }`,
+    )
+    .join('\n');
+
+  const collections = collectionNames
+    .map(
+      (name) =>
+        `      ${name}: pongoSchema.collection<${formatTypeName(name)}>('${name}'),`,
+    )
+    .join('\n');
+
+  return `import { pongoSchema } from '@event-driven-io/pongo';
+
+${types}
 
 export default {
   schema: pongoSchema.client({
     database: pongoSchema.db({
-      users: pongoSchema.collection<User>('users'),
+${collections}
     }),
   }),
 };`;
+};
 
-const missingDefaultExport = `Error: Config should contain default export, e.g.\n\n${sampleConfig}`;
-const missingSchema = `Error: Config should contain schema property, e.g.\n\n${sampleConfig}`;
-const missingDbs = `Error: Config should have at least a single database defined, e.g.\n\n${sampleConfig}`;
-const missingDefaultDb = `Error: Config should have a default database defined (without name or or with default database name), e.g.\n\n${sampleConfig}`;
-const missingCollections = `Error: Database should have defined at least one collection, e.g.\n\n${sampleConfig}`;
+const missingDefaultExport = `Error: Config should contain default export, e.g.\n\n${sampleConfig()}`;
+const missingSchema = `Error: Config should contain schema property, e.g.\n\n${sampleConfig()}`;
+const missingDbs = `Error: Config should have at least a single database defined, e.g.\n\n${sampleConfig()}`;
+const missingDefaultDb = `Error: Config should have a default database defined (without name or or with default database name), e.g.\n\n${sampleConfig()}`;
+const missingCollections = `Error: Database should have defined at least one collection, e.g.\n\n${sampleConfig()}`;
 
 export const loadConfigFile = async (
   configPath: string,
@@ -46,6 +78,20 @@ export const loadConfigFile = async (
   }
 };
 
+export const generateConfigFile = (
+  configPath: string,
+  collectionNames: string[],
+): void => {
+  try {
+    fs.writeFileSync(configPath, sampleConfig(collectionNames), 'utf8');
+    console.log(`Configuration file stored at: ${configPath}`);
+  } catch (error) {
+    console.error(`Error: Couldn't store config file: ${configPath}!`);
+    console.error(error);
+    process.exit(1);
+  }
+};
+
 export const parseDefaultDbSchema = (
   imported: Partial<{ default: PongoSchemaConfig }>,
 ): PongoDbSchemaMetadata | string => {
@@ -81,3 +127,63 @@ export const parseDefaultDbSchema = (
 
   return toDbSchemaMetadata(defaultDb);
 };
+
+type SampleConfigOptions =
+  | {
+      collection: string[];
+      print?: boolean;
+    }
+  | {
+      collection: string[];
+      generate?: boolean;
+      file?: string;
+    };
+
+export const configCommand = new Command('config').description(
+  'Manage Pongo configuration',
+);
+
+const sampleConfigCommand = configCommand
+  .command('sample')
+  .description('Generate or print sample configuration')
+  .option(
+    '-col, --collection <name>',
+    'Specify the collection name',
+    (value: string, previous: string[]) => {
+      // Accumulate collection names into an array (explicitly typing `previous` as `string[]`)
+      return previous.concat([value]);
+    },
+    [] as string[],
+  )
+  .option(
+    '-f, --file <path>',
+    'Path to configuration file with collection list',
+  )
+  .option('-g, --generate', 'Generate sample config file')
+  .option('-p, --print', 'Print sample config file')
+  .action((options: SampleConfigOptions) => {
+    const collectionNames =
+      options.collection.length > 0 ? options.collection : ['users'];
+
+    if (!('print' in options) && !('generate' in options)) {
+      console.error(
+        'Error: Please provide either:\n--print param to print sample config or\n--generate to generate sample config file',
+      );
+      process.exit(1);
+    }
+
+    if ('print' in options) {
+      console.log(`${sampleConfig(collectionNames)}`);
+    } else if ('generate' in options) {
+      if (!options.file) {
+        console.error(
+          'Error: You need to provide a config file through a --file',
+        );
+        process.exit(1);
+      }
+
+      generateConfigFile(options.file, collectionNames);
+    }
+  });
+
+sampleConfigCommand.command('generate');
diff --git a/src/packages/pongo/src/commandLine/migrate.ts b/src/packages/pongo/src/commandLine/migrate.ts
index cd3d09d..3d8a9b1 100644
--- a/src/packages/pongo/src/commandLine/migrate.ts
+++ b/src/packages/pongo/src/commandLine/migrate.ts
@@ -41,10 +41,7 @@ migrateCommand
     },
     [] as string[],
   )
-  .option(
-    '-f, --config <path>',
-    'Path to configuration file with collection list',
-  )
+  .option('-f, --config <path>', 'Path to configuration file with Pongo config')
   .option('-dr, --dryRun', 'Perform dry run without commiting changes', false)
   .action(async (options: MigrateRunOptions) => {
     const { collection, dryRun } = options;