@elysiumjs/styx 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @elysiumjs/styx
2
2
 
3
- CLI tool for Elysium.
3
+ CLI tool for Elysium.js framework.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,34 +10,45 @@ bun install @elysiumjs/styx
10
10
 
11
11
  ## Usage
12
12
 
13
- The `styx` command needs to be run from the root of your Elysium project.
13
+ The `styx` command needs to be run inside an Elysium.js project.
14
14
 
15
15
  To create new items:
16
16
 
17
17
  ```bash
18
- bun styx exec make:controller [options]
19
- bun styx exec make:job [options]
20
- bun styx exec make:middleware [options]
21
- bun styx exec make:service [options]
22
- bun styx exec make:command [options]
18
+ bun styx make:command [options]
19
+ bun styx make:controller [options]
20
+ bun styx make:job [options]
21
+ bun styx make:middleware [options]
22
+ bun styx make:model [options]
23
+ bun styx make:repository [options]
24
+ bun styx make:service [options]
25
+ bun styx make:validator [options]
23
26
  ```
24
27
 
25
- To start the Elysium project:
28
+ To manage migrations:
26
29
 
27
30
  ```bash
28
- bun styx serve
31
+ bun styx migration:generate [options]
32
+ bun styx migration:run [options]
29
33
  ```
30
34
 
31
- To execute a custom command:
35
+ To manage modules:
36
+
37
+ ```bash
38
+ bun styx module:new [options]
39
+ bun styx module:rename [options]
40
+ ```
41
+
42
+ To start the Elysium.js project:
32
43
 
33
44
  ```bash
34
- bun styx exec <command>
45
+ bun styx serve
35
46
  ```
36
47
 
37
- To run a worker:
48
+ To execute a custom command:
38
49
 
39
50
  ```bash
40
- bun styx work --queue queue1 --queue queue2
51
+ bun styx <command>
41
52
  ```
42
53
 
43
54
  To show all available commands:
@@ -60,4 +71,4 @@ Feel free to open issues for any bugs or questions you may have. Pull requests a
60
71
 
61
72
  ## License
62
73
 
63
- This project is licensed under the Apache License, Version 2.0. See the [LICENSE](../../LICENSE) file for more information.
74
+ This project is licensed under the Apache License, Version 2.0. See the [LICENSE](https://github.com/workbud/elysium/blob/main/LICENSE) file for more information.
package/package.json CHANGED
@@ -1,39 +1,38 @@
1
1
  {
2
2
  "name": "@elysiumjs/styx",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "license": "Apache-2.0",
5
- "description": "The CLI tool for the Elysium framework.",
5
+ "description": "The CLI tool for the Elysium.js framework.",
6
6
  "private": false,
7
- "author": {
8
- "name": "Axel Nana",
9
- "email": "axel.nana@workbud.com"
10
- },
7
+ "author": "Workbud Technologies Inc.",
11
8
  "repository": {
12
9
  "type": "git",
13
- "url": "https://github.com/Workerly/elysium"
10
+ "url": "https://github.com/workbud/elysium"
14
11
  },
15
- "homepage": "https://github.com/Workerly/elysium",
12
+ "homepage": "https://github.com/workbud/elysium",
16
13
  "scripts": {
17
14
  "format": "prettier --write \"./src/**/*.ts\"",
18
- "build": "bun build index.ts --outfile=styx.js --target bun --minify -e @elysiumjs/core"
15
+ "build": "bun build ./src/styx.ts --outdir=./ --root ./src --target bun --minify -e @elysiumjs/core -e reflect-metadata",
16
+ "prepublishOnly": "bun run build"
19
17
  },
20
18
  "keywords": [
21
19
  "elysium",
22
20
  "framework",
23
21
  "web",
24
22
  "api",
25
- "typescript"
23
+ "typescript",
24
+ "cli"
26
25
  ],
27
26
  "type": "module",
28
27
  "bin": {
29
28
  "styx": "./styx.js"
30
29
  },
31
30
  "devDependencies": {
32
- "@ianvs/prettier-plugin-sort-imports": "^4.4.1",
31
+ "@ianvs/prettier-plugin-sort-imports": "^4.4.2",
33
32
  "@types/bun": "latest",
34
33
  "@types/prompts": "^2.4.9",
35
34
  "@types/string-template": "^1.0.6",
36
- "type-fest": "^4.40.1",
35
+ "type-fest": "^4.41.0",
37
36
  "prompts": "^2.4.2",
38
37
  "radash": "^12.1.0",
39
38
  "string-template": "^1.0.0"
@@ -25,7 +25,7 @@ import { getModulePath, parseProjectConfig } from '../config';
25
25
  import { getRootPath } from '../utils';
26
26
 
27
27
  /**
28
- * Maker class for creating Elysium commands.
28
+ * Maker class for creating Elysium.js commands.
29
29
  * @author Axel Nana <axel.nana@workbud.com>
30
30
  */
31
31
  export class MakeCommandCommand extends Command {
@@ -132,7 +132,12 @@ export class MakeCommandCommand extends Command {
132
132
  }
133
133
  ];
134
134
 
135
- const answers = await prompts(items);
135
+ const answers = await prompts(items, {
136
+ onCancel: () => {
137
+ this.error('Operation cancelled.');
138
+ process.exit(0);
139
+ }
140
+ });
136
141
 
137
142
  answers.name = pascal(answers.command.replaceAll(':', '_'));
138
143
 
@@ -25,7 +25,7 @@ import { getModulePath, parseProjectConfig } from '../config';
25
25
  import { getRootPath } from '../utils';
26
26
 
27
27
  /**
28
- * Maker class for creating Elysium controllers.
28
+ * Maker class for creating Elysium.js controllers.
29
29
  * @author Axel Nana <axel.nana@workbud.com>
30
30
  */
31
31
  export class MakeControllerCommand extends Command {
@@ -239,7 +239,12 @@ export class MakeControllerCommand extends Command {
239
239
  }
240
240
  ];
241
241
 
242
- const answers = await prompts(items);
242
+ const answers = await prompts(items, {
243
+ onCancel: () => {
244
+ this.error('Operation cancelled.');
245
+ process.exit(0);
246
+ }
247
+ });
243
248
 
244
249
  return this.build(answers);
245
250
  }
@@ -25,7 +25,7 @@ import { getModulePath, parseProjectConfig } from '../config';
25
25
  import { getRootPath } from '../utils';
26
26
 
27
27
  /**
28
- * Maker class for creating Elysium jobs.
28
+ * Maker class for creating Elysium.js jobs.
29
29
  * @author Axel Nana <axel.nana@workbud.com>
30
30
  */
31
31
  export class MakeJobCommand extends Command {
@@ -141,7 +141,12 @@ export class MakeJobCommand extends Command {
141
141
  }
142
142
  ];
143
143
 
144
- const answers = await prompts(items);
144
+ const answers = await prompts(items, {
145
+ onCancel: () => {
146
+ this.error('Operation cancelled.');
147
+ process.exit(0);
148
+ }
149
+ });
145
150
 
146
151
  return this.build(answers);
147
152
  }
@@ -25,7 +25,7 @@ import { getModulePath, parseProjectConfig } from '../config';
25
25
  import { getRootPath } from '../utils';
26
26
 
27
27
  /**
28
- * Maker class for creating Elysium middlewares.
28
+ * Maker class for creating Elysium.js middlewares.
29
29
  * @author Axel Nana <axel.nana@workbud.com>
30
30
  */
31
31
  export class MakeMiddlewareCommand extends Command {
@@ -105,7 +105,12 @@ export class MakeMiddlewareCommand extends Command {
105
105
  }
106
106
  ];
107
107
 
108
- const answers = await prompts(items);
108
+ const answers = await prompts(items, {
109
+ onCancel: () => {
110
+ this.error('Operation cancelled.');
111
+ process.exit(0);
112
+ }
113
+ });
109
114
 
110
115
  return this.build(answers);
111
116
  }
@@ -25,7 +25,7 @@ import { getModulePath, parseProjectConfig } from '../config';
25
25
  import { getRootPath } from '../utils';
26
26
 
27
27
  /**
28
- * Maker command for creating Elysium models.
28
+ * Maker command for creating Elysium.js models.
29
29
  * @author Axel Nana <axel.nana@workbud.com>
30
30
  */
31
31
  export class MakeModelCommand extends Command {
@@ -74,15 +74,20 @@ export class MakeModelCommand extends Command {
74
74
  };
75
75
 
76
76
  if (!answers.module && !config.mono) {
77
- const module = await prompts({
78
- type: 'select',
79
- name: 'module',
80
- message: 'Module:',
81
- choices: Object.keys(config.modules ?? {}).map((moduleName) => ({
82
- title: moduleName,
83
- value: moduleName
84
- }))
85
- });
77
+ const module = await prompts(
78
+ {
79
+ type: 'select',
80
+ name: 'module',
81
+ message: 'Module:',
82
+ choices: Object.keys(config.modules ?? {}).map((moduleName) => ({
83
+ title: moduleName,
84
+ value: moduleName
85
+ }))
86
+ },
87
+ {
88
+ onCancel: () => process.exit(1)
89
+ }
90
+ );
86
91
 
87
92
  answers.module = module.module;
88
93
  }
@@ -160,7 +165,12 @@ export class MakeModelCommand extends Command {
160
165
  }
161
166
  ];
162
167
 
163
- const answers = await prompts(items);
168
+ const answers = await prompts(items, {
169
+ onCancel: () => {
170
+ this.error('Operation cancelled.');
171
+ process.exit(0);
172
+ }
173
+ });
164
174
 
165
175
  return this.build(answers);
166
176
  }
@@ -179,7 +179,12 @@ export class MakeRepositoryCommand extends Command {
179
179
  }
180
180
  ];
181
181
 
182
- const answers = await prompts(items);
182
+ const answers = await prompts(items, {
183
+ onCancel: () => {
184
+ this.error('Operation cancelled.');
185
+ process.exit(0);
186
+ }
187
+ });
183
188
 
184
189
  return this.build(answers);
185
190
  }
@@ -159,7 +159,12 @@ export class MakeServiceCommand extends Command {
159
159
  }
160
160
  ];
161
161
 
162
- const answers = await prompts(items);
162
+ const answers = await prompts(items, {
163
+ onCancel: () => {
164
+ this.error('Operation cancelled.');
165
+ process.exit(0);
166
+ }
167
+ });
163
168
 
164
169
  return this.build(answers);
165
170
  }
@@ -105,7 +105,12 @@ export class MakeValidatorCommand extends Command {
105
105
  }
106
106
  ];
107
107
 
108
- const answers = await prompts(items);
108
+ const answers = await prompts(items, {
109
+ onCancel: () => {
110
+ this.error('Operation cancelled.');
111
+ process.exit(0);
112
+ }
113
+ });
109
114
 
110
115
  return this.build(answers);
111
116
  }
@@ -17,6 +17,8 @@ import { exists } from 'node:fs/promises';
17
17
  import { Command, CommandArgumentType } from '@elysiumjs/core';
18
18
  import prompts from 'prompts';
19
19
 
20
+ import { getProjectPath } from '../utils';
21
+
20
22
  /**
21
23
  * Generates migration files.
22
24
  * @author Axel Nana <axel.nana@workbud.com>
@@ -33,37 +35,46 @@ export class MigrationGenerateCommand extends Command {
33
35
 
34
36
  public async run(): Promise<void> {
35
37
  if (!this.name) {
36
- const answers = await prompts([
37
- {
38
- type: 'text',
39
- name: 'name',
40
- message: 'Migration Name:',
41
- initial: 'create_users_table',
42
- validate(value: string) {
43
- if (value.length < 1) {
44
- return 'Migration name cannot be empty';
45
- }
38
+ const answers = await prompts(
39
+ [
40
+ {
41
+ type: 'text',
42
+ name: 'name',
43
+ message: 'Migration Name:',
44
+ initial: 'create_users_table',
45
+ validate(value: string) {
46
+ if (value.length < 1) {
47
+ return 'Migration name cannot be empty';
48
+ }
46
49
 
47
- return true;
50
+ return true;
51
+ }
52
+ }
53
+ ],
54
+ {
55
+ onCancel: () => {
56
+ this.error('Operation cancelled.');
57
+ process.exit(0);
48
58
  }
49
59
  }
50
- ]);
60
+ );
51
61
 
52
62
  this.name = answers.name;
53
63
  }
54
64
 
55
- let schemaPath = `./src/database/schemas`;
56
- if (await exists(`${process.cwd()}/src/database/schema.ts`)) {
57
- schemaPath = `./src/database/schema.ts`;
65
+ const projectRoot = getProjectPath();
66
+ let schemaPath = `${projectRoot}/src/database/schemas`;
67
+ if (await exists(`${projectRoot}/src/database/schema.ts`)) {
68
+ schemaPath = `${projectRoot}/src/database/schema.ts`;
58
69
  }
59
70
 
60
- await Bun.$`bunx --bun drizzle-kit generate --dialect postgresql --schema ${schemaPath} --out ./src/database/migrations --prefix timestamp --name ${this.name}`;
71
+ await Bun.$`bunx --bun drizzle-kit generate --dialect postgresql --schema ${schemaPath} --out ${projectRoot}/src/database/migrations --prefix timestamp --name ${this.name}`;
61
72
 
62
- const sqlFiles = new Bun.Glob(`${process.cwd()}/src/database/migrations/**/*.sql`).scan();
73
+ const sqlFiles = new Bun.Glob(`${projectRoot}/src/database/migrations/**/*.sql`).scan();
63
74
  for await (const path of sqlFiles) {
64
75
  const file = Bun.file(path);
65
76
  const content = await file.text();
66
- await file.write(content.replace(/"public"./gm, ''));
77
+ await file.write(content.replace(/"public"\./gm, ''));
67
78
  }
68
79
  }
69
80
  }
@@ -69,14 +69,22 @@ export class MigrationRunCommand extends Command {
69
69
  ) AS schema_exists;`;
70
70
 
71
71
  if (!schema_exists) {
72
- const answers = await prompts([
72
+ const answers = await prompts(
73
+ [
74
+ {
75
+ type: 'confirm',
76
+ name: 'create',
77
+ message: `Schema ${this.tenant} does not exist. Do you want to create it?`,
78
+ initial: true
79
+ }
80
+ ],
73
81
  {
74
- type: 'confirm',
75
- name: 'create',
76
- message: `Schema ${this.tenant} does not exist. Do you want to create it?`,
77
- initial: true
82
+ onCancel: () => {
83
+ this.error('Operation cancelled.');
84
+ process.exit(0);
85
+ }
78
86
  }
79
- ]);
87
+ );
80
88
 
81
89
  if (answers.create) {
82
90
  await sql`CREATE SCHEMA ${sql(this.tenant)};`;
@@ -91,7 +99,7 @@ export class MigrationRunCommand extends Command {
91
99
  const [{ table_exists }] = await sql`SELECT EXISTS (
92
100
  SELECT *
93
101
  FROM information_schema.tables
94
- WHERE table_name = 'elysium_migrations'
102
+ WHERE table_name = 'elysium_migrations'
95
103
  AND table_schema = ${this.tenant}
96
104
  ) AS table_exists;`;
97
105
 
@@ -0,0 +1,112 @@
1
+ // Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import { exists, rmdir } from 'node:fs/promises';
16
+ import { join } from 'node:path';
17
+
18
+ import { Command, CommandArgumentType } from '@elysiumjs/core';
19
+ import prompts from 'prompts';
20
+ import { snake } from 'radash';
21
+
22
+ import { parseProjectConfig } from '../config';
23
+ import { createModule, getProjectPath } from '../utils';
24
+
25
+ /**
26
+ * Command for creating Elysium.js modules.
27
+ * @author Axel Nana <axel.nana@workbud.com>
28
+ */
29
+ export class ModuleNewCommand extends Command {
30
+ public static readonly command: string = 'module:new';
31
+ public static readonly description: string = 'Creates a new module.';
32
+
33
+ @Command.arg({
34
+ description: 'The name of the module to create',
35
+ type: CommandArgumentType.STRING
36
+ })
37
+ private name?: string;
38
+
39
+ private module?: string;
40
+ private convert: boolean = false;
41
+
42
+ public override async run(): Promise<void> {
43
+ const config = await parseProjectConfig();
44
+
45
+ // 1. Prompt for new module name if not provided
46
+ if (!this.name) {
47
+ const name = await prompts(
48
+ {
49
+ type: 'text',
50
+ name: 'name',
51
+ message: 'New module name:',
52
+ initial: 'module',
53
+ validate(value) {
54
+ return value.length > 0 ? true : 'Module name cannot be empty';
55
+ }
56
+ },
57
+ {
58
+ onCancel: () => process.exit(0)
59
+ }
60
+ );
61
+
62
+ this.name = snake(name.name);
63
+ }
64
+
65
+ // 2. If mono-module, prompt for conversion and current module name
66
+ if (config.mono) {
67
+ const { module, convert } = await prompts(
68
+ [
69
+ {
70
+ type: 'confirm',
71
+ name: 'convert',
72
+ message:
73
+ 'This project is a mono-module project. Do you want to convert it to a multi-module project?',
74
+ initial: false
75
+ },
76
+ {
77
+ type: (convert) => (convert ? 'text' : null),
78
+ name: 'module',
79
+ message: 'Current module name:',
80
+ initial: 'main',
81
+ validate(value) {
82
+ return value.length > 0 ? true : 'Module name cannot be empty';
83
+ }
84
+ }
85
+ ],
86
+ {
87
+ onCancel: () => process.exit(0)
88
+ }
89
+ );
90
+
91
+ if (!convert) return this.error('Operation cancelled. The project is left unchanged.');
92
+
93
+ this.module = snake(module as string);
94
+ this.convert = convert;
95
+ }
96
+
97
+ // 3. If multi-module conversion is enabled, convert the project to a multi-module project
98
+ if (this.convert) {
99
+ const modulesDir = join(getProjectPath(), 'src', 'modules');
100
+ if (await exists(modulesDir)) await rmdir(modulesDir, { recursive: true });
101
+
102
+ await createModule(this.module!, 'root');
103
+
104
+ this.success(`Project converted to multi-module.`);
105
+ this.success(`Module '${this.module}' created.`);
106
+ }
107
+
108
+ // 4. Create the new module
109
+ await createModule(this.name);
110
+ this.success(`Module '${this.name}' created.`);
111
+ }
112
+ }
@@ -0,0 +1,78 @@
1
+ // Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import { Command, CommandArgumentType } from '@elysiumjs/core';
16
+ import prompts from 'prompts';
17
+
18
+ import { parseProjectConfig } from '../config';
19
+ import { createModule } from '../utils';
20
+
21
+ /**
22
+ * Command for renaming Elysium.js modules.
23
+ * @author Axel Nana <axel.nana@workbud.com>
24
+ */
25
+ export class ModuleRenameCommand extends Command {
26
+ public static readonly command: string = 'module:rename';
27
+ public static readonly description: string = 'Renames a module.';
28
+
29
+ @Command.arg({
30
+ description: 'The module to rename',
31
+ type: CommandArgumentType.STRING
32
+ })
33
+ private module?: string;
34
+
35
+ @Command.arg({
36
+ description: 'The new name of the module',
37
+ type: CommandArgumentType.STRING
38
+ })
39
+ private name?: string;
40
+
41
+ public override async run(): Promise<void> {
42
+ const config = await parseProjectConfig();
43
+
44
+ if (config.mono) {
45
+ return this.error('This operation is not supported for mono-module projects.');
46
+ }
47
+
48
+ if (!this.module) {
49
+ const { module } = await prompts({
50
+ type: 'text',
51
+ name: 'module',
52
+ message: 'Module to rename:',
53
+ validate: (value) => value.length > 0
54
+ });
55
+
56
+ this.module = module as string;
57
+ }
58
+
59
+ if (!this.name) {
60
+ const { name } = await prompts({
61
+ type: 'text',
62
+ name: 'name',
63
+ message: 'New module name:',
64
+ initial: this.module,
65
+ validate: (value) => value.length > 0
66
+ });
67
+
68
+ this.name = name as string;
69
+ }
70
+
71
+ if (this.module === this.name) {
72
+ return this.info('Module name is the same as the current name.');
73
+ }
74
+
75
+ await createModule(this.name!, this.module!);
76
+ this.success(`Module '${this.module}' renamed to '${this.name}'.`);
77
+ }
78
+ }
package/src/config.ts CHANGED
@@ -12,7 +12,9 @@
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
14
 
15
- import { resolve } from 'node:path';
15
+ import { join } from 'node:path';
16
+
17
+ import { getProjectPath } from './utils';
16
18
 
17
19
  /**
18
20
  * Describes the Elysium project's configuration.
@@ -44,12 +46,12 @@ export interface ProjectConfig {
44
46
  * @returns The Elysium project's configuration.
45
47
  */
46
48
  export const parseProjectConfig = async (): Promise<ProjectConfig> => {
47
- const config = Bun.file(resolve('.elysiumrc'));
48
- if (!config.exists()) {
49
+ const file = Bun.file(join(getProjectPath(), '.elysiumrc'));
50
+ if (!file.exists()) {
49
51
  throw new Error('Elysium config file not found.');
50
52
  }
51
53
 
52
- return JSON.parse(await config.text());
54
+ return file.json();
53
55
  };
54
56
 
55
57
  /**
@@ -58,7 +60,7 @@ export const parseProjectConfig = async (): Promise<ProjectConfig> => {
58
60
  * @param config The Elysium project's configuration.
59
61
  */
60
62
  export const writeProjectConfig = async (config: ProjectConfig) => {
61
- const file = Bun.file(resolve('.elysiumrc'));
63
+ const file = Bun.file(join(getProjectPath(), '.elysiumrc'));
62
64
  await file.write(JSON.stringify(config, null, 2));
63
65
  };
64
66