diff --git a/README.md b/README.md
index 37f8d00..73afbea 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@ The projects that can be added to a Monux monorepo also provide a lot of functio
- [Adding a new project to the monorepo](#adding-a-new-project-to-the-monorepo)
- [Running development services](#running-development-services)
- [Listing monorepo services](#listing-monorepo-services)
+ - [Stopping development services](#stopping-development-services)
- [Running npm scripts](#running-npm-scripts)
- [Running npm scripts in multiple projects](#running-npm-scripts-in-multiple-projects)
- [Handling environment variables](#handling-environment-variables)
@@ -55,6 +56,7 @@ The projects that can be added to a Monux monorepo also provide a lot of functio
- [How do they work?](#how-do-they-work)
- [Starting prod locally](#starting-prod-locally)
- [Starting in production](#starting-in-production)
+ - [Global Management of Monorepos](#global-management-of-monorepos)
- [Supported project types](#supported-project-types)
- [Angular app](#angular-app)
- [Angular website](#angular-website)
@@ -107,13 +109,22 @@ That section also includes a guide on how to add projects manually.
## Running development services
Some things like databases will be added to the monorepo solely in the docker compose.
-To use these during development, the cli includes the `mx up-dev` command.
+To use these during development, the cli includes the `mx up` command, where you can provide the "dev" environment when prompted.
## Listing monorepo services
To list all of Monux monorepos and their respective docker services we included the `mx ls` and `mx la` commands.
Where `ls` or `list` only shows monorepos with currently running docker services, while `la` or `list-all` also shows monorepos with stopped docker services.
+Both of these commands finds Monux workspaces globally. The only constraint for being found is that `mx up` had to be used at least once.
+
+## Stopping development services
+To stop your services again, Monux provides the `mx down` command.
+
+>If you are used to docker:
+>Please note that this is NOT equivalent to `docker compose down`.
+>This actually uses `docker compose stop` internally.
+
## Running npm scripts
To run an npm script of one of your projects you can use `mx {projectName} {npmScript}`. This works for projects in the "apps" and "libs" directories of your monorepo.
@@ -135,7 +146,7 @@ How these environment files are generated depends on a `environment.model.ts`-fi
That way it is possible to only have certain variables like an contact email-address be available to a website project, while certain other variables like a db-password are not.
### Calculated environment variables
-Working with static variables can sometimes be pretty tedious. This is especially true when you want to support different "modes" in which to launch your application (like we do with the `mx up`, `mx up-dev` or `mx up-local` commands).
+Working with static variables can sometimes be pretty tedious. This is especially true when you want to support different "modes" in which to launch your application (like we do with the different options for `mx up`).
For example, if we defined the variables "api_base_url", "website_base_url" and "admin_base_url" all statically, we would need to manually fiddle with the `.env`-file anytime we switch between dev and local.
To solve this, Monux implements calculated environment variables.
@@ -240,7 +251,7 @@ Notice that these files also include a Environment type for the specific project
#### Automatically create the projects environment.ts files
The `environment.ts` that is used by the project needs to be generated by running `mx prepare`. This will also take care of the validation mentioned before.
-When you use any of `mx up`, `mx up-dev` or `mx up-local` to deploy the project, the `mx prepare` command is actually called internally, so you don't have to call it manually.
+When you use `mx up` to deploy the project, the `mx prepare` command is actually called internally, so you don't have to call it manually.
## Handling initial database content
Because Docker Compose environment variables can often only initialize 1 default user and 1 default db, Monux provides a way to add multiple of these during the `mx prepare` command.
@@ -268,15 +279,23 @@ Monux handles everything regarding mapping these variable names back to values a
## Starting prod locally
Often times you want to test your project under production like constraints (eg. when developing a website to check its SEO performance).
-For that Monux provides the command `mx up-local`.
+For that, you can run `mx up` with the environment "local".
## Starting in production
-You can start the whole monorepo with the single command `mx up`.
+You can start the whole monorepo with running `mx up` and then selecting "prod" as the environment.
This will try to run the `mx prepare` command.
The only info required by that command is inside the `.env`-file.
Monux also validates the `.env`-file, so by continueously running the command you can fill it little by little and don't need to worry that you start your monorepo with invalid or missing environment variables.
+## Global Management of Monorepos
+The Monux cli commands `mx up`, `mx down`, `mx ls` and `mx la` can be run globally. That way you don't have to open up a certain directory just to exit some services.
+
+The way this works is by supplying an additional argument to `up` and `down`: The name of the monorepo.
+
+The name of the monorepo is simply the directory name. If you are unsure about that or don't quite now what is currently running, you can simply use the `mx ls`/`mx la` commands.
+They will give you a nice overview of all monorepos on your machine and their respective services, including if they are running or not.
+
# Supported project types
## Angular app
## Angular website
diff --git a/package-lock.json b/package-lock.json
index c687210..e59cd7c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "monux-cli",
- "version": "2.1.2",
+ "version": "2.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "monux-cli",
- "version": "2.1.2",
+ "version": "2.2.0",
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2",
diff --git a/package.json b/package.json
index 2e50256..960610a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "monux-cli",
- "version": "2.1.2",
+ "version": "2.2.0",
"license": "MIT",
"main": "index.js",
"engines": {
diff --git a/src/angular/angular.utilities.ts b/src/angular/angular.utilities.ts
index c98322b..903d7f6 100644
--- a/src/angular/angular.utilities.ts
+++ b/src/angular/angular.utilities.ts
@@ -177,13 +177,15 @@ export abstract class AngularUtilities {
name,
getPath(projectRoot, 'src', 'environment', ENVIRONMENT_MODEL_TS_FILE_NAME),
DefaultEnvKeys.baseUrl(apiName),
- false
+ false,
+ getPath('.')
);
await EnvUtilities.addProjectVariableKey(
name,
getPath(projectRoot, 'src', 'environment', ENVIRONMENT_MODEL_TS_FILE_NAME),
DefaultEnvKeys.domain(apiName),
- false
+ false,
+ getPath('.')
);
const authServicePath: string = getPath(projectRoot, 'src', 'app', 'services', 'auth.service.ts');
await FsUtilities.createFile(authServicePath, authServiceContent);
@@ -619,8 +621,8 @@ export abstract class AngularUtilities {
* @param domain - The domain of the project. Is needed to create the robots.txt file when the baseUrl environment variable has not been set yet.
*/
static async addSitemapAndRobots(root: string, projectName: string, domain: string): Promise {
- const app: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(projectName);
- await RobotsUtilities.createRobotsTxtForApp(app, 'dev.docker-compose.yaml', domain);
+ const app: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(projectName, getPath('.'));
+ await RobotsUtilities.createRobotsTxtForApp(app, 'dev.docker-compose.yaml', domain, getPath('.'));
await FsUtilities.createFile(getPath(root, 'src', SITEMAP_FILE_NAME), [
'',
'',
diff --git a/src/commands/add/add-angular-library/add-angular-library.command.ts b/src/commands/add/add-angular-library/add-angular-library.command.ts
index 495991a..fb7bc11 100644
--- a/src/commands/add/add-angular-library/add-angular-library.command.ts
+++ b/src/commands/add/add-angular-library/add-angular-library.command.ts
@@ -9,7 +9,7 @@ import { TsConfig, TsConfigUtilities } from '../../../tsconfig';
import { OmitStrict } from '../../../types';
import { getPath, mergeDeep } from '../../../utilities';
import { WorkspaceConfig, WorkspaceProject, WorkspaceUtilities } from '../../../workspace';
-import { AddCommand } from '../models';
+import { BaseAddCommand } from '../models';
import { AddConfiguration } from '../models/add-configuration.model';
/**
@@ -33,7 +33,7 @@ type CreateResult = {
/**
* Command that handles adding an angular library to the monorepo.
*/
-export class AddAngularLibraryCommand extends AddCommand {
+export class AddAngularLibraryCommand extends BaseAddCommand {
protected override configQuestions: QuestionsFor> = {
scope: {
@@ -118,7 +118,7 @@ export class AddAngularLibraryCommand extends AddCommand {
+export class AddAngularWebsiteCommand extends BaseAddCommand {
protected override configQuestions: QuestionsFor> = {
port: {
@@ -75,7 +75,8 @@ export class AddAngularWebsiteCommand extends AddCommand {
@@ -236,7 +237,7 @@ export class AddAngularWebsiteCommand extends AddCommand {
+export class AddAngularCommand extends BaseAddCommand {
protected override readonly configQuestions: QuestionsFor> = {
port: {
type: 'number',
@@ -100,7 +99,11 @@ export class AddAngularCommand extends AddCommand {
AngularUtilities.setupMaterial(root)
]);
- const prodRootDomain: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.PROD_ROOT_DOMAIN, 'dev.docker-compose.yaml');
+ const prodRootDomain: string = await EnvUtilities.getEnvVariable(
+ DefaultEnvKeys.PROD_ROOT_DOMAIN,
+ 'dev.docker-compose.yaml',
+ getPath('.')
+ );
const fullDomain: string = config.subDomain ? `${config.subDomain}.${prodRootDomain}` : prodRootDomain;
await AngularUtilities.setupNavigation(root, config.name);
@@ -119,8 +122,8 @@ export class AddAngularCommand extends AddCommand {
await NpmUtilities.updatePackageJson(config.name, { scripts: { start: `ng serve --port ${config.port}` } });
- const app: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.name);
- await EnvUtilities.buildEnvironmentFileForApp(app, false, 'dev.docker-compose.yaml');
+ const app: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.name, getPath('.'));
+ await EnvUtilities.buildEnvironmentFileForApp(app, false, 'dev.docker-compose.yaml', getPath('.'));
}
private async setupTailwind(root: string): Promise {
@@ -189,7 +192,7 @@ export class AddAngularCommand extends AddCommand {
`new ${config.name}`,
{ '--skip-git': true, '--style': 'css', '--inline-style': true, '--ssr': true }
);
- const newProject: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.name);
+ const newProject: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.name, getPath('.'));
await FsUtilities.updateFile(getPath(newProject.path, 'src', 'app', 'app.component.html'), '', 'replace');
await AngularUtilities.addProvider(newProject.path, 'provideHttpClient(withInterceptorsFromDi(), withFetch())', [
// eslint-disable-next-line sonar/no-duplicate-string
diff --git a/src/commands/add/add-loopback/add-loopback.command.ts b/src/commands/add/add-loopback/add-loopback.command.ts
index e7c8cf7..58a41d1 100644
--- a/src/commands/add/add-loopback/add-loopback.command.ts
+++ b/src/commands/add/add-loopback/add-loopback.command.ts
@@ -11,7 +11,7 @@ import { TsConfigUtilities } from '../../../tsconfig';
import { OmitStrict } from '../../../types';
import { getPath, toKebabCase, toPascalCase } from '../../../utilities';
import { WorkspaceProject, WorkspaceUtilities } from '../../../workspace';
-import { AddCommand } from '../models';
+import { BaseAddCommand } from '../models';
import { AddConfiguration } from '../models/add-configuration.model';
/**
@@ -46,7 +46,7 @@ export type AddLoopbackConfiguration = AddConfiguration & {
/**
* Command that handles adding a loopback api to the monorepo.
*/
-export class AddLoopbackCommand extends AddCommand {
+export class AddLoopbackCommand extends BaseAddCommand {
protected override configQuestions: QuestionsFor> = {
port: {
type: 'number',
@@ -80,7 +80,7 @@ export class AddLoopbackCommand extends AddCommand {
override async run(): Promise {
const config: AddLoopbackConfiguration = await this.getConfig();
- const { dbServiceName, databaseName } = await DbUtilities.configureDb(config.name);
+ const { dbServiceName, databaseName } = await DbUtilities.configureDb(config.name, undefined, getPath('.'));
const root: string = await this.createProject(config);
await EnvUtilities.setupProjectEnvironment(root, false);
await this.createLoopbackDatasource(dbServiceName, databaseName, root, config.name);
@@ -119,8 +119,8 @@ export class AddLoopbackCommand extends AddCommand {
await LoopbackUtilities.setupChangeSets(root, config.name);
await LoopbackUtilities.setupMigrations(root, config.name);
- const app: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.name);
- await EnvUtilities.buildEnvironmentFileForApp(app, false, 'dev.docker-compose.yaml');
+ const app: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.name, getPath('.'));
+ await EnvUtilities.buildEnvironmentFileForApp(app, false, 'dev.docker-compose.yaml', getPath('.'));
}
private async updateDockerFile(root: string): Promise {
@@ -221,11 +221,22 @@ export class AddLoopbackCommand extends AddCommand {
projectName,
environmentModel,
DefaultEnvKeys.dbPassword(dbServiceName, databaseName),
- true
+ true,
+ getPath('.')
);
- await EnvUtilities.addProjectVariableKey(projectName, environmentModel, DefaultEnvKeys.dbUser(dbServiceName, databaseName), true);
- await EnvUtilities.addProjectVariableKey(projectName, environmentModel, DefaultEnvKeys.dbName(dbServiceName, databaseName), true);
- await EnvUtilities.addProjectVariableKey(projectName, environmentModel, DefaultEnvKeys.dbHost(dbServiceName), true);
+ await EnvUtilities.addProjectVariableKey(
+ projectName,
+ environmentModel,
+ DefaultEnvKeys.dbUser(dbServiceName, databaseName),
+ true,
+ getPath('.')
+ );
+ await EnvUtilities.addProjectVariableKey(projectName,
+ environmentModel,
+ DefaultEnvKeys.dbName(dbServiceName, databaseName),
+ true,
+ getPath('.'));
+ await EnvUtilities.addProjectVariableKey(projectName, environmentModel, DefaultEnvKeys.dbHost(dbServiceName), true, getPath('.'));
}
private async createProject(config: AddLoopbackConfiguration): Promise {
@@ -240,7 +251,7 @@ export class AddLoopbackCommand extends AddCommand {
vscode: false
}
});
- const newProject: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.name);
+ const newProject: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.name, getPath('.'));
await Promise.all([
FsUtilities.rm(getPath(newProject.path, 'src', '__tests__')),
FsUtilities.rm(getPath(newProject.path, GIT_IGNORE_FILE_NAME)),
diff --git a/src/commands/add/add-ts-library/add-ts-library.command.ts b/src/commands/add/add-ts-library/add-ts-library.command.ts
index 33b41db..ad63595 100644
--- a/src/commands/add/add-ts-library/add-ts-library.command.ts
+++ b/src/commands/add/add-ts-library/add-ts-library.command.ts
@@ -7,8 +7,7 @@ import { TsConfigUtilities } from '../../../tsconfig';
import { OmitStrict } from '../../../types';
import { getPath } from '../../../utilities';
import { WorkspaceConfig, WorkspaceProject, WorkspaceUtilities } from '../../../workspace';
-import { AddCommand } from '../models/add-command.class';
-import { AddConfiguration } from '../models/add-configuration.model';
+import { AddConfiguration, BaseAddCommand } from '../models';
/**
* Configuration for creating a new ts library.
@@ -23,7 +22,7 @@ type TsLibraryConfiguration = AddConfiguration & {
/**
* The command for adding a typescript library to the monorepo.
*/
-export class AddTsLibraryCommand extends AddCommand {
+export class AddTsLibraryCommand extends BaseAddCommand {
protected override configQuestions: QuestionsFor> = {
scope: {
type: 'input',
@@ -59,7 +58,7 @@ export class AddTsLibraryCommand extends AddCommand {
}
private async installInProjects(config: TsLibraryConfiguration): Promise {
- const projects: WorkspaceProject[] = await WorkspaceUtilities.getProjects('apps');
+ const projects: WorkspaceProject[] = await WorkspaceUtilities.getProjects('apps', getPath('.'));
const npmPackage: string = `${config.scope}/${config.name}`;
await Promise.all(projects.map((p) => NpmUtilities.install(p.name, [npmPackage as NpmPackage])));
}
diff --git a/src/commands/add/add-wordpress/add-wordpress.command.ts b/src/commands/add/add-wordpress/add-wordpress.command.ts
index a14fcf6..553c4f0 100644
--- a/src/commands/add/add-wordpress/add-wordpress.command.ts
+++ b/src/commands/add/add-wordpress/add-wordpress.command.ts
@@ -4,8 +4,8 @@ import { ComposeService, DockerUtilities } from '../../../docker';
import { QuestionsFor } from '../../../encapsulation';
import { DefaultEnvKeys } from '../../../env';
import { OmitStrict } from '../../../types';
-import { toKebabCase } from '../../../utilities';
-import { AddCommand, AddConfiguration } from '../models';
+import { getPath, toKebabCase } from '../../../utilities';
+import { BaseAddCommand, AddConfiguration } from '../models';
/**
* Configuration for adding a wordpress service to the monorepo.
@@ -20,7 +20,7 @@ type AddWordpressConfiguration = AddConfiguration & {
/**
* The command for adding a wordpress server to the monorepo.
*/
-export class AddWordpressCommand extends AddCommand {
+export class AddWordpressCommand extends BaseAddCommand {
protected override readonly configQuestions: QuestionsFor> = {
subDomain: {
type: 'input',
@@ -31,7 +31,7 @@ export class AddWordpressCommand extends AddCommand {
override async run(): Promise {
const config: AddWordpressConfiguration = await this.getConfig();
- const { dbServiceName, databaseName } = await DbUtilities.configureDb(config.name, DbType.MARIADB);
+ const { dbServiceName, databaseName } = await DbUtilities.configureDb(config.name, DbType.MARIADB, getPath('.'));
await this.createProject(config, dbServiceName, databaseName);
}
diff --git a/src/commands/add/add.command.ts b/src/commands/add/add.command.ts
index 83b6575..ee7141c 100644
--- a/src/commands/add/add.command.ts
+++ b/src/commands/add/add.command.ts
@@ -4,54 +4,46 @@ import { AddAngularWebsiteCommand } from './add-angular-website';
import { AddLoopbackCommand } from './add-loopback';
import { AddTsLibraryCommand } from './add-ts-library';
import { AddWordpressCommand } from './add-wordpress';
-import { AddConfiguration, AddType } from './models';
-import { InquirerUtilities, QuestionsFor } from '../../encapsulation';
-import { WorkspaceUtilities } from '../../workspace';
-
-const addConfigQuestions: QuestionsFor = {
- type: {
- type: 'select',
- choices: Object.values(AddType),
- message: 'type'
- },
- name: {
- type: 'input',
- message: 'name',
- required: true,
- validate: async (input: string) => await WorkspaceUtilities.findProject(input) == undefined
- }
-};
+import { AddConfiguration, addConfigurationQuestions, AddType } from './models';
+import { InquirerUtilities } from '../../encapsulation';
+import { BaseCommand } from '../base-command.model';
/**
- * Runs the add cli command.
+ * Adds a new project to the current monorepo.
*/
-export async function runAdd(): Promise {
- const config: AddConfiguration = await InquirerUtilities.prompt(addConfigQuestions);
+export class AddCommand extends BaseCommand {
+ protected override readonly insideWorkspace: boolean = true;
- switch (config.type) {
- case AddType.ANGULAR: {
- await new AddAngularCommand(config).run();
- return;
- }
- case AddType.ANGULAR_LIBRARY: {
- await new AddAngularLibraryCommand(config).run();
- return;
- }
- case AddType.ANGULAR_WEBSITE: {
- await new AddAngularWebsiteCommand(config).run();
- return;
- }
- case AddType.LOOPBACK: {
- await new AddLoopbackCommand(config).run();
- return;
- }
- case AddType.TS_LIBRARY: {
- await new AddTsLibraryCommand(config).run();
- return;
- }
- case AddType.WORDPRESS: {
- await new AddWordpressCommand(config).run();
- return;
+ protected override async run(config: AddConfiguration): Promise {
+ switch (config.type) {
+ case AddType.ANGULAR: {
+ await new AddAngularCommand(config).run();
+ return;
+ }
+ case AddType.ANGULAR_LIBRARY: {
+ await new AddAngularLibraryCommand(config).run();
+ return;
+ }
+ case AddType.ANGULAR_WEBSITE: {
+ await new AddAngularWebsiteCommand(config).run();
+ return;
+ }
+ case AddType.LOOPBACK: {
+ await new AddLoopbackCommand(config).run();
+ return;
+ }
+ case AddType.TS_LIBRARY: {
+ await new AddTsLibraryCommand(config).run();
+ return;
+ }
+ case AddType.WORDPRESS: {
+ await new AddWordpressCommand(config).run();
+ return;
+ }
}
}
+
+ protected override async resolveInput(): Promise {
+ return await InquirerUtilities.prompt(addConfigurationQuestions);
+ }
}
\ No newline at end of file
diff --git a/src/commands/add/models/add-configuration.model.ts b/src/commands/add/models/add-configuration.model.ts
index d701c0e..5ddba08 100644
--- a/src/commands/add/models/add-configuration.model.ts
+++ b/src/commands/add/models/add-configuration.model.ts
@@ -1,3 +1,7 @@
+import { QuestionsFor } from '../../../encapsulation';
+import { getPath } from '../../../utilities';
+import { WorkspaceUtilities } from '../../../workspace';
+
/**
* The type of project to add.
*/
@@ -22,4 +26,21 @@ export type AddConfiguration = {
* The name of the new project.
*/
name: string
+};
+
+/**
+ * Questions for getting the base configuration for adding a new project to a monorepo.
+ */
+export const addConfigurationQuestions: QuestionsFor = {
+ type: {
+ type: 'select',
+ choices: Object.values(AddType),
+ message: 'type'
+ },
+ name: {
+ type: 'input',
+ message: 'name',
+ required: true,
+ validate: async (input: string) => await WorkspaceUtilities.findProject(input, getPath('.')) == undefined
+ }
};
\ No newline at end of file
diff --git a/src/commands/add/models/add-command.class.ts b/src/commands/add/models/base-add-command.model.ts
similarity index 91%
rename from src/commands/add/models/add-command.class.ts
rename to src/commands/add/models/base-add-command.model.ts
index eb182bb..2fdc009 100644
--- a/src/commands/add/models/add-command.class.ts
+++ b/src/commands/add/models/base-add-command.model.ts
@@ -5,7 +5,7 @@ import { OmitStrict } from '../../../types';
/**
* Base Add Command class.
*/
-export abstract class AddCommand {
+export abstract class BaseAddCommand {
protected abstract readonly configQuestions: QuestionsFor>;
diff --git a/src/commands/add/models/index.ts b/src/commands/add/models/index.ts
index 0b3fb6d..9d8bf81 100644
--- a/src/commands/add/models/index.ts
+++ b/src/commands/add/models/index.ts
@@ -1,2 +1,2 @@
-export * from './add-command.class';
+export * from './base-add-command.model';
export * from './add-configuration.model';
\ No newline at end of file
diff --git a/src/commands/base-command.model.ts b/src/commands/base-command.model.ts
new file mode 100644
index 0000000..79d969c
--- /dev/null
+++ b/src/commands/base-command.model.ts
@@ -0,0 +1,80 @@
+import { exitWithError } from '../utilities';
+import { WorkspaceConfig, WorkspaceUtilities } from '../workspace';
+
+const TOO_MANY_ARGUMENTS_ERROR_MESSAGE: string = 'Error parsing the command: Too many arguments.';
+
+/**
+ * Contains configuration shared by every cli command of Monux.
+ */
+export abstract class BaseCommand {
+ /**
+ * Whether or not the command should only be called from inside of an initialized workspace.
+ * @default false
+ */
+ protected readonly insideWorkspace: boolean = false;
+ /**
+ * The maximum length of arguments that should be provided.
+ * When set to undefined, the amount of arguments is unrestricted.
+ * @default 1
+ */
+ protected readonly maxLength: number | undefined = 1;
+
+ /**
+ * Starts the cli command.
+ * Calls validation, resolves the input and then runs the actual command.
+ * @param args - The args from the cli.
+ */
+ async start(args: string[]): Promise {
+ await this.validate(args);
+ const input: Input = await this.resolveInput(args);
+ await this.run(input);
+ }
+
+ protected abstract run(input: Input): Promise | void;
+
+ /**
+ * Resolves the input args of the cli to input that can be used by the command.
+ * @param args - The user provided args.
+ * @returns The resolved input.
+ */
+ // eslint-disable-next-line unusedImports/no-unused-vars
+ protected resolveInput(args: string[]): Input | Promise {
+ return undefined as unknown as Input;
+ }
+
+ /**
+ * Validates the user input.
+ * @param args - The cli args.
+ */
+ protected async validate(args: string[]): Promise {
+ this.validateMaxLength(args);
+ await this.validateInsideWorkspace();
+ }
+
+ /**
+ * Validates that the cwd is a Monux workspace.
+ */
+ protected async validateInsideWorkspace(): Promise {
+ if (!this.insideWorkspace) {
+ return;
+ }
+ const config: WorkspaceConfig | undefined = await WorkspaceUtilities.getConfig();
+ // eslint-disable-next-line typescript/strict-boolean-expressions
+ if (!config?.isWorkspace) {
+ exitWithError('This command can only be run inside a workspace');
+ }
+ }
+
+ /**
+ * Validates that the provided args are not bigger than the maxLength.
+ * @param args - The cli args.
+ */
+ protected validateMaxLength(args: string[]): void {
+ if (this.maxLength == undefined) {
+ return;
+ }
+ if (args.length > this.maxLength) {
+ exitWithError(TOO_MANY_ARGUMENTS_ERROR_MESSAGE);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/commands/command.enum.ts b/src/commands/command.enum.ts
index fcaa125..76599d6 100644
--- a/src/commands/command.enum.ts
+++ b/src/commands/command.enum.ts
@@ -16,14 +16,6 @@ export enum Command {
U = 'u',
DOWN = 'down',
D = 'd',
- UP_DEV = 'up-dev',
- UD = 'ud',
- DOWN_DEV = 'down-dev',
- DD = 'dd',
- UP_LOCAL = 'up-local',
- UL = 'ul',
- DOWN_LOCAL = 'down-local',
- DL = 'dl',
GENERATE_PAGE = 'generatePage',
GP = 'gp',
RUN_ALL = 'run-all',
diff --git a/src/commands/down-dev/down-dev.command.ts b/src/commands/down-dev/down-dev.command.ts
deleted file mode 100644
index 91ebeb2..0000000
--- a/src/commands/down-dev/down-dev.command.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { DEV_DOCKER_COMPOSE_FILE_NAME } from '../../constants';
-import { CPUtilities } from '../../encapsulation';
-
-/**
- * Shuts down the dev services.
- */
-export function runDownDev(): void {
- CPUtilities.execSync(`docker compose -f ${DEV_DOCKER_COMPOSE_FILE_NAME} stop`);
-}
\ No newline at end of file
diff --git a/src/commands/down-dev/index.ts b/src/commands/down-dev/index.ts
deleted file mode 100644
index c5ec1f3..0000000
--- a/src/commands/down-dev/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './down-dev.command';
\ No newline at end of file
diff --git a/src/commands/down-local/down-local.command.ts b/src/commands/down-local/down-local.command.ts
deleted file mode 100644
index df89f19..0000000
--- a/src/commands/down-local/down-local.command.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { LOCAL_DOCKER_COMPOSE_FILE_NAME } from '../../constants';
-import { CPUtilities } from '../../encapsulation';
-
-/**
- * Shuts down the local services.
- */
-export function runDownLocal(): void {
- CPUtilities.execSync(`docker compose -f ${LOCAL_DOCKER_COMPOSE_FILE_NAME} stop`);
-}
\ No newline at end of file
diff --git a/src/commands/down-local/index.ts b/src/commands/down-local/index.ts
deleted file mode 100644
index db01d9b..0000000
--- a/src/commands/down-local/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './down-local.command';
\ No newline at end of file
diff --git a/src/commands/down/down-configuration.model.ts b/src/commands/down/down-configuration.model.ts
new file mode 100644
index 0000000..6340020
--- /dev/null
+++ b/src/commands/down/down-configuration.model.ts
@@ -0,0 +1,13 @@
+/**
+ * Configuration for running the down command.
+ */
+export type DownConfiguration = {
+ /**
+ * Paths of all docker files of a monorepo.
+ */
+ dockerFilePaths: string[],
+ /**
+ * The name of the Monux repo to run down for.
+ */
+ projectName: string
+};
\ No newline at end of file
diff --git a/src/commands/down/down.command.ts b/src/commands/down/down.command.ts
index 7608d56..581cdde 100644
--- a/src/commands/down/down.command.ts
+++ b/src/commands/down/down.command.ts
@@ -1,8 +1,47 @@
+import { getDockerServices } from '../../docker/get-docker-services.function';
+import { FullyParsedDockerService } from '../../docker/stringified-docker-service.model';
import { CPUtilities } from '../../encapsulation';
+import { exitWithError } from '../../utilities';
+import { WorkspaceUtilities } from '../../workspace';
+import { BaseCommand } from '../base-command.model';
+import { DownConfiguration } from './down-configuration.model';
/**
* Shuts down the monorepo.
*/
-export function runDown(): void {
- CPUtilities.execSync('docker compose stop');
+export class DownCommand extends BaseCommand {
+ protected override readonly insideWorkspace: boolean = true;
+ protected override readonly maxLength: number = 2;
+
+ protected override run(input: DownConfiguration): Promise | void {
+ for (const filePath of input.dockerFilePaths) {
+ CPUtilities.execSync(`docker compose -f ${filePath} -p ${input.projectName} stop`);
+ }
+ }
+
+ protected override async validate(args: string[]): Promise {
+ this.validateMaxLength(args);
+ if (args.length === 1) {
+ await this.validateInsideWorkspace();
+ }
+ }
+
+ protected override async resolveInput(args: string[]): Promise {
+ const projectName: string = args.length === 1 ? (await WorkspaceUtilities.getConfigOrFail()).name : args[1];
+ const dockerServices: FullyParsedDockerService[] = await getDockerServices(false);
+
+ const dockerFilePaths: string[] = dockerServices
+ .filter(d => d.config.name === projectName)
+ .map(d => d.Labels['com.docker.compose.project.config_files'])
+ .filter(d => d != undefined);
+
+ if (!dockerFilePaths.length) {
+ exitWithError(`Error: Could not find any running docker services for "${projectName}"`);
+ }
+
+ return {
+ dockerFilePaths: [...new Set(dockerFilePaths)],
+ projectName
+ };
+ }
}
\ No newline at end of file
diff --git a/src/commands/generate-page/generate-page-configuration.model.ts b/src/commands/generate-page/generate-page-configuration.model.ts
new file mode 100644
index 0000000..f1a0891
--- /dev/null
+++ b/src/commands/generate-page/generate-page-configuration.model.ts
@@ -0,0 +1,51 @@
+import { QuestionsFor } from '../../encapsulation';
+import { getPath } from '../../utilities';
+import { WorkspaceUtilities } from '../../workspace';
+
+/**
+ * Configuration for generating an angular page.
+ */
+export type GeneratePageConfiguration = {
+ /**
+ * The name of the project to generate the page for.
+ */
+ projectName: string,
+ /**
+ * The name of the page to generate.
+ */
+ pageName: string,
+ /**
+ * The route under which the page should be reachable.
+ */
+ route: string,
+ /**
+ * The meta title of the page.
+ */
+ title: string
+};
+
+/**
+ * Questions for getting the configuration for generating an angular page.
+ */
+export const generatePageConfigurationQuestions: QuestionsFor = {
+ projectName: {
+ message: 'Project',
+ type: 'select',
+ choices: async () => (await WorkspaceUtilities.getProjects('apps', getPath('.'))).map(a => a.name)
+ },
+ pageName: {
+ message: 'Page name',
+ type: 'input',
+ required: true
+ },
+ route: {
+ message: 'Route',
+ type: 'input',
+ required: true
+ },
+ title: {
+ message: 'Title',
+ type: 'input',
+ required: true
+ }
+};
\ No newline at end of file
diff --git a/src/commands/generate-page/generate-page.command.ts b/src/commands/generate-page/generate-page.command.ts
index 98fdcfc..014ef26 100644
--- a/src/commands/generate-page/generate-page.command.ts
+++ b/src/commands/generate-page/generate-page.command.ts
@@ -1,89 +1,53 @@
+import { GeneratePageConfiguration, generatePageConfigurationQuestions } from './generate-page-configuration.model';
import { AddNavElementConfig, AngularUtilities, NavElementTypes } from '../../angular';
import { ANGULAR_ROUTES_FILE_NAME } from '../../constants';
-import { FsUtilities, InquirerUtilities, QuestionsFor } from '../../encapsulation';
+import { FsUtilities, InquirerUtilities } from '../../encapsulation';
import { DefaultEnvKeys, EnvUtilities } from '../../env';
import { getPath, toKebabCase, toPascalCase } from '../../utilities';
import { WorkspaceProject, WorkspaceUtilities } from '../../workspace';
-
-/**
- * Options for generating an angular page.
- */
-type GeneratePageOptions = {
- /**
- * The name of the project to generate the page for.
- */
- projectName: string,
- /**
- * The name of the page to generate.
- */
- pageName: string,
- /**
- * The route under which the page should be reachable.
- */
- route: string,
- /**
- * The meta title of the page.
- */
- title: string
-};
+import { BaseCommand } from '../base-command.model';
const LOAD_COMPONENT_PLACEHOLDER: string = 'LOAD_COMPONENT_PLACEHOLDER';
/**
- * Runs the generate page cli command.
+ * Generates an angular page.
*/
-export async function runGeneratePage(): Promise {
- const apps: WorkspaceProject[] = await WorkspaceUtilities.getProjects('apps');
- const questions: QuestionsFor = {
- projectName: {
- message: 'Project',
- type: 'select',
- choices: apps.map(a => a.name)
- },
- pageName: {
- message: 'Page name',
- type: 'input',
- required: true
- },
- route: {
- message: 'Route',
- type: 'input',
- required: true
- },
- title: {
- message: 'Title',
- type: 'input',
- required: true
- }
- };
+export class GeneratePageCommand extends BaseCommand {
+ protected override readonly insideWorkspace: boolean = true;
+
+ protected override async run(input: GeneratePageConfiguration): Promise {
+ const projectRoot: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(input.projectName, getPath('.'));
+ const domain: string = await EnvUtilities.getEnvVariable(
+ DefaultEnvKeys.domain(input.projectName),
+ 'dev.docker-compose.yaml',
+ getPath('.')
+ );
- const options: GeneratePageOptions = await InquirerUtilities.prompt(questions);
- const projectRoot: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(options.projectName);
- const domain: string = await EnvUtilities.getEnvVariable(
- DefaultEnvKeys.domain(options.projectName),
- 'dev.docker-compose.yaml'
- );
+ const navElement: AddNavElementConfig = {
+ addTo: 'navbar',
+ element: {
+ type: NavElementTypes.INTERNAL_LINK,
+ name: input.pageName,
+ route: {
+ path: input.route,
+ title: input.title,
+ // eslint-disable-next-line typescript/no-unsafe-assignment, typescript/no-explicit-any
+ loadComponent: LOAD_COMPONENT_PLACEHOLDER as any
+ }
+ },
+ rowIndex: 0
+ };
- const navElement: AddNavElementConfig = {
- addTo: 'navbar',
- element: {
- type: NavElementTypes.INTERNAL_LINK,
- name: options.pageName,
- route: {
- path: options.route,
- title: options.title,
- // eslint-disable-next-line typescript/no-unsafe-assignment, typescript/no-explicit-any
- loadComponent: LOAD_COMPONENT_PLACEHOLDER as any
- }
- },
- rowIndex: 0
- };
+ await AngularUtilities.generatePage(projectRoot.path, input.pageName, navElement, domain);
+ const pageName: string = toKebabCase(input.pageName);
+ await FsUtilities.replaceInFile(
+ getPath(projectRoot.path, 'src', 'app', ANGULAR_ROUTES_FILE_NAME),
+ `\'${LOAD_COMPONENT_PLACEHOLDER}\'`,
+ `() => import(\'./pages/${pageName}/${pageName}.component\').then(m => m.${toPascalCase(input.pageName)}Component)`
+ );
+ }
- await AngularUtilities.generatePage(projectRoot.path, options.pageName, navElement, domain);
- const pageName: string = toKebabCase(options.pageName);
- await FsUtilities.replaceInFile(
- getPath(projectRoot.path, 'src', 'app', ANGULAR_ROUTES_FILE_NAME),
- `\'${LOAD_COMPONENT_PLACEHOLDER}\'`,
- `() => import(\'./pages/${pageName}/${pageName}.component\').then(m => m.${toPascalCase(options.pageName)}Component)`
- );
+ protected override async resolveInput(): Promise {
+ return await InquirerUtilities.prompt(generatePageConfigurationQuestions);
+ }
}
\ No newline at end of file
diff --git a/src/commands/help/help.command.ts b/src/commands/help/help.command.ts
index 53148fb..8ea7572 100644
--- a/src/commands/help/help.command.ts
+++ b/src/commands/help/help.command.ts
@@ -1,49 +1,45 @@
/* eslint-disable no-console */
import { APPS_DIRECTORY_NAME, CLI_BASE_COMMAND, LIBS_DIRECTORY_NAME } from '../../constants';
import { ChalkUtilities } from '../../encapsulation';
+import { BaseCommand } from '../base-command.model';
import { Command } from '../command.enum';
-// eslint-disable-next-line jsdoc/require-jsdoc
-function getCommandLabel(command: Command, shortForm: Command): string {
- return `${ChalkUtilities.secondary(command)} / ${ChalkUtilities.secondary(shortForm)}:`;
-}
-
/**
- * Runs the help cli command.
+ * Displays info on how to use the cli.
*/
-export function runHelp(): void {
- console.log(ChalkUtilities.boldUnderline('Commands:'));
- console.log(getCommandLabel(Command.HELP, Command.H), 'opens this help-page');
- console.log(getCommandLabel(Command.VERSION, Command.V), 'shows the currently used version');
- console.log(getCommandLabel(Command.INIT, Command.I), 'initializes a new monorepo workspace');
- console.log(getCommandLabel(Command.ADD, Command.A), 'adds a new application to the current monorepo workspace');
- console.log(getCommandLabel(Command.PREPARE, Command.P), 'Handles things like creating robots.txt and environment.ts files');
- console.log(getCommandLabel(Command.UP_DEV, Command.UD), 'deploys things like a database with connection to localhost for development');
- console.log(getCommandLabel(Command.DOWN_DEV, Command.DD), 'stops the deployed development services');
- console.log(
- getCommandLabel(Command.UP_LOCAL, Command.UL),
- `deploys the monorepo on localhost. This includes the ${ChalkUtilities.secondary(Command.PREPARE)} command.`
- );
- console.log(getCommandLabel(Command.DOWN_LOCAL, Command.DL), 'stops the deployed monorepo on localhost.');
- console.log(
- getCommandLabel(Command.UP, Command.U),
- `deploys the monorepo. This includes the ${ChalkUtilities.secondary(Command.PREPARE)} command.`
- );
- console.log(getCommandLabel(Command.DOWN, Command.D), 'stops the currently deployed monorepo');
- console.log(getCommandLabel(Command.LIST, Command.LS), 'lists running monorepos with their respective docker services');
- console.log(getCommandLabel(Command.LIST_ALL, Command.LA), 'lists all monorepos with their respective docker services');
- console.log(getCommandLabel(Command.GENERATE_PAGE, Command.GP), 'generates a new page for an angular project');
- console.log();
- console.log(`${ChalkUtilities.boldUnderline('Running an npm script')}:`);
- console.log(
- 'To run an npm script in one of your projects you can use',
- ChalkUtilities.exampleUsage(`"${CLI_BASE_COMMAND} {projectName} {npmScript}"`)
- );
- console.log(`This works for projects in the "${APPS_DIRECTORY_NAME}" and "${LIBS_DIRECTORY_NAME}" directories of your workspace`);
- console.log(`${ChalkUtilities.boldUnderline('Running an npm script in multiple projects')}:`);
- console.log('If you want the script to run in multiple projects,');
- console.log(
- `you can use the ${ChalkUtilities.secondary(Command.RUN_ALL)} / ${ChalkUtilities.secondary(Command.RA)} command:`,
- ChalkUtilities.exampleUsage(`"${CLI_BASE_COMMAND} ${Command.RUN_ALL} {npmScript}"`)
- );
+export class HelpCommand extends BaseCommand {
+
+ protected override run(): Promise | void {
+ console.log(ChalkUtilities.boldUnderline('Commands:'));
+ console.log(this.getCommandLabel(Command.HELP, Command.H), 'opens this help-page');
+ console.log(this.getCommandLabel(Command.VERSION, Command.V), 'shows the currently used version');
+ console.log(this.getCommandLabel(Command.INIT, Command.I), 'initializes a new monorepo workspace');
+ console.log(this.getCommandLabel(Command.ADD, Command.A), 'adds a new application to the current monorepo workspace');
+ console.log(this.getCommandLabel(Command.PREPARE, Command.P), 'Handles things like creating robots.txt and environment.ts files');
+ console.log(
+ this.getCommandLabel(Command.UP, Command.U),
+ `deploys the monorepo. This includes the ${ChalkUtilities.secondary(Command.PREPARE)} command.`
+ );
+ console.log(this.getCommandLabel(Command.DOWN, Command.D), 'stops the currently deployed monorepo');
+ console.log(this.getCommandLabel(Command.LIST, Command.LS), 'lists running monorepos with their respective docker services');
+ console.log(this.getCommandLabel(Command.LIST_ALL, Command.LA), 'lists all monorepos with their respective docker services');
+ console.log(this.getCommandLabel(Command.GENERATE_PAGE, Command.GP), 'generates a new page for an angular project');
+ console.log();
+ console.log(`${ChalkUtilities.boldUnderline('Running an npm script')}:`);
+ console.log(
+ 'To run an npm script in one of your projects you can use',
+ ChalkUtilities.exampleUsage(`"${CLI_BASE_COMMAND} {projectName} {npmScript}"`)
+ );
+ console.log(`This works for projects in the "${APPS_DIRECTORY_NAME}" and "${LIBS_DIRECTORY_NAME}" directories of your workspace`);
+ console.log(`${ChalkUtilities.boldUnderline('Running an npm script in multiple projects')}:`);
+ console.log('If you want the script to run in multiple projects,');
+ console.log(
+ `you can use the ${ChalkUtilities.secondary(Command.RUN_ALL)} / ${ChalkUtilities.secondary(Command.RA)} command:`,
+ ChalkUtilities.exampleUsage(`"${CLI_BASE_COMMAND} ${Command.RUN_ALL} {npmScript}"`)
+ );
+ }
+
+ private getCommandLabel(command: Command, shortForm: Command): string {
+ return `${ChalkUtilities.secondary(command)} / ${ChalkUtilities.secondary(shortForm)}:`;
+ }
}
\ No newline at end of file
diff --git a/src/commands/index.ts b/src/commands/index.ts
index 51cbe57..10fd1fe 100644
--- a/src/commands/index.ts
+++ b/src/commands/index.ts
@@ -4,17 +4,10 @@ export * from './init';
export * from './run';
export * from './version';
export * from './command.enum';
-export * from './exit-with-error.function';
export * from './up';
export * from './down';
export * from './prepare';
export * from './generate-page';
-export * from './up-dev';
-export * from './down-dev';
export * from './run-all';
-export * from './up-local';
export * from './is-command.function';
-export * from './is-error-with-signal.function';
-export * from './is-exit-prompt-error.function';
-export * from './exit-with-interrupt.function';
export * from './list';
\ No newline at end of file
diff --git a/src/commands/init/init-configuration.model.ts b/src/commands/init/init-configuration.model.ts
index 7a3f9a5..459f7ec 100644
--- a/src/commands/init/init-configuration.model.ts
+++ b/src/commands/init/init-configuration.model.ts
@@ -1,3 +1,5 @@
+import { QuestionsFor } from '../../encapsulation';
+
/**
* Configuration for initializing a monorepo.
*/
@@ -15,4 +17,26 @@ export type InitConfiguration = {
* Whether or not to setup github actions.
*/
setupGithubActions: boolean
+};
+
+/**
+ * Questions for getting the init configuration.
+ */
+export const initConfigQuestions: QuestionsFor = {
+ prodRootDomain: {
+ type: 'input',
+ message: 'prod root domain (eg. "test.com")',
+ required: true
+ },
+ email: {
+ type: 'input',
+ message: 'E-Mail (needed for ssl certificates)',
+ required: true
+ },
+ setupGithubActions: {
+ type: 'select',
+ message: 'Setup Github Actions?',
+ choices: [{ value: true, name: 'Yes' }, { value: false, name: 'No' }],
+ default: true
+ }
};
\ No newline at end of file
diff --git a/src/commands/init/init.command.ts b/src/commands/init/init.command.ts
index 26db91e..a77f92d 100644
--- a/src/commands/init/init.command.ts
+++ b/src/commands/init/init.command.ts
@@ -1,156 +1,130 @@
-
-import { APPS_DIRECTORY_NAME, ENV_FILE_NAME, ENVIRONMENT_TS_FILE_NAME, ESLINT_CONFIG_FILE_NAME, GIT_IGNORE_FILE_NAME, LIBS_DIRECTORY_NAME, ROBOTS_FILE_NAME, TAILWIND_CONFIG_FILE_NAME, WORKSPACE_FILE_NAME } from '../../constants';
+import { initConfigQuestions, InitConfiguration } from './init-configuration.model';
+import { APPS_DIRECTORY_NAME, ENV_FILE_NAME, ENVIRONMENT_TS_FILE_NAME, ESLINT_CONFIG_FILE_NAME, GIT_IGNORE_FILE_NAME, LIBS_DIRECTORY_NAME, ROBOTS_FILE_NAME, TAILWIND_CONFIG_FILE_NAME } from '../../constants';
import { DockerUtilities } from '../../docker';
-import { CPUtilities, FsUtilities, InquirerUtilities, QuestionsFor } from '../../encapsulation';
+import { CPUtilities, FsUtilities, InquirerUtilities } from '../../encapsulation';
import { EnvUtilities } from '../../env';
+import { GithubUtilities } from '../../github';
import { NpmPackage, NpmUtilities } from '../../npm';
import { TsConfigUtilities } from '../../tsconfig';
-import { WorkspaceUtilities } from '../../workspace';
-import { exitWithError } from '../exit-with-error.function';
-import { InitConfiguration } from './init-configuration.model';
-import { GithubUtilities } from '../../github';
-import { getPath } from '../../utilities';
-
-const initConfigQuestions: QuestionsFor = {
- prodRootDomain: {
- type: 'input',
- message: 'prod root domain (eg. "test.com")',
- required: true
- },
- email: {
- type: 'input',
- message: 'E-Mail (needed for ssl certificates)',
- required: true
- },
- setupGithubActions: {
- type: 'select',
- message: 'Setup Github Actions?',
- choices: [{ value: true, name: 'Yes' }, { value: false, name: 'No' }],
- default: true
- }
-};
+import { exitWithError } from '../../utilities';
+import { WorkspaceConfig, WorkspaceUtilities } from '../../workspace';
+import { BaseCommand } from '../base-command.model';
/**
- * Runs the init cli command.
+ * Initializes a new Monux monorepo.
*/
-export async function runInit(): Promise {
- if (await FsUtilities.exists(getPath(WORKSPACE_FILE_NAME))) {
- exitWithError('Error: The current directory is already a monorepo workspace');
- }
-
- const config: InitConfiguration = await InquirerUtilities.prompt(initConfigQuestions);
+export class InitCommand extends BaseCommand {
+ protected override async run(config: InitConfiguration): Promise {
+ await NpmUtilities.init('root', false);
- await NpmUtilities.init('root', false);
+ NpmUtilities.installInRoot([
+ NpmPackage.ESLINT_CONFIG_SERVICE_SOFT,
+ NpmPackage.ESLINT,
+ NpmPackage.TAILWIND,
+ NpmPackage.POSTCSS,
+ NpmPackage.AUTOPREFIXER
+ ], true);
- NpmUtilities.installInRoot([
- NpmPackage.ESLINT_CONFIG_SERVICE_SOFT,
- NpmPackage.ESLINT,
- NpmPackage.TAILWIND,
- NpmPackage.POSTCSS,
- NpmPackage.AUTOPREFIXER
- ], true);
+ await EnvUtilities.init(config.prodRootDomain);
- await EnvUtilities.init(config.prodRootDomain);
+ await Promise.all([
+ WorkspaceUtilities.createConfig(),
+ TsConfigUtilities.createBaseTsConfig(),
+ this.createEslintConfig(),
+ this.createCspellWords(),
+ DockerUtilities.createComposeFiles(config.email),
+ FsUtilities.mkdir(APPS_DIRECTORY_NAME),
+ FsUtilities.mkdir(LIBS_DIRECTORY_NAME),
+ this.createGitIgnore(),
+ this.createTailwindConfig(),
+ this.addNpmWorkspaces()
+ ]);
- await Promise.all([
- WorkspaceUtilities.createConfig(),
- TsConfigUtilities.createBaseTsConfig(),
- createEslintConfig(),
- createCspellWords(),
- DockerUtilities.createComposeFiles(config.email),
- FsUtilities.mkdir(APPS_DIRECTORY_NAME),
- FsUtilities.mkdir(LIBS_DIRECTORY_NAME),
- createGitIgnore(),
- createTailwindConfig(),
- addNpmWorkspaces()
- ]);
-
- CPUtilities.execSync('git init');
- if (config.setupGithubActions) {
- await GithubUtilities.createWorkflow({
- name: 'main',
- on: 'push',
- jobs: {
- test: {
- 'runs-on': 'ubuntu-latest',
- steps: [
- { uses: 'actions/checkout@v4' },
- { name: 'npm i', run: 'npm ci' },
- { name: 'Linting', run: 'npm run lint --workspaces --if-present' },
- { name: 'Unit Tests', run: 'npm run test --workspaces --if-present' }
- ]
+ CPUtilities.execSync('git init');
+ if (config.setupGithubActions) {
+ await GithubUtilities.createWorkflow({
+ name: 'main',
+ on: 'push',
+ jobs: {
+ test: {
+ 'runs-on': 'ubuntu-latest',
+ steps: [
+ { uses: 'actions/checkout@v4' },
+ { name: 'npm i', run: 'npm ci' },
+ { name: 'Linting', run: 'npm run lint --workspaces --if-present' },
+ { name: 'Unit Tests', run: 'npm run test --workspaces --if-present' }
+ ]
+ }
}
- }
- });
+ });
+ }
}
-}
-/**
- * Adds the base npm workspaces for the monorepo.
- */
-async function addNpmWorkspaces(): Promise {
- await NpmUtilities.updateRootPackageJson({ workspaces: [`${APPS_DIRECTORY_NAME}/*`, `${LIBS_DIRECTORY_NAME}/*`] });
-}
+ protected override async resolveInput(): Promise {
+ const config: InitConfiguration = await InquirerUtilities.prompt(initConfigQuestions);
+ return config;
+ }
-/**
- * Creates the base .gitignore.
- */
-async function createGitIgnore(): Promise {
- await FsUtilities.createFile(GIT_IGNORE_FILE_NAME, [
- '# See http://help.github.com/ignore-files/ for more about ignoring files.',
- ENV_FILE_NAME,
- ENVIRONMENT_TS_FILE_NAME,
- ROBOTS_FILE_NAME,
- '**/init/**.sh',
- '**/init/**.sql',
- 'letsencrypt',
- '# compiled output',
- 'dist',
- 'tmp',
- '/out-tsc',
- '.angular',
- 'tsconfig.tsbuildinfo',
- '# node_modules',
- 'node_modules'
- ]);
-}
+ protected override async validate(args: string[]): Promise {
+ await super.validate(args);
+ const config: WorkspaceConfig | undefined = await WorkspaceUtilities.getConfig();
+ if (config?.isWorkspace === true) {
+ exitWithError('Error: The current directory is already a monorepo workspace');
+ }
+ }
-/**
- * Creates a cspell words txt file used for spell checking.
- */
-async function createCspellWords(): Promise {
- await FsUtilities.createFile('cspell.words.txt', '');
-}
+ private async addNpmWorkspaces(): Promise {
+ await NpmUtilities.updateRootPackageJson({ workspaces: [`${APPS_DIRECTORY_NAME}/*`, `${LIBS_DIRECTORY_NAME}/*`] });
+ }
-/**
- * Creates an eslint config.
- */
-async function createEslintConfig(): Promise {
- await FsUtilities.createFile(ESLINT_CONFIG_FILE_NAME, [
- `import { configs } from '${NpmPackage.ESLINT_CONFIG_SERVICE_SOFT}';`,
- '',
- '// eslint-disable-next-line jsdoc/require-description',
- '/** @type {import(\'eslint\').Linter.Config} */',
- 'export default [...configs];'
- ]);
-}
+ private async createGitIgnore(): Promise {
+ await FsUtilities.createFile(GIT_IGNORE_FILE_NAME, [
+ '# See http://help.github.com/ignore-files/ for more about ignoring files.',
+ ENV_FILE_NAME,
+ ENVIRONMENT_TS_FILE_NAME,
+ ROBOTS_FILE_NAME,
+ '**/init/**.sh',
+ '**/init/**.sql',
+ 'letsencrypt',
+ '# compiled output',
+ 'dist',
+ 'tmp',
+ '/out-tsc',
+ '.angular',
+ 'tsconfig.tsbuildinfo',
+ '# node_modules',
+ 'node_modules'
+ ]);
+ }
-/**
- * Creates a tailwind config file.
- */
-async function createTailwindConfig(): Promise {
- await FsUtilities.createFile(
- TAILWIND_CONFIG_FILE_NAME,
- [
+ private async createCspellWords(): Promise {
+ await FsUtilities.createFile('cspell.words.txt', '');
+ }
+
+ private async createEslintConfig(): Promise {
+ await FsUtilities.createFile(ESLINT_CONFIG_FILE_NAME, [
+ `import { configs } from '${NpmPackage.ESLINT_CONFIG_SERVICE_SOFT}';`,
+ '',
'// eslint-disable-next-line jsdoc/require-description',
- '/** @type {import(\'tailwindcss\').Config} */',
- 'module.exports = {',
- '\tcontent: [],',
- '\ttheme: {',
- '\t\textend: {}',
- '\t},',
- '\tplugins: []',
- '};'
- ]
- );
+ '/** @type {import(\'eslint\').Linter.Config} */',
+ 'export default [...configs];'
+ ]);
+ }
+
+ private async createTailwindConfig(): Promise {
+ await FsUtilities.createFile(
+ TAILWIND_CONFIG_FILE_NAME,
+ [
+ '// eslint-disable-next-line jsdoc/require-description',
+ '/** @type {import(\'tailwindcss\').Config} */',
+ 'module.exports = {',
+ '\tcontent: [],',
+ '\ttheme: {',
+ '\t\textend: {}',
+ '\t},',
+ '\tplugins: []',
+ '};'
+ ]
+ );
+ }
}
\ No newline at end of file
diff --git a/src/commands/list/docker-service-status.model.ts b/src/commands/list/docker-service-status.model.ts
new file mode 100644
index 0000000..e6f92af
--- /dev/null
+++ b/src/commands/list/docker-service-status.model.ts
@@ -0,0 +1,14 @@
+/**
+ * Status of a docker service.
+ * Consists of a label and an optional color.
+ */
+export type DockerServiceStatus = {
+ /**
+ * The label of the status.
+ */
+ label: string,
+ /**
+ * The color that should be used to highlight the service, eg. For errors.
+ */
+ color: 'error' | 'success' | undefined
+};
\ No newline at end of file
diff --git a/src/commands/list/list.command.ts b/src/commands/list/list.command.ts
index b7e6c99..96926ac 100644
--- a/src/commands/list/list.command.ts
+++ b/src/commands/list/list.command.ts
@@ -1,120 +1,75 @@
import path from 'path';
-import { getDockerServices } from './get-docker-services.function';
-import { FullyParsedDockerService } from './stringified-docker-service.model';
import { CLI_BASE_COMMAND } from '../../constants';
-import { DockerLabel } from '../../docker';
+import { DockerLabel, FullyParsedDockerService } from '../../docker';
+import { getDockerServices } from '../../docker/get-docker-services.function';
import { ChalkUtilities, CliTable, CliTableUtilities } from '../../encapsulation';
+import { BaseCommand } from '../base-command.model';
+import { DockerServiceStatus } from './docker-service-status.model';
/**
- * Status of a docker service.
- * Consists of a label and an optional color.
+ * Lists the docker services grouped by their monorepo.
*/
-type DockerServiceStatus = {
- /**
- * The label of the status.
- */
- label: string,
- /**
- * The color that should be used to highlight the service, eg. For errors.
- */
- color: 'error' | 'success' | undefined
-};
+export class ListCommand extends BaseCommand {
+ constructor(private readonly all: boolean) {
+ super();
+ }
-/**
- * Runs the list command of the cli.
- * This gives information about any docker services that are managed by Monux.
- * @param all - Whether to list all services or only the ones currently running.
- */
-export async function runList(all: boolean): Promise {
- const services: FullyParsedDockerService[] = await getDockerServices(all);
- if (!services.length) {
+ protected override async run(): Promise {
+ const services: FullyParsedDockerService[] = await getDockerServices(this.all);
+ if (!services.length) {
// eslint-disable-next-line no-console
- console.log('No running services found.');
- if (!all) {
+ console.log('No running services found.');
+ if (!this.all) {
// eslint-disable-next-line no-console, sonar/no-nested-template-literals
- console.log(`You can call ${ChalkUtilities.secondary(`${CLI_BASE_COMMAND} la`)} to also see stopped services.`);
+ console.log(`You can call ${ChalkUtilities.secondary(`${CLI_BASE_COMMAND} la`)} to also see stopped services.`);
+ }
+ // eslint-disable-next-line no-console
+ console.log();
}
- // eslint-disable-next-line no-console
- console.log();
- }
- const grouped: Record = groupByMonorepo(services);
+ const grouped: Record = this.groupByMonorepo(services);
- for (const key in grouped) {
- const services: FullyParsedDockerService[] = grouped[key];
- const data: CliTable = {
- title: key,
- headers: ['Name', 'Type', 'Status', 'environment'],
- rows: services.map(s => {
- const { label, color } = getStatus(s);
- return [
- getName(s, color),
- getType(s, color),
- label,
- getEnv(s.Labels, color)
- ];
- })
- };
-
- CliTableUtilities.logTable(data);
- }
-}
+ for (const key in grouped) {
+ const services: FullyParsedDockerService[] = grouped[key];
+ const data: CliTable = {
+ title: key,
+ headers: ['Name', 'Type', 'Status', 'environment'],
+ rows: services.map(s => {
+ const { label, color } = this.getStatus(s);
+ return [
+ this.getName(s, color),
+ this.getType(s, color),
+ label,
+ this.getEnv(s.Labels, color)
+ ];
+ })
+ };
-/**
- * Gets the status of a docker service.
- * @param s - The service to get the status for.
- * @returns Label and color of the status.
- */
-function getStatus(s: FullyParsedDockerService): DockerServiceStatus {
- if (s.Status.startsWith('Exited')) {
- const exitCode: string = s.Status.split('Exited (')[1].split(')')[0];
- const time: string = s.Status.split(') ')[1];
-
- if (exitCode === '0') {
- return { label: `Stopped (${time})`, color: undefined };
+ CliTableUtilities.logTable(data);
}
- return { label: ChalkUtilities.error(`Crashed (${time}) with ${exitCode}`), color: 'error' };
}
- if (s.Status.startsWith('Up')) {
- const time: string = s.Status.split('Up ')[1];
- return { label: ChalkUtilities.success(`Running (${time})`), color: 'success' };
- }
+ private getStatus(s: FullyParsedDockerService): DockerServiceStatus {
+ if (s.Status.startsWith('Exited')) {
+ const exitCode: string = s.Status.split('Exited (')[1].split(')')[0];
+ const time: string = s.Status.split(') ')[1];
- return { label: s.Status, color: undefined };
-}
-
-/**
- * Gets the name of the given docker service.
- * @param service - The docker service to get the name for.
- * @param color - An optional color to highlight the row.
- * @returns The service name if found, the container name otherwise.
- */
-function getName(service: FullyParsedDockerService, color: 'success' | 'error' | undefined): string {
- const res: string = service.Labels['com.docker.compose.service'] ?? service.Names;
- switch (color) {
- case 'success': {
- return ChalkUtilities.success(res);
- }
- case 'error': {
- return ChalkUtilities.error(res);
+ if (exitCode === '0') {
+ return { label: `Stopped (${time})`, color: undefined };
+ }
+ return { label: ChalkUtilities.error(`Crashed (${time}) with ${exitCode}`), color: 'error' };
}
- case undefined: {
- return res;
+
+ if (s.Status.startsWith('Up')) {
+ const time: string = s.Status.split('Up ')[1];
+ return { label: ChalkUtilities.success(`Running (${time})`), color: 'success' };
}
+
+ return { label: s.Status, color: undefined };
}
-}
-/**
- * Gets the environment that the service was run in.
- * @param labels - The labels of the service to get the environment from.
- * @param color - An optional color to highlight the row.
- * @returns 'dev', 'prod', 'local' or '-', when no environment could be determined.
- */
-function getEnv(labels: Record, color: 'error' | 'success' | undefined): string {
- const dockerFile: string | undefined = labels['com.docker.compose.project.config_files'];
- let res: 'local' | 'prod' | 'dev' | '-' = '-';
- if (!dockerFile) {
+ private getName(service: FullyParsedDockerService, color: 'success' | 'error' | undefined): string {
+ const res: string = service.Labels['com.docker.compose.service'] ?? service.Names;
switch (color) {
case 'success': {
return ChalkUtilities.success(res);
@@ -127,65 +82,72 @@ function getEnv(labels: Record, color: 'error'
}
}
}
- const fileName: string = path.basename(dockerFile);
- if (fileName === 'docker-compose.yaml' || fileName === 'docker-compose.yml') {
- res = 'prod';
- }
- if (fileName.startsWith('dev.')) {
- res = 'dev';
- }
- if (fileName.startsWith('local.')) {
- res = 'local';
- }
- if (res === '-') {
- // eslint-disable-next-line no-console
- console.error(ChalkUtilities.error('Could not determine environment for the docker compose file', fileName));
- }
-
- switch (color) {
- case 'success': {
- return ChalkUtilities.success(res);
+ private getEnv(labels: Record, color: 'error' | 'success' | undefined): string {
+ const dockerFile: string | undefined = labels['com.docker.compose.project.config_files'];
+ let res: 'local' | 'prod' | 'dev' | '-' = '-';
+ if (!dockerFile) {
+ switch (color) {
+ case 'success': {
+ return ChalkUtilities.success(res);
+ }
+ case 'error': {
+ return ChalkUtilities.error(res);
+ }
+ case undefined: {
+ return res;
+ }
+ }
}
- case 'error': {
- return ChalkUtilities.error(res);
+ const fileName: string = path.basename(dockerFile);
+ if (fileName === 'docker-compose.yaml' || fileName === 'docker-compose.yml') {
+ res = 'prod';
}
- case undefined: {
- return res;
+ if (fileName.startsWith('dev.')) {
+ res = 'dev';
+ }
+ if (fileName.startsWith('local.')) {
+ res = 'local';
}
- }
-}
-
-/**
- * Groups the given docker services by the monorepo that they belong to.
- * @param services - The services to group.
- * @returns A record with the name of the monorepo and the docker services that belong to them.
- */
-function groupByMonorepo(services: FullyParsedDockerService[]): Record {
- return services.reduce>((acc, svc) => {
- const repo: string = svc.config.name;
- acc[repo] = acc[repo] ?? [];
- acc[repo].push(svc);
- return acc;
- }, {});
-}
-/**
- * Gets the type (image) of the service.
- * @param s - The service to get the type of.
- * @param color - An optional color to highlight the row.
- * @returns The name of the image used by that service.
- */
-function getType(s: FullyParsedDockerService, color: 'error' | 'success' | undefined): string {
- switch (color) {
- case 'success': {
- return ChalkUtilities.success(s.Image);
+ if (res === '-') {
+ // eslint-disable-next-line no-console
+ console.error(ChalkUtilities.error('Could not determine environment for the docker compose file', fileName));
}
- case 'error': {
- return ChalkUtilities.error(s.Image);
+
+ switch (color) {
+ case 'success': {
+ return ChalkUtilities.success(res);
+ }
+ case 'error': {
+ return ChalkUtilities.error(res);
+ }
+ case undefined: {
+ return res;
+ }
}
- case undefined: {
- return s.Image;
+ }
+
+ private groupByMonorepo(services: FullyParsedDockerService[]): Record {
+ return services.reduce>((acc, svc) => {
+ const repo: string = svc.config.name;
+ acc[repo] = acc[repo] ?? [];
+ acc[repo].push(svc);
+ return acc;
+ }, {});
+ }
+
+ private getType(s: FullyParsedDockerService, color: 'error' | 'success' | undefined): string {
+ switch (color) {
+ case 'success': {
+ return ChalkUtilities.success(s.Image);
+ }
+ case 'error': {
+ return ChalkUtilities.error(s.Image);
+ }
+ case undefined: {
+ return s.Image;
+ }
}
}
}
\ No newline at end of file
diff --git a/src/commands/prepare/index.ts b/src/commands/prepare/index.ts
index c717686..a19257b 100644
--- a/src/commands/prepare/index.ts
+++ b/src/commands/prepare/index.ts
@@ -1 +1,3 @@
-export * from './prepare.command';
\ No newline at end of file
+/* eslint-disable jsdoc/require-jsdoc */
+export * from './prepare.command';
+export { prepareConfigQuestions } from './prepare-config.model';
\ No newline at end of file
diff --git a/src/commands/prepare/prepare-config.model.ts b/src/commands/prepare/prepare-config.model.ts
new file mode 100644
index 0000000..3975cd1
--- /dev/null
+++ b/src/commands/prepare/prepare-config.model.ts
@@ -0,0 +1,34 @@
+import { DockerComposeFileName } from '../../constants';
+import { QuestionsFor } from '../../encapsulation';
+
+/**
+ * Configuration for the prepare command.
+ */
+export type PrepareConfig = {
+ /**
+ * The name of the docker file/environment to use.
+ */
+ fileName: DockerComposeFileName,
+ /**
+ * The root directory of the monorepo to prepare.
+ */
+ rootDir: string
+};
+
+// eslint-disable-next-line jsdoc/require-jsdoc
+const choices: { name: string, value: DockerComposeFileName }[] = [
+ { name: 'dev', value: 'dev.docker-compose.yaml' },
+ { name: 'local', value: 'local.docker-compose.yaml' },
+ { name: 'prod', value: 'docker-compose.yaml' }
+];
+
+/**
+ * Questions for getting the environment to run in.
+ */
+export const prepareConfigQuestions: QuestionsFor> = {
+ fileName: {
+ message: 'env',
+ type: 'select',
+ choices: choices
+ }
+};
\ No newline at end of file
diff --git a/src/commands/prepare/prepare.command.ts b/src/commands/prepare/prepare.command.ts
index ca1aa5a..615906d 100644
--- a/src/commands/prepare/prepare.command.ts
+++ b/src/commands/prepare/prepare.command.ts
@@ -1,56 +1,40 @@
/* eslint-disable jsdoc/require-jsdoc */
+import { PrepareConfig, prepareConfigQuestions } from './prepare-config.model';
import { DockerComposeFileName } from '../../constants';
import { DbUtilities } from '../../db';
-import { InquirerUtilities, QuestionsFor } from '../../encapsulation';
+import { InquirerUtilities } from '../../encapsulation';
import { EnvUtilities, EnvValidationErrorMessage } from '../../env';
import { RobotsUtilities } from '../../robots';
import { KeyValue } from '../../types';
-import { exitWithError } from '../exit-with-error.function';
+import { exitWithError, getPath } from '../../utilities';
+import { BaseCommand } from '../base-command.model';
-export type PrepareConfig = {
- fileName: DockerComposeFileName
-};
+export class PrepareCommand extends BaseCommand {
+ protected override readonly insideWorkspace: boolean = true;
-const choices: { name: string, value: DockerComposeFileName }[] = [
- { name: 'dev', value: 'dev.docker-compose.yaml' },
- { name: 'local', value: 'local.docker-compose.yaml' },
- { name: 'prod', value: 'docker-compose.yaml' }
-];
-const prepareConfigQuestions: QuestionsFor = {
- fileName: {
- message: 'env',
- type: 'select',
- choices: choices
+ protected override async run(input: PrepareConfig): Promise {
+ await this.buildEnv(input.fileName, input.rootDir);
+ await RobotsUtilities.createRobotsTxtFiles(input.fileName, input.rootDir);
+ await DbUtilities.createInitFiles(input.fileName, input.rootDir);
}
-};
-/**
- * Prepares everything that is needed to be done for deploying the monorepo.
- * @param fileName - The docker compose file get the variables for.
- */
-export async function runPrepare(fileName: DockerComposeFileName | undefined): Promise {
- fileName ??= (await InquirerUtilities.prompt(prepareConfigQuestions)).fileName;
- await buildEnv(fileName);
- await buildRobotsTxtFiles(fileName);
- await buildDbInitFiles(fileName);
-}
+ protected override async resolveInput(): Promise {
+ const fileName: DockerComposeFileName = (await InquirerUtilities.prompt(prepareConfigQuestions)).fileName;
-async function buildDbInitFiles(fileName: DockerComposeFileName): Promise {
- await DbUtilities.createInitFiles(fileName);
-}
-
-async function buildEnv(fileName: DockerComposeFileName): Promise {
- const validationErrors: KeyValue[] = await EnvUtilities.validate();
- if (validationErrors.length) {
- exitWithError(
- 'Error when validating the .env file:\n'
- + validationErrors.map(e => `\t${e.key}: ${e.value}`).join('\n')
- );
- return;
+ return {
+ fileName,
+ rootDir: getPath('.')
+ };
}
- await EnvUtilities.buildEnvironmentFiles(fileName);
-}
-async function buildRobotsTxtFiles(fileName: DockerComposeFileName): Promise {
- await RobotsUtilities.createRobotsTxtFiles(fileName);
+ private async buildEnv(fileName: DockerComposeFileName, rootDir: string): Promise {
+ const validationErrors: KeyValue[] = await EnvUtilities.validate(rootDir);
+ if (validationErrors.length) {
+ exitWithError(
+ 'Error when validating the .env file:\n'
+ + validationErrors.map(e => `\t${e.key}: ${e.value}`).join('\n')
+ );
+ }
+ await EnvUtilities.buildEnvironmentFiles(fileName, rootDir);
+ }
}
\ No newline at end of file
diff --git a/src/commands/resolve-command.function.ts b/src/commands/resolve-command.function.ts
new file mode 100644
index 0000000..f9c423f
--- /dev/null
+++ b/src/commands/resolve-command.function.ts
@@ -0,0 +1,25 @@
+import { exitWithError } from '../utilities';
+import { Command } from './command.enum';
+import { isCommand } from './is-command.function';
+
+/**
+ * Resolves the command from the given args.
+ * @param args - The args provided by the cli.
+ * @returns The resolved command.
+ */
+export function resolveCommand(args: string[]): Command | 'run' {
+ if (args.length < 1) {
+ exitWithError('Error: You need to specify a command.');
+ }
+
+ const command: string = args[0];
+
+ if (!isCommand(command)) {
+ if (args.length === 1) {
+ exitWithError(`Error: Unknown command ${command}.`);
+ }
+ return 'run';
+ }
+
+ return command;
+}
\ No newline at end of file
diff --git a/src/commands/run-all/run-all.command.ts b/src/commands/run-all/run-all.command.ts
index 2746647..67722b4 100644
--- a/src/commands/run-all/run-all.command.ts
+++ b/src/commands/run-all/run-all.command.ts
@@ -1,9 +1,34 @@
import { NpmUtilities } from '../../npm';
+import { exitWithError } from '../../utilities';
+import { BaseCommand } from '../base-command.model';
/**
- * Runs the run-many cli command.
- * @param npmScript - The npm script to run.
+ * Configuration for the run-all command.
*/
-export function runRunAll(npmScript: string): void {
- NpmUtilities.runAll(npmScript);
+type RunAllConfiguration = {
+ /**
+ * The npm script to run.
+ */
+ npmScript: string
+};
+
+/**
+ * Runs the given command in all projects of the current monorepo.
+ */
+export class RunAllCommand extends BaseCommand {
+
+ protected override run(config: RunAllConfiguration): void {
+ NpmUtilities.runAll(config.npmScript);
+ }
+
+ protected override resolveInput(args: string[]): RunAllConfiguration {
+ return { npmScript: args[1] };
+ }
+
+ protected override async validate(args: string[]): Promise {
+ if (args.length === 1) {
+ exitWithError('Error: No npm script specified to run in all projects.');
+ }
+ await this.validateInsideWorkspace();
+ }
}
\ No newline at end of file
diff --git a/src/commands/run/run-configuration.model.ts b/src/commands/run/run-configuration.model.ts
new file mode 100644
index 0000000..baa14d6
--- /dev/null
+++ b/src/commands/run/run-configuration.model.ts
@@ -0,0 +1,17 @@
+/**
+ * Configuration for the run command.
+ */
+export type RunConfiguration = {
+ /**
+ * Whether or not the command to run is native (like "npm install") or not (like "npm run {myScript}").
+ */
+ isNativeCommand: boolean,
+ /**
+ * The name of the Monux project.
+ */
+ projectName: string,
+ /**
+ * The commands as a single concatenated string.
+ */
+ commands: string
+};
\ No newline at end of file
diff --git a/src/commands/run/run.command.ts b/src/commands/run/run.command.ts
index 0a82156..6e5a7bb 100644
--- a/src/commands/run/run.command.ts
+++ b/src/commands/run/run.command.ts
@@ -1,22 +1,56 @@
-import { CPUtilities } from '../../encapsulation';
-import { NativeNpmCommands, NpmUtilities } from '../../npm';
+import { Dirent } from 'fs';
+
+import { CPUtilities, FsUtilities } from '../../encapsulation';
+import { NativeNpmCommands, NpmUtilities, PackageJson } from '../../npm';
+import { exitWithError, getPath } from '../../utilities';
import { WorkspaceProject, WorkspaceUtilities } from '../../workspace';
+import { BaseCommand } from '../base-command.model';
+import { RunConfiguration } from './run-configuration.model';
+import { PACKAGE_JSON_FILE_NAME } from '../../constants';
/**
- * Runs the run cli command.
- * @param args - The passed cli commands.
+ * Runs either a native npm command or a script from the package.json.
*/
-export async function runRun(...args: string[]): Promise {
- const projectName: string = args[0];
- const npmScript: string = args[1];
- const nativeCommand: boolean = Object.values(NativeNpmCommands).includes(npmScript as NativeNpmCommands);
-
- const commands: string = args.slice(1).join(' ');
- if (!nativeCommand) {
- await NpmUtilities.run(projectName, commands);
- return;
+export class RunCommand extends BaseCommand {
+ protected override insideWorkspace: boolean = true;
+ protected override maxLength: undefined = undefined;
+
+ protected override async run(config: RunConfiguration): Promise {
+ if (!config.isNativeCommand) {
+ await NpmUtilities.run(config.projectName, config.commands);
+ return;
+ }
+ const project: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(config.projectName, getPath('.'));
+ CPUtilities.execSync(`npm ${config.commands} --workspace=${project.npmWorkspaceString}`);
}
- const project: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(projectName);
- CPUtilities.execSync(`npm ${commands} --workspace=${project.npmWorkspaceString}`);
+ protected override resolveInput(args: string[]): RunConfiguration {
+ return {
+ projectName: args[0],
+ isNativeCommand: Object.values(NativeNpmCommands).includes(args[1] as NativeNpmCommands),
+ commands: args.slice(1).join(' ')
+ };
+ }
+
+ protected override async validate(args: string[]): Promise {
+ const project: string = args[0];
+ await this.validateInsideWorkspace();
+ const foundProject: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(project, getPath('.'));
+
+ const packageJson: Dirent | undefined = (await FsUtilities.readdir(foundProject.path)).find(f => f.name === PACKAGE_JSON_FILE_NAME);
+ if (!packageJson) {
+ exitWithError(`The provided project "${project}" does not contain a ${PACKAGE_JSON_FILE_NAME} file`);
+ }
+
+ if (Object.values(NativeNpmCommands).includes(args[1] as NativeNpmCommands)) {
+ return;
+ }
+
+ const npmScript: string = args[1];
+ const file: PackageJson = await FsUtilities.parseFileAs(getPath(packageJson.parentPath, packageJson.name));
+
+ if (!Object.keys(file.scripts).includes(npmScript)) {
+ exitWithError(`The project "${project}" does not contain the provided script "${npmScript}"`);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/commands/up-dev/index.ts b/src/commands/up-dev/index.ts
deleted file mode 100644
index b241823..0000000
--- a/src/commands/up-dev/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './up-dev.command';
\ No newline at end of file
diff --git a/src/commands/up-dev/up-dev.command.ts b/src/commands/up-dev/up-dev.command.ts
deleted file mode 100644
index a6a4082..0000000
--- a/src/commands/up-dev/up-dev.command.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { DEV_DOCKER_COMPOSE_FILE_NAME } from '../../constants';
-import { CPUtilities } from '../../encapsulation';
-import { runPrepare } from '../prepare';
-
-/**
- * Starts up the docker compose service.
- */
-export async function runUpDev(): Promise {
- await runPrepare('dev.docker-compose.yaml');
- CPUtilities.execSync(`docker compose -f ${DEV_DOCKER_COMPOSE_FILE_NAME} up --build -d`);
-}
\ No newline at end of file
diff --git a/src/commands/up-local/index.ts b/src/commands/up-local/index.ts
deleted file mode 100644
index 71ba2f0..0000000
--- a/src/commands/up-local/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './up-local.command';
\ No newline at end of file
diff --git a/src/commands/up-local/up-local.command.ts b/src/commands/up-local/up-local.command.ts
deleted file mode 100644
index 78d07c2..0000000
--- a/src/commands/up-local/up-local.command.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { LOCAL_DOCKER_COMPOSE_FILE_NAME } from '../../constants';
-import { CPUtilities } from '../../encapsulation';
-import { runPrepare } from '../prepare';
-
-/**
- * Starts up the docker compose service.
- */
-export async function runUpLocal(): Promise {
- await runPrepare('local.docker-compose.yaml');
- CPUtilities.execSync(`docker compose -f ${LOCAL_DOCKER_COMPOSE_FILE_NAME} up --build`);
-}
\ No newline at end of file
diff --git a/src/commands/up/up-configuration.model.ts b/src/commands/up/up-configuration.model.ts
new file mode 100644
index 0000000..dd22641
--- /dev/null
+++ b/src/commands/up/up-configuration.model.ts
@@ -0,0 +1,24 @@
+import { DockerComposeFileName } from '../../constants';
+
+/**
+ * Configuration for the up command.
+ */
+export type UpConfiguration = {
+ /**
+ * Which docker compose file to use.
+ * Is relevant to determine the environment t.
+ */
+ fileName: DockerComposeFileName,
+ /**
+ * This needs to be optional, to run the command locally before the services are registered with docker.
+ */
+ dockerFilePath: string | undefined,
+ /**
+ * The name of the monorepo project.
+ */
+ projectName: string,
+ /**
+ * The root dir of the monorepo project.
+ */
+ rootDir: string
+};
\ No newline at end of file
diff --git a/src/commands/up/up.command.ts b/src/commands/up/up.command.ts
index 50d7f47..9ea8563 100644
--- a/src/commands/up/up.command.ts
+++ b/src/commands/up/up.command.ts
@@ -1,10 +1,62 @@
-import { CPUtilities } from '../../encapsulation';
-import { runPrepare } from '../prepare';
+import { dirname } from 'path';
+
+import { DockerComposeFileName } from '../../constants';
+import { FullyParsedDockerService, getDockerServices } from '../../docker';
+import { CPUtilities, InquirerUtilities } from '../../encapsulation';
+import { exitWithError, getPath } from '../../utilities';
+import { WorkspaceUtilities } from '../../workspace';
+import { BaseCommand } from '../base-command.model';
+import { PrepareCommand, prepareConfigQuestions } from '../prepare';
+import { UpConfiguration } from './up-configuration.model';
/**
- * Starts up the docker compose service.
+ * Starts monorepo services.
+ * Also calls the prepare command.
*/
-export async function runUp(): Promise {
- await runPrepare('docker-compose.yaml');
- CPUtilities.execSync('docker compose up --build -d');
+export class UpCommand extends BaseCommand {
+ protected override readonly insideWorkspace: boolean = true;
+ protected override readonly maxLength: number = 2;
+
+ protected override async run(input: UpConfiguration): Promise {
+ await new PrepareCommand()['run'](input);
+ CPUtilities.execSync(`docker compose -f ${input.dockerFilePath ?? input.fileName} -p ${input.projectName} up --build -d`);
+ }
+
+ protected override async validate(args: string[]): Promise {
+ this.validateMaxLength(args);
+ if (args.length === 1) {
+ await this.validateInsideWorkspace();
+ }
+ }
+
+ protected override async resolveInput(args: string[]): Promise {
+ const projectName: string = args.length === 1 ? (await WorkspaceUtilities.getConfigOrFail()).name : args[1];
+ const dockerServices: FullyParsedDockerService[] = await getDockerServices(true);
+ const service: FullyParsedDockerService | undefined = dockerServices.find(d => d.config.name === projectName);
+
+ let rootDir: string | undefined = service?.Labels['com.docker.compose.project.working_dir'];
+ if (args.length === 1) {
+ rootDir = getPath('.');
+ }
+ if (rootDir === undefined) {
+ exitWithError(`Error: Could not find root of "${projectName}"`);
+ }
+
+ const fileName: DockerComposeFileName = (await InquirerUtilities.prompt(prepareConfigQuestions)).fileName;
+ const dockerFilePath: string | undefined = this.getDockerFilePath(service, fileName);
+ return {
+ fileName,
+ projectName,
+ dockerFilePath,
+ rootDir
+ };
+ }
+
+ private getDockerFilePath(service: FullyParsedDockerService | undefined, fileName: DockerComposeFileName): string | undefined {
+ const dockerFilePath: string | undefined = service?.Labels['com.docker.compose.project.config_files'];
+ if (dockerFilePath === undefined) {
+ return undefined;
+ }
+ return getPath(dirname(dockerFilePath), fileName);
+ }
}
\ No newline at end of file
diff --git a/src/commands/validate-input.function.ts b/src/commands/validate-input.function.ts
deleted file mode 100644
index d8f14d8..0000000
--- a/src/commands/validate-input.function.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import { Dirent } from 'fs';
-
-import { FsUtilities } from '../encapsulation';
-import { WorkspaceConfig, WorkspaceProject, WorkspaceUtilities } from '../workspace';
-import { Command } from './command.enum';
-import { exitWithError } from './exit-with-error.function';
-import { PACKAGE_JSON_FILE_NAME } from '../constants';
-import { NativeNpmCommands, PackageJson } from '../npm';
-import { getPath } from '../utilities';
-import { isCommand } from './is-command.function';
-
-const TOO_MANY_ARGUMENTS_ERROR_MESSAGE: string = 'Error parsing the command: Too many arguments.';
-
-/**
- * Validates the user input.
- * @param args - The arguments provided by the user for the cli-command.
- */
-export async function validateInput(args: string[]): Promise {
- if (args.length < 1) {
- exitWithError('Error: You need to specify a command.');
- return;
- }
-
- const command: string = args[0];
-
- if (!isCommand(command)) {
- if (args.length === 1) {
- exitWithError(`Error: Unknown command ${command}.`);
- }
- await validateRunInput(...args);
- return;
- }
-
- switch (command) {
- case Command.HELP:
- case Command.H:
- case Command.VERSION:
- case Command.V:
- case Command.INIT:
- case Command.I:
- case Command.LIST:
- case Command.LS:
- case Command.LIST_ALL:
- case Command.LA: {
- validateMaxLength(args.length, 1);
- return;
- }
- case Command.A:
- case Command.ADD:
- case Command.PREPARE:
- case Command.P:
- case Command.UP:
- case Command.U:
- case Command.DOWN:
- case Command.D:
- case Command.UP_DEV:
- case Command.UD:
- case Command.DOWN_DEV:
- case Command.DD:
- case Command.UP_LOCAL:
- case Command.UL:
- case Command.DOWN_LOCAL:
- case Command.DL:
- case Command.GENERATE_PAGE:
- case Command.GP: {
- await validateInsideWorkspace();
- validateMaxLength(args.length, 1);
- return;
- }
- case Command.RUN_MANY:
- case Command.RUN_ALL:
- case Command.RA: {
- if (args.length === 1) {
- exitWithError('Error: No npm script specified to run in all projects.');
- }
- await validateInsideWorkspace();
- return;
- }
- }
-}
-
-// eslint-disable-next-line jsdoc/require-jsdoc
-async function validateRunInput(...args: string[]): Promise {
- const project: string = args[0];
- await validateInsideWorkspace();
- const foundProject: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(project);
-
- const packageJson: Dirent | undefined = (await FsUtilities.readdir(foundProject.path)).find(f => f.name === PACKAGE_JSON_FILE_NAME);
- if (!packageJson) {
- exitWithError(`The provided project "${project}" does not contain a ${PACKAGE_JSON_FILE_NAME} file`);
- return;
- }
-
- if (Object.values(NativeNpmCommands).includes(args[1] as NativeNpmCommands)) {
- return;
- }
-
- const npmScript: string = args[1];
- const file: PackageJson = await FsUtilities.parseFileAs(getPath(packageJson.parentPath, packageJson.name));
-
- if (!Object.keys(file.scripts).includes(npmScript)) {
- exitWithError(`The project "${project}" does not contain the provided script "${npmScript}"`);
- }
-}
-
-// eslint-disable-next-line jsdoc/require-jsdoc
-async function validateInsideWorkspace(): Promise {
- const config: WorkspaceConfig | undefined = await WorkspaceUtilities.getConfig();
- // eslint-disable-next-line typescript/strict-boolean-expressions
- if (!config?.isWorkspace) {
- exitWithError('This command can only be run inside a workspace');
- }
-}
-
-// eslint-disable-next-line jsdoc/require-jsdoc
-function validateMaxLength(value: number, maxLength: number): void {
- if (value > maxLength) {
- exitWithError(TOO_MANY_ARGUMENTS_ERROR_MESSAGE);
- }
-}
\ No newline at end of file
diff --git a/src/commands/version/version.command.ts b/src/commands/version/version.command.ts
index a2f7c89..e0fe5d2 100644
--- a/src/commands/version/version.command.ts
+++ b/src/commands/version/version.command.ts
@@ -2,19 +2,21 @@
import { PACKAGE_JSON_FILE_NAME } from '../../constants';
import { ChalkUtilities, FsUtilities } from '../../encapsulation';
import { PackageJson } from '../../npm';
-import { getPath } from '../../utilities';
-import { exitWithError } from '../exit-with-error.function';
+import { getPath, exitWithError } from '../../utilities';
+import { BaseCommand } from '../base-command.model';
/**
- * Runs the version cli command.
+ * Prints out the currently used version of Monux.
*/
-export async function runVersion(): Promise {
- const packageJsonPath: string = getPath(__dirname, '..', '..', '..', PACKAGE_JSON_FILE_NAME);
- const pkg: PackageJson = await FsUtilities.parseFileAs(packageJsonPath);
- if (!pkg.version) {
- exitWithError('Could not determine the currently running version of Monux');
- return;
+export class VersionCommand extends BaseCommand {
+ protected override async run(): Promise {
+ const packageJsonPath: string = getPath(__dirname, '..', '..', '..', PACKAGE_JSON_FILE_NAME);
+ const pkg: PackageJson = await FsUtilities.parseFileAs(packageJsonPath);
+ if (!pkg.version) {
+ exitWithError('Could not determine the currently running version of Monux');
+ return;
+ }
+ console.log(ChalkUtilities.boldUnderline('Version:'));
+ console.log(ChalkUtilities.secondary(pkg.version));
}
- console.log(ChalkUtilities.boldUnderline('Version:'));
- console.log(ChalkUtilities.secondary(pkg.version));
}
\ No newline at end of file
diff --git a/src/db/db-utilities.test.ts b/src/db/db-utilities.test.ts
index e2e8f0d..9e74266 100644
--- a/src/db/db-utilities.test.ts
+++ b/src/db/db-utilities.test.ts
@@ -167,12 +167,12 @@ describe('DbUtilities', () => {
}
);
- await DbUtilities.createInitFiles('dev.docker-compose.yaml');
+ await DbUtilities.createInitFiles('dev.docker-compose.yaml', getPath('.'));
const initShContent: string[] = await FsUtilities.readFileLines(getPath(mockConstants.PROJECT_DIR, DATABASES_DIRECTORY_NAME, 'postgres-db', 'init', '0.sh'));
- const postgresPassword: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.dbPassword(POSTGRES_SERVICE_NAME, POSTGRES_DATABASE_NAME), 'dev.docker-compose.yaml');
+ const postgresPassword: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.dbPassword(POSTGRES_SERVICE_NAME, POSTGRES_DATABASE_NAME), 'dev.docker-compose.yaml', getPath('.'));
const initSqlContent: string[] = await FsUtilities.readFileLines(getPath(mockConstants.PROJECT_DIR, DATABASES_DIRECTORY_NAME, 'maria-db', 'init', '0.sql'));
- const mariadbPassword: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.dbPassword(MARIADB_SERVICE_NAME, MARIADB_DATABASE_NAME), 'dev.docker-compose.yaml');
+ const mariadbPassword: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.dbPassword(MARIADB_SERVICE_NAME, MARIADB_DATABASE_NAME), 'dev.docker-compose.yaml', getPath('.'));
expect(initShContent).toEqual([
'#!/bin/bash',
diff --git a/src/db/db.utilities.ts b/src/db/db.utilities.ts
index 4ea8cd7..e5a734f 100644
--- a/src/db/db.utilities.ts
+++ b/src/db/db.utilities.ts
@@ -58,17 +58,18 @@ export abstract class DbUtilities {
/**
* Creates the bash init files for setting up default databases and users.
* @param fileName - The docker compose file get the variables for.
+ * @param rootDir - The directory of the Monux monorepo.
*/
- static async createInitFiles(fileName: DockerComposeFileName): Promise {
- const dbs: ComposeService[] = await this.getAvailableDatabases();
+ static async createInitFiles(fileName: DockerComposeFileName, rootDir: string): Promise {
+ const dbs: ComposeService[] = await this.getAvailableDatabases(rootDir);
for (const db of dbs) {
- const configs: DbInitConfig[] = await this.getInitConfigsForDb(db.name);
+ const configs: DbInitConfig[] = await this.getInitConfigsForDb(db.name, rootDir);
for (let i: number = 0; i < configs.length; i++) {
- const initFileSh: string = getPath(DATABASES_DIRECTORY_NAME, toKebabCase(db.name), 'init', `${i}.sh`);
- const initFileSql: string = getPath(DATABASES_DIRECTORY_NAME, toKebabCase(db.name), 'init', `${i}.sql`);
+ const initFileSh: string = getPath(rootDir, DATABASES_DIRECTORY_NAME, toKebabCase(db.name), 'init', `${i}.sh`);
+ const initFileSql: string = getPath(rootDir, DATABASES_DIRECTORY_NAME, toKebabCase(db.name), 'init', `${i}.sql`);
await FsUtilities.rm(initFileSh);
await FsUtilities.rm(initFileSql);
- await this.createInitFile(configs[i], initFileSh, initFileSql, fileName);
+ await this.createInitFile(configs[i], initFileSh, initFileSql, fileName, rootDir);
}
}
}
@@ -77,11 +78,12 @@ export abstract class DbUtilities {
config: DbInitConfig,
initFileSh: string,
initFileSql: string,
- fileName: DockerComposeFileName
+ fileName: DockerComposeFileName,
+ rootDir: string
): Promise {
- const dbName: string = await EnvUtilities.getEnvVariable(config.nameEnvVariable, fileName);
- const dbUser: string = await EnvUtilities.getEnvVariable(config.userEnvVariable, fileName);
- const dbPassword: string = await EnvUtilities.getEnvVariable(config.passwordEnvVariable, fileName);
+ const dbName: string = await EnvUtilities.getEnvVariable(config.nameEnvVariable, fileName, rootDir);
+ const dbUser: string = await EnvUtilities.getEnvVariable(config.userEnvVariable, fileName, rootDir);
+ const dbPassword: string = await EnvUtilities.getEnvVariable(config.passwordEnvVariable, fileName, rootDir);
switch (config.type) {
case DbType.POSTGRES: {
@@ -115,8 +117,8 @@ export abstract class DbUtilities {
}
}
- private static async getInitConfigsForDb(db: string): Promise {
- const dbFolder: Dirent[] = await FsUtilities.readdir(getPath(DATABASES_DIRECTORY_NAME, toKebabCase(db)));
+ private static async getInitConfigsForDb(db: string, rootDir: string): Promise {
+ const dbFolder: Dirent[] = await FsUtilities.readdir(getPath(rootDir, DATABASES_DIRECTORY_NAME, toKebabCase(db)));
const configFilePaths: string[] = dbFolder.filter(e => e.name.endsWith('.json')).map(e => getPath(e.parentPath, e.name));
return await Promise.all(configFilePaths.map(async p => await FsUtilities.parseFileAs(p)));
}
@@ -126,14 +128,15 @@ export abstract class DbUtilities {
* Prompts the user for selecting an existing one or creates a new.
* @param projectName - The name of the project to configure this database for.
* @param dbType - The type of database to configure. If omitted, the user is prompted for input.
+ * @param rootDir - The directory of the Monux monorepo.
* @returns The name of the database service.
*/
- static async configureDb(projectName: string, dbType?: DbType): Promise {
+ static async configureDb(projectName: string, dbType: DbType | undefined, rootDir: string): Promise {
const baseDbQuestions: QuestionsFor = {
dbServiceName: {
type: 'select',
message: 'Database compose service',
- choices: ['NEW', ...(await this.getAvailableDatabases()).map(db => db.name)],
+ choices: ['NEW', ...(await this.getAvailableDatabases(rootDir)).map(db => db.name)],
default: 'NEW'
},
databaseName: {
@@ -146,7 +149,7 @@ export abstract class DbUtilities {
if (baseDbConfig.dbServiceName !== 'NEW') {
await this.addDbInitConfig(baseDbConfig.dbServiceName, {
- type: await this.getDbTypeForService(baseDbConfig.dbServiceName),
+ type: await this.getDbTypeForService(baseDbConfig.dbServiceName, rootDir),
nameEnvVariable: DefaultEnvKeys.dbName(baseDbConfig.dbServiceName, baseDbConfig.databaseName),
passwordEnvVariable: DefaultEnvKeys.dbPassword(baseDbConfig.dbServiceName, baseDbConfig.databaseName),
userEnvVariable: DefaultEnvKeys.dbUser(baseDbConfig.dbServiceName, baseDbConfig.databaseName)
@@ -189,8 +192,8 @@ export abstract class DbUtilities {
}
}
- private static async getDbTypeForService(serviceName: string): Promise {
- const foundServices: ComposeService[] = (await DbUtilities.getAvailableDatabases()).filter(s => s.name === serviceName);
+ private static async getDbTypeForService(serviceName: string, rootDir: string): Promise {
+ const foundServices: ComposeService[] = (await this.getAvailableDatabases(rootDir)).filter(s => s.name === serviceName);
if (!foundServices.length) {
throw new Error(`Could not determine db type for service "${serviceName}": Not Found.`);
}
@@ -224,8 +227,8 @@ export abstract class DbUtilities {
}
}
- private static async getAvailableDatabases(): Promise {
- const services: ComposeService[] = await DockerUtilities.getComposeServices();
+ private static async getAvailableDatabases(rootDir: string): Promise {
+ const services: ComposeService[] = await DockerUtilities.getComposeServices(rootDir);
return services.filter(s => this.isDatabaseService(s));
}
diff --git a/src/docker/docker.utilities.ts b/src/docker/docker.utilities.ts
index 96e39ca..79f72dd 100644
--- a/src/docker/docker.utilities.ts
+++ b/src/docker/docker.utilities.ts
@@ -318,10 +318,11 @@ export abstract class DockerUtilities {
/**
* Gets all services from the docker compose file.
+ * @param rootDir - The directory of the Monux monorepo.
* @returns The parsed services.
*/
- static async getComposeServices(): Promise {
- const composePath: string = getPath(PROD_DOCKER_COMPOSE_FILE_NAME);
+ static async getComposeServices(rootDir: string): Promise {
+ const composePath: string = getPath(rootDir, PROD_DOCKER_COMPOSE_FILE_NAME);
const definition: ComposeDefinition = await this.yamlToComposeDefinition(composePath);
return definition.services;
}
diff --git a/src/commands/list/get-docker-services.function.ts b/src/docker/get-docker-services.function.ts
similarity index 84%
rename from src/commands/list/get-docker-services.function.ts
rename to src/docker/get-docker-services.function.ts
index ccc1db5..4f00bbb 100644
--- a/src/commands/list/get-docker-services.function.ts
+++ b/src/docker/get-docker-services.function.ts
@@ -1,11 +1,11 @@
import { execSync } from 'child_process';
-import { FullyParsedDockerService, StringifiedDockerServiceWithParsedLabels, isFullyParsedDockerService, StringifiedDockerService } from './stringified-docker-service.model';
-import { WORKSPACE_FILE_NAME } from '../../constants';
-import { DockerLabel } from '../../docker';
-import { JsonUtilities } from '../../encapsulation';
-import { getPath } from '../../utilities';
-import { WorkspaceConfig, WorkspaceUtilities } from '../../workspace';
+import { DockerLabel } from '.';
+import { WORKSPACE_FILE_NAME } from '../constants';
+import { JsonUtilities } from '../encapsulation';
+import { getPath } from '../utilities';
+import { WorkspaceConfig, WorkspaceUtilities } from '../workspace';
+import { FullyParsedDockerService, isFullyParsedDockerService, StringifiedDockerService, StringifiedDockerServiceWithParsedLabels } from './stringified-docker-service.model';
/**
* Gets all docker services that come from a project with an mx.workspace.json.
diff --git a/src/docker/index.ts b/src/docker/index.ts
index f1af289..165a80e 100644
--- a/src/docker/index.ts
+++ b/src/docker/index.ts
@@ -1,3 +1,5 @@
export * from './docker.utilities';
export * from './compose-file.model';
-export * from './docker-labels.enum';
\ No newline at end of file
+export * from './docker-labels.enum';
+export * from './get-docker-services.function';
+export * from './stringified-docker-service.model';
\ No newline at end of file
diff --git a/src/commands/list/stringified-docker-service.model.ts b/src/docker/stringified-docker-service.model.ts
similarity index 96%
rename from src/commands/list/stringified-docker-service.model.ts
rename to src/docker/stringified-docker-service.model.ts
index b069093..e63e0ea 100644
--- a/src/commands/list/stringified-docker-service.model.ts
+++ b/src/docker/stringified-docker-service.model.ts
@@ -1,5 +1,5 @@
-import { DockerLabel } from '../../docker';
-import { WorkspaceConfig } from '../../workspace';
+import { WorkspaceConfig } from '../workspace';
+import { DockerLabel } from './docker-labels.enum';
/**
* Represents a Docker container's information as returned by
diff --git a/src/encapsulation/cp.utilities.ts b/src/encapsulation/cp.utilities.ts
index 404cc15..22d478c 100644
--- a/src/encapsulation/cp.utilities.ts
+++ b/src/encapsulation/cp.utilities.ts
@@ -1,7 +1,7 @@
import { execSync, ExecSyncOptions } from 'child_process';
-import { exitWithInterrupt, isErrorWithSignal, isExitPromptError } from '../commands';
import { ChalkUtilities } from './chalk.utilities';
+import { exitWithInterrupt, isErrorWithSignal, isExitPromptError } from '../utilities';
/**
* Encapsulates functionality of the child_process package.
diff --git a/src/encapsulation/inquirer.utilities.ts b/src/encapsulation/inquirer.utilities.ts
index 50f87e7..8b30e59 100644
--- a/src/encapsulation/inquirer.utilities.ts
+++ b/src/encapsulation/inquirer.utilities.ts
@@ -1,7 +1,7 @@
import inquirer from 'inquirer';
import { BuiltInQuestion, Answers } from 'inquirer/dist/cjs/types/types';
-import { exitWithInterrupt, isErrorWithSignal, isExitPromptError } from '../commands';
+import { exitWithInterrupt, isErrorWithSignal, isExitPromptError } from '../utilities';
/**
* Inquirer Questions to get the generic object result.
diff --git a/src/env/env-utilities.test.ts b/src/env/env-utilities.test.ts
index 004d6aa..d486b93 100644
--- a/src/env/env-utilities.test.ts
+++ b/src/env/env-utilities.test.ts
@@ -163,18 +163,18 @@ describe('EnvUtilities', () => {
'};'
]);
- const devBaseUrl: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.baseUrl('test'), 'dev.docker-compose.yaml');
- const devDomain: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.domain('test'), 'dev.docker-compose.yaml');
+ const devBaseUrl: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.baseUrl('test'), 'dev.docker-compose.yaml', getPath('.'));
+ const devDomain: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.domain('test'), 'dev.docker-compose.yaml', getPath('.'));
expect(devBaseUrl).toEqual('http://localhost:4201');
expect(devDomain).toEqual('localhost:4201');
- const localBaseUrl: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.baseUrl('test'), 'local.docker-compose.yaml');
- const localDomain: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.domain('test'), 'local.docker-compose.yaml');
+ const localBaseUrl: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.baseUrl('test'), 'local.docker-compose.yaml', getPath('.'));
+ const localDomain: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.domain('test'), 'local.docker-compose.yaml', getPath('.'));
expect(localBaseUrl).toEqual('http://admin.localhost');
expect(localDomain).toEqual('admin.localhost');
- const prodBaseUrl: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.baseUrl('test'), 'docker-compose.yaml');
- const prodDomain: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.domain('test'), 'docker-compose.yaml');
+ const prodBaseUrl: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.baseUrl('test'), 'docker-compose.yaml', getPath('.'));
+ const prodDomain: string = await EnvUtilities.getEnvVariable(DefaultEnvKeys.domain('test'), 'docker-compose.yaml', getPath('.'));
expect(prodBaseUrl).toEqual('https://admin.test.com');
expect(prodDomain).toEqual('admin.test.com');
});
@@ -182,21 +182,21 @@ describe('EnvUtilities', () => {
test('validate', async () => {
const variable: EnvVariable = fakeEnvVariable({ required: true, type: 'number', value: 42 });
await EnvUtilities.addStaticVariable(variable);
- const errorMessages: KeyValue[] = await EnvUtilities.validate();
+ const errorMessages: KeyValue[] = await EnvUtilities.validate(getPath('.'));
expect(errorMessages.length).toEqual(0);
await FsUtilities.replaceInFile(mockConstants.ENV, '42', 'test');
- const errorMessages2: KeyValue[] = await EnvUtilities.validate();
+ const errorMessages2: KeyValue[] = await EnvUtilities.validate(getPath('.'));
expect(errorMessages2.length).toEqual(1);
expect(errorMessages2[0].value).toEqual(EnvValidationErrorMessage.NUMBER);
await FsUtilities.updateFile(mockConstants.ENV, '', 'replace');
- const errorMessages3: KeyValue[] = await EnvUtilities.validate();
+ const errorMessages3: KeyValue[] = await EnvUtilities.validate(getPath('.'));
expect(errorMessages3.length).toEqual(3);
expect(errorMessages3[0].value).toEqual(EnvValidationErrorMessage.REQUIRED);
await FsUtilities.rm(mockConstants.ENV);
- const errorMessages4: KeyValue[] = await EnvUtilities.validate();
+ const errorMessages4: KeyValue[] = await EnvUtilities.validate(getPath('.'));
expect(errorMessages4.length).toEqual(1);
expect(errorMessages4[0].value).toEqual(EnvValidationErrorMessage.FILE_DOES_NOT_EXIST);
});
diff --git a/src/env/env.utilities.ts b/src/env/env.utilities.ts
index 0a08613..9e4dd44 100644
--- a/src/env/env.utilities.ts
+++ b/src/env/env.utilities.ts
@@ -77,10 +77,11 @@ export abstract class EnvUtilities {
/**
* Builds the environment files based on the model and the root .env file.
* @param fileName - The docker compose file to build the variables for.
+ * @param rootDir - The directory of the Monux monorepo.
*/
- static async buildEnvironmentFiles(fileName: DockerComposeFileName): Promise {
- const apps: WorkspaceProject[] = await WorkspaceUtilities.getProjects('apps');
- await Promise.all(apps.map(a => this.buildEnvironmentFileForApp(a, true, fileName)));
+ static async buildEnvironmentFiles(fileName: DockerComposeFileName, rootDir: string): Promise {
+ const apps: WorkspaceProject[] = await WorkspaceUtilities.getProjects('apps', rootDir);
+ await Promise.all(apps.map(a => this.buildEnvironmentFileForApp(a, true, fileName, rootDir)));
}
/**
@@ -88,11 +89,13 @@ export abstract class EnvUtilities {
* @param app - The app to build the environment file for.
* @param failOnMissingVariable - Whether or not the build should fail when a variable is missing.
* @param fileName - The docker compose file to build the variables for.
+ * @param rootDir - The directory of the Monux monorepo.
*/
static async buildEnvironmentFileForApp(
app: WorkspaceProject,
failOnMissingVariable: boolean,
- fileName: DockerComposeFileName
+ fileName: DockerComposeFileName,
+ rootDir: string
): Promise {
const environmentFolder: string = getPath(app.path, 'src', 'environment');
const variableKeys: EnvironmentVariableKey[] = await this.getProjectVariableKeys(
@@ -101,7 +104,7 @@ export abstract class EnvUtilities {
// TODO: The first time getPath fails here because
await FsUtilities.rm(getPath(environmentFolder, ENVIRONMENT_TS_FILE_NAME));
- await this.createProjectEnvironmentFile(app.path, variableKeys, failOnMissingVariable, fileName);
+ await this.createProjectEnvironmentFile(app.path, variableKeys, failOnMissingVariable, fileName, rootDir);
}
/**
@@ -110,12 +113,14 @@ export abstract class EnvUtilities {
* @param environmentModelPath - The path of the projects environment model.
* @param variable - The variable to add.
* @param failOnMissingVariable - Whether or not the build should fail when a variable is missing.
+ * @param rootDir - The directory of the Monux monorepo.
*/
static async addProjectVariableKey(
name: string,
environmentModelPath: string,
variable: EnvironmentVariableKey,
- failOnMissingVariable: boolean
+ failOnMissingVariable: boolean,
+ rootDir: string
): Promise {
const lines: string[] = await FsUtilities.readFileLines(environmentModelPath);
const firstLine: FileLine = await FsUtilities.findLineWithContent(lines, 'defineVariables(');
@@ -126,8 +131,8 @@ export abstract class EnvUtilities {
const newValue: string = firstLine.content.includes('defineVariables([]') ? `defineVariables(['${variable}']` : `defineVariables(['${variable}', `;
await FsUtilities.replaceInFile(environmentModelPath, oldValue, newValue);
- const app: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(name);
- await this.buildEnvironmentFileForApp(app, failOnMissingVariable, 'dev.docker-compose.yaml');
+ const app: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(name, rootDir);
+ await this.buildEnvironmentFileForApp(app, failOnMissingVariable, 'dev.docker-compose.yaml', rootDir);
}
private static async getProjectVariableKeys(environmentModelPath: string): Promise {
@@ -168,16 +173,17 @@ export abstract class EnvUtilities {
'}'
]
);
- await this.createProjectEnvironmentFile(projectPath, [], true, 'dev.docker-compose.yaml');
+ await this.createProjectEnvironmentFile(projectPath, [], true, 'dev.docker-compose.yaml', '.');
}
private static async createProjectEnvironmentFile(
projectPath: string,
variableKeys: EnvironmentVariableKey[],
failOnMissingVariable: boolean,
- fileName: DockerComposeFileName
+ fileName: DockerComposeFileName,
+ rootDir: string
): Promise {
- const variables: EnvVariable[] = await this.getEnvVariables(variableKeys, failOnMissingVariable, fileName);
+ const variables: EnvVariable[] = await this.getEnvVariables(variableKeys, failOnMissingVariable, fileName, rootDir);
await FsUtilities.createFile(
getPath(projectPath, 'src', 'environment', ENVIRONMENT_TS_FILE_NAME),
[
@@ -197,21 +203,24 @@ export abstract class EnvUtilities {
private static async getEnvVariables(
variableKeys: EnvironmentVariableKey[] | undefined,
failOnMissingVariable: boolean,
- fileName: DockerComposeFileName
+ fileName: DockerComposeFileName,
+ rootDir: string
): Promise {
- const keys: VariableKeys = await this.splitVariableKeys(variableKeys, failOnMissingVariable);
- const staticVariables: EnvVariable[] = await this.getStaticEnvVariables(keys.static, failOnMissingVariable);
+ const keys: VariableKeys = await this.splitVariableKeys(variableKeys, failOnMissingVariable, rootDir);
+ const staticVariables: EnvVariable[] = await this.getStaticEnvVariables(keys.static, failOnMissingVariable, rootDir);
const calculatedVariables: EnvVariable[] = await this.getCalculatedEnvVariables(
keys.calculated,
failOnMissingVariable,
- fileName
+ fileName,
+ rootDir
);
return [...staticVariables, ...calculatedVariables];
}
private static async splitVariableKeys(
variableKeys: EnvironmentVariableKey[] | undefined,
- failOnMissingVariable: boolean
+ failOnMissingVariable: boolean,
+ rootDir: string
): Promise {
if (variableKeys == undefined) {
return { static: undefined, calculated: undefined };
@@ -221,12 +230,12 @@ export abstract class EnvUtilities {
}
const staticVariableDefinitions: OmitStrict[] = await this.getVariableDefinitions(
- getPath(GLOBAL_ENVIRONMENT_MODEL_FILE_NAME),
+ getPath(rootDir, GLOBAL_ENVIRONMENT_MODEL_FILE_NAME),
// eslint-disable-next-line sonar/no-duplicate-string
'StaticGlobalEnvironment = {'
);
const calculatedVariableDefinitions: OmitStrict[] = await this.getVariableDefinitions(
- getPath(GLOBAL_ENVIRONMENT_MODEL_FILE_NAME),
+ getPath(rootDir, GLOBAL_ENVIRONMENT_MODEL_FILE_NAME),
// eslint-disable-next-line sonar/no-duplicate-string
'CalculatedGlobalEnvironment = {'
);
@@ -251,13 +260,14 @@ export abstract class EnvUtilities {
private static async getCalculatedEnvVariables(
variableKeys: EnvironmentVariableKey[] | undefined,
failOnMissingVariable: boolean,
- fileName: DockerComposeFileName
+ fileName: DockerComposeFileName,
+ rootDir: string
): Promise {
- const staticVariables: EnvVariable[] = await this.getStaticEnvVariables(undefined, failOnMissingVariable);
+ const staticVariables: EnvVariable[] = await this.getStaticEnvVariables(undefined, failOnMissingVariable, rootDir);
const staticVariableObject: Record = staticVariables.reduce((p, c) => p = { ...p, [c.key]: c.value }, {});
const calculatedVariableDefinitions: OmitStrict[] = await this.getVariableDefinitions(
- getPath(GLOBAL_ENVIRONMENT_MODEL_FILE_NAME),
+ getPath(rootDir, GLOBAL_ENVIRONMENT_MODEL_FILE_NAME),
'CalculatedGlobalEnvironment = {'
);
@@ -266,7 +276,7 @@ export abstract class EnvUtilities {
string,
(env: Record, fileName: DockerComposeFileName) => EnvValue
>
- > = await TsUtilities.getObjectStartingWith(getPath(GLOBAL_ENVIRONMENT_MODEL_FILE_NAME), '> = {');
+ > = await TsUtilities.getObjectStartingWith(getPath(rootDir, GLOBAL_ENVIRONMENT_MODEL_FILE_NAME), '> = {');
const res: EnvVariable[] = [];
for (const key in definition.result) {
@@ -307,11 +317,12 @@ export abstract class EnvUtilities {
// eslint-disable-next-line sonar/cognitive-complexity
private static async getStaticEnvVariables(
variableKeys: EnvironmentVariableKey[] | undefined,
- failOnMissingVariable: boolean
+ failOnMissingVariable: boolean,
+ rootDir: string
): Promise {
- const lines: string[] = await FsUtilities.readFileLines(getPath(ENV_FILE_NAME));
+ const lines: string[] = await FsUtilities.readFileLines(getPath(rootDir, ENV_FILE_NAME));
const staticVariableDefinitions: OmitStrict[] = await this.getVariableDefinitions(
- getPath(GLOBAL_ENVIRONMENT_MODEL_FILE_NAME),
+ getPath(rootDir, GLOBAL_ENVIRONMENT_MODEL_FILE_NAME),
'StaticGlobalEnvironment = {'
);
const res: EnvVariable[] = [];
@@ -352,13 +363,15 @@ export abstract class EnvUtilities {
* Gets the value for the given environment variable.
* @param variable - The variable to get the value of.
* @param fileName - The docker compose file to build the variables for.
+ * @param rootDir - The directory of the Monux monorepo.
* @returns The value of the provided variable.
*/
static async getEnvVariable(
variable: EnvironmentVariableKey,
- fileName: DockerComposeFileName
+ fileName: DockerComposeFileName,
+ rootDir: string
): Promise {
- const envValues: EnvVariable[] = await this.getEnvVariables([variable], false, fileName);
+ const envValues: EnvVariable[] = await this.getEnvVariables([variable], false, fileName, rootDir);
if (!envValues.length) {
throw new Error(`Could not find env value ${variable}`);
}
@@ -422,10 +435,11 @@ export abstract class EnvUtilities {
/**
* Validates the .env file.
+ * @param rootDir - The directory of the Monux monorepo.
* @returns An array of error messages mapped to the keys that caused them.
*/
- static async validate(): Promise[]> {
- if (!await FsUtilities.exists(getPath(ENV_FILE_NAME))) {
+ static async validate(rootDir: string): Promise[]> {
+ if (!await FsUtilities.exists(getPath(rootDir, ENV_FILE_NAME))) {
return [
{
key: ENV_FILE_NAME,
@@ -434,10 +448,10 @@ export abstract class EnvUtilities {
];
}
- const envValues: EnvVariable[] = await this.getStaticEnvVariables(undefined, true);
+ const envValues: EnvVariable[] = await this.getStaticEnvVariables(undefined, true, rootDir);
const staticVariableDefinitions: OmitStrict[] = await this.getVariableDefinitions(
- getPath(GLOBAL_ENVIRONMENT_MODEL_FILE_NAME),
+ getPath(rootDir, GLOBAL_ENVIRONMENT_MODEL_FILE_NAME),
'StaticGlobalEnvironment = {'
);
diff --git a/src/index.ts b/src/index.ts
index 078af68..9624d0d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,7 +1,6 @@
#!/usr/bin/env node
-import { Command, isCommand, runAdd, runDown, runDownDev, runGeneratePage, runHelp, runInit, runList, runPrepare, runRun, runRunAll, runUp, runUpDev, runUpLocal, runVersion } from './commands';
-import { runDownLocal } from './commands/down-local';
-import { validateInput } from './commands/validate-input.function';
+import { Command, DownCommand, HelpCommand, VersionCommand, InitCommand, UpCommand, PrepareCommand, GeneratePageCommand, AddCommand, ListCommand, RunCommand, RunAllCommand } from './commands';
+import { resolveCommand } from './commands/resolve-command.function';
import { DeathUtilities, FigletUtilities } from './encapsulation';
DeathUtilities.death();
@@ -12,89 +11,67 @@ async function main(): Promise {
FigletUtilities.displayLogo();
- await validateInput(args);
- const command: string = args[0];
-
- if (!isCommand(command)) {
- await runRun(...args);
- return;
- }
+ const command: Command | 'run' = resolveCommand(args);
switch (command) {
case Command.H:
case Command.HELP: {
- runHelp();
+ await new HelpCommand().start(args);
return;
}
case Command.VERSION:
case Command.V: {
- await runVersion();
+ await new VersionCommand().start(args);
return;
}
case Command.INIT:
case Command.I: {
- await runInit();
+ await new InitCommand().start(args);
return;
}
case Command.ADD:
case Command.A: {
- await runAdd();
+ await new AddCommand().start(args);
return;
}
case Command.UP:
case Command.U: {
- await runUp();
+ await new UpCommand().start(args);
return;
}
case Command.DOWN:
case Command.D: {
- runDown();
+ await new DownCommand().start(args);
return;
}
case Command.PREPARE:
case Command.P: {
- await runPrepare(undefined);
+ await new PrepareCommand().start(args);
return;
}
case Command.GENERATE_PAGE:
case Command.GP: {
- await runGeneratePage();
- return;
- }
- case Command.UP_DEV:
- case Command.UD: {
- await runUpDev();
- return;
- }
- case Command.DOWN_DEV:
- case Command.DD: {
- runDownDev();
- return;
- }
- case Command.UP_LOCAL:
- case Command.UL: {
- await runUpLocal();
- return;
- }
- case Command.DOWN_LOCAL:
- case Command.DL: {
- runDownLocal();
+ await new GeneratePageCommand().start(args);
return;
}
case Command.RUN_ALL:
case Command.RA:
case Command.RUN_MANY: {
- runRunAll(args[1]);
+ await new RunAllCommand().start(args);
return;
}
case Command.LIST:
case Command.LS: {
- await runList(false);
+ await new ListCommand(false).start(args);
return;
}
case Command.LIST_ALL:
case Command.LA: {
- await runList(true);
+ await new ListCommand(true).start(args);
+ return;
+ }
+ case 'run': {
+ await new RunCommand().start(args);
return;
}
}
diff --git a/src/loopback/loopback.utilities.ts b/src/loopback/loopback.utilities.ts
index c535147..30424e4 100644
--- a/src/loopback/loopback.utilities.ts
+++ b/src/loopback/loopback.utilities.ts
@@ -367,18 +367,42 @@ export abstract class LoopbackUtilities {
);
const environmentModel: string = getPath(root, 'src', 'environment', ENVIRONMENT_MODEL_TS_FILE_NAME);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.defaultUserEmail(config.name), true);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.defaultUserPassword(config.name), true);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'access_token_secret', true);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'refresh_token_secret', true);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'webserver_mail_user', true);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'webserver_mail_password', true);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'webserver_mail_host', true);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'webserver_mail_port', true);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.baseUrl(config.frontendName), false);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.baseUrl(config.name), false);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.IS_PUBLIC, false);
- await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.domain(config.frontendName), false);
+ await EnvUtilities.addProjectVariableKey(
+ config.name,
+ environmentModel,
+ DefaultEnvKeys.defaultUserEmail(config.name),
+ true,
+ getPath('.')
+ );
+ await EnvUtilities.addProjectVariableKey(
+ config.name,
+ environmentModel,
+ DefaultEnvKeys.defaultUserPassword(config.name),
+ true,
+ getPath('.')
+ );
+ await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'access_token_secret', true, getPath('.'));
+ await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'refresh_token_secret', true, getPath('.'));
+ await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'webserver_mail_user', true, getPath('.'));
+ await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'webserver_mail_password', true, getPath('.'));
+ await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'webserver_mail_host', true, getPath('.'));
+ await EnvUtilities.addProjectVariableKey(config.name, environmentModel, 'webserver_mail_port', true, getPath('.'));
+ await EnvUtilities.addProjectVariableKey(
+ config.name,
+ environmentModel,
+ DefaultEnvKeys.baseUrl(config.frontendName),
+ false,
+ getPath('.')
+ );
+ await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.baseUrl(config.name), false, getPath('.'));
+ await EnvUtilities.addProjectVariableKey(config.name, environmentModel, DefaultEnvKeys.IS_PUBLIC, false, getPath('.'));
+ await EnvUtilities.addProjectVariableKey(
+ config.name,
+ environmentModel,
+ DefaultEnvKeys.domain(config.frontendName),
+ false,
+ getPath('.')
+ );
}
private static async applyAuthToIndexTs(
diff --git a/src/npm/npm.utilities.ts b/src/npm/npm.utilities.ts
index a6318c4..267d8b6 100644
--- a/src/npm/npm.utilities.ts
+++ b/src/npm/npm.utilities.ts
@@ -62,7 +62,7 @@ export abstract class NpmUtilities {
* @param commands - The npm script to run.
*/
static async run(projectName: string, commands: string): Promise {
- const project: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(projectName);
+ const project: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(projectName, getPath('.'));
CPUtilities.execSync(`npm run ${commands} --workspace=${project.npmWorkspaceString}`);
}
@@ -81,7 +81,7 @@ export abstract class NpmUtilities {
* @param development - Whether or not the packages will be installed with -D or not.
*/
static async install(projectName: string, npmPackages: NpmPackage[], development: boolean = false): Promise {
- const project: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(projectName);
+ const project: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(projectName, getPath('.'));
const installCommand: string = development ? 'npm i -D' : 'npm i';
CPUtilities.execSync(`cd ${project.path} && ${installCommand} ${npmPackages.join(' ')}`);
@@ -103,7 +103,7 @@ export abstract class NpmUtilities {
* @param data - The data to update the package.json with.
*/
static async updatePackageJson(projectName: string, data: Partial): Promise {
- const project: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(projectName);
+ const project: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(projectName, getPath('.'));
const packageJsonPath: string = getPath(project.path, PACKAGE_JSON_FILE_NAME);
await this.updatePackageJsonFile(packageJsonPath, data);
}
diff --git a/src/robots/robots-utilities.test.ts b/src/robots/robots-utilities.test.ts
index 09c5da7..a054967 100644
--- a/src/robots/robots-utilities.test.ts
+++ b/src/robots/robots-utilities.test.ts
@@ -19,7 +19,7 @@ describe('RobotsUtilities', () => {
});
test('createRobotsTxtForApp', async () => {
- const isPublic: boolean = await EnvUtilities.getEnvVariable(DefaultEnvKeys.IS_PUBLIC, 'dev.docker-compose.yaml');
+ const isPublic: boolean = await EnvUtilities.getEnvVariable(DefaultEnvKeys.IS_PUBLIC, 'dev.docker-compose.yaml', getPath('.'));
expect(isPublic).toBe(false);
await RobotsUtilities.createRobotsTxtForApp(
@@ -29,7 +29,8 @@ describe('RobotsUtilities', () => {
npmWorkspaceString: `apps/${mockConstants.ANGULAR_APP_NAME}`
},
'dev.docker-compose.yaml',
- undefined
+ undefined,
+ getPath('.')
);
const robotsTxt: string[] = await FsUtilities.readFileLines(getPath(mockConstants.ANGULAR_APP_DIR, 'src', ROBOTS_FILE_NAME));
diff --git a/src/robots/robots.utilities.ts b/src/robots/robots.utilities.ts
index 1fdd71c..9337bef 100644
--- a/src/robots/robots.utilities.ts
+++ b/src/robots/robots.utilities.ts
@@ -12,20 +12,21 @@ export abstract class RobotsUtilities {
/**
* Automatically creates robots.txt files for every project with a sitemap.xml.
* @param fileName - The docker compose file get the variables for.
+ * @param rootDir - The directory of the Monux monorepo where the files should be created.
*/
- static async createRobotsTxtFiles(fileName: DockerComposeFileName): Promise {
- const environmentFilePath: string = getPath(ENV_FILE_NAME);
+ static async createRobotsTxtFiles(fileName: DockerComposeFileName, rootDir: string): Promise {
+ const environmentFilePath: string = getPath(rootDir, ENV_FILE_NAME);
if (!(await FsUtilities.readFile(environmentFilePath)).includes(`${DefaultEnvKeys.IS_PUBLIC}=`)) {
return;
}
// Only projects that have a sitemap file get a robots.txt file.
- const apps: WorkspaceProject[] = await filterAsync(await WorkspaceUtilities.getProjects('apps'), async a => {
+ const apps: WorkspaceProject[] = await filterAsync(await WorkspaceUtilities.getProjects('apps', rootDir), async a => {
const sitemapPath: string = getPath(a.path, 'src', SITEMAP_FILE_NAME);
return await FsUtilities.exists(sitemapPath);
});
await Promise.all(apps.map(async a => {
- return this.createRobotsTxtForApp(a, fileName, undefined);
+ return this.createRobotsTxtForApp(a, fileName, undefined, rootDir);
}));
}
@@ -34,23 +35,25 @@ export abstract class RobotsUtilities {
* @param app - The app to generate the robots.txt for.
* @param fileName - The docker compose file get the variables for.
* @param domain - An optional domain. This is used when creating new projects, where the domain environment variable has not been set yet.
+ * @param rootDir - The directory of the Monux monorepo.
*/
static async createRobotsTxtForApp(
app: WorkspaceProject,
fileName: DockerComposeFileName,
- domain: string | undefined
+ domain: string | undefined,
+ rootDir: string
): Promise {
const robotsTxtPath: string = getPath(app.path, 'src', ROBOTS_FILE_NAME);
await FsUtilities.rm(robotsTxtPath);
- const isPublic: boolean = await EnvUtilities.getEnvVariable(DefaultEnvKeys.IS_PUBLIC, fileName);
+ const isPublic: boolean = await EnvUtilities.getEnvVariable(DefaultEnvKeys.IS_PUBLIC, fileName, rootDir);
const content: string[] = [
'User-agent: *',
`${isPublic ? 'Allow' : 'Disallow'}: /`
];
- const baseUrl: string = domain ? `https://${domain}` : await EnvUtilities.getEnvVariable(DefaultEnvKeys.baseUrl(app.name), fileName);
+ const baseUrl: string = domain ? `https://${domain}` : await EnvUtilities.getEnvVariable(DefaultEnvKeys.baseUrl(app.name), fileName, rootDir);
content.push('', `Sitemap: ${baseUrl}/${SITEMAP_FILE_NAME}`);
await FsUtilities.createFile(robotsTxtPath, content);
diff --git a/src/tsconfig/tsconfig.utilities.ts b/src/tsconfig/tsconfig.utilities.ts
index 8053bb3..4176809 100644
--- a/src/tsconfig/tsconfig.utilities.ts
+++ b/src/tsconfig/tsconfig.utilities.ts
@@ -59,7 +59,7 @@ export abstract class TsConfigUtilities {
* @param data - The data to update the tsconfig with.
*/
static async updateTsConfig(projectName: string, data: Partial): Promise {
- const project: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(projectName);
+ const project: WorkspaceProject = await WorkspaceUtilities.findProjectOrFail(projectName, getPath('.'));
const tsConfigPath: string = getPath(project.path, TS_CONFIG_FILE_NAME);
await this.update(tsConfigPath, data);
}
diff --git a/src/commands/exit-with-error.function.ts b/src/utilities/exit-with-error.function.ts
similarity index 100%
rename from src/commands/exit-with-error.function.ts
rename to src/utilities/exit-with-error.function.ts
diff --git a/src/commands/exit-with-interrupt.function.ts b/src/utilities/exit-with-interrupt.function.ts
similarity index 100%
rename from src/commands/exit-with-interrupt.function.ts
rename to src/utilities/exit-with-interrupt.function.ts
diff --git a/src/utilities/index.ts b/src/utilities/index.ts
index 5f96435..db9999c 100644
--- a/src/utilities/index.ts
+++ b/src/utilities/index.ts
@@ -5,4 +5,8 @@ export * from './to-kebab-case.function';
export * from './to-pascal-case.function';
export * from './generate-placeholder-password.function';
export * from './filter-async.function';
-export * from './get-path.function';
\ No newline at end of file
+export * from './get-path.function';
+export * from './exit-with-error.function';
+export * from './exit-with-interrupt.function';
+export * from './is-error-with-signal.function';
+export * from './is-exit-prompt-error.function';
\ No newline at end of file
diff --git a/src/commands/is-error-with-signal.function.ts b/src/utilities/is-error-with-signal.function.ts
similarity index 100%
rename from src/commands/is-error-with-signal.function.ts
rename to src/utilities/is-error-with-signal.function.ts
diff --git a/src/commands/is-exit-prompt-error.function.ts b/src/utilities/is-exit-prompt-error.function.ts
similarity index 100%
rename from src/commands/is-exit-prompt-error.function.ts
rename to src/utilities/is-exit-prompt-error.function.ts
diff --git a/src/workspace/workspace.utilities.ts b/src/workspace/workspace.utilities.ts
index 9d25eca..0ff985f 100644
--- a/src/workspace/workspace.utilities.ts
+++ b/src/workspace/workspace.utilities.ts
@@ -67,21 +67,23 @@ export abstract class WorkspaceUtilities {
/**
* Finds the directory of the project with the given name.
* @param name - The name of the project to find.
+ * @param rootDir - The directory of the Monux monorepo to find the project in.
* @returns The found directory or undefined.
*/
- static async findProject(name: string): Promise {
- const allProjects: WorkspaceProject[] = await this.getProjects();
+ static async findProject(name: string, rootDir: string): Promise {
+ const allProjects: WorkspaceProject[] = await this.getProjects('all', rootDir);
return allProjects.find(p => p.name === name);
}
/**
* Finds the directory of the project with the given name.
* @param name - The name of the project to find.
+ * @param rootDir - The directory of the Monux monorepo to find the project in.
* @returns The found directory.
* @throws When no project with the given name was found.
*/
- static async findProjectOrFail(name: string): Promise {
- const res: WorkspaceProject | undefined = await this.findProject(name);
+ static async findProjectOrFail(name: string, rootDir: string): Promise {
+ const res: WorkspaceProject | undefined = await this.findProject(name, rootDir);
if (res == undefined) {
throw new Error(`project with name ${name} does not exist`);
}
@@ -91,27 +93,28 @@ export abstract class WorkspaceUtilities {
/**
* Gets either all libraries, all apps or every project.
* @param filter - Filter to only return libraries, apps or all projects.
+ * @param rootDir - The directory of the Monux monorepo to get the projects for.
* @returns An array of directories.
*/
- static async getProjects(filter: 'libs' | 'apps' | 'all' = 'all'): Promise {
+ static async getProjects(filter: 'libs' | 'apps' | 'all' = 'all', rootDir: string): Promise {
switch (filter) {
case 'apps': {
- return await this.getApps();
+ return await this.getApps(rootDir);
}
case 'libs': {
- return await this.getLibs();
+ return await this.getLibs(rootDir);
}
case 'all': {
return [
- ...await this.getApps(),
- ...await this.getLibs()
+ ...await this.getApps(rootDir),
+ ...await this.getLibs(rootDir)
];
}
}
}
- private static async getApps(): Promise {
- const dirs: Dirent[] = (await FsUtilities.readdir(getPath(APPS_DIRECTORY_NAME))).filter(d => d.isDirectory());
+ private static async getApps(rootDir: string): Promise {
+ const dirs: Dirent[] = (await FsUtilities.readdir(getPath(rootDir, APPS_DIRECTORY_NAME))).filter(d => d.isDirectory());
return dirs.map(d => {
const res: WorkspaceProject = {
name: d.name,
@@ -122,8 +125,8 @@ export abstract class WorkspaceUtilities {
});
}
- private static async getLibs(): Promise {
- const dirs: Dirent[] = (await FsUtilities.readdir(getPath(LIBS_DIRECTORY_NAME))).filter(d => d.isDirectory());
+ private static async getLibs(rootDir: string): Promise {
+ const dirs: Dirent[] = (await FsUtilities.readdir(getPath(rootDir, LIBS_DIRECTORY_NAME))).filter(d => d.isDirectory());
return dirs.map(d => {
const res: WorkspaceProject = {
name: d.name,