@elysiumjs/styx 0.3.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.3.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"
@@ -0,0 +1,172 @@
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 type { PromptObject } from 'prompts';
16
+
17
+ import { join } from 'node:path';
18
+
19
+ import { Command, CommandArgumentType } from '@elysiumjs/core';
20
+ import prompts from 'prompts';
21
+ import { pascal, snake } from 'radash';
22
+ import formatter from 'string-template';
23
+
24
+ import { getModulePath, parseProjectConfig } from '../config';
25
+ import { getRootPath } from '../utils';
26
+
27
+ /**
28
+ * Maker class for creating Elysium.js commands.
29
+ * @author Axel Nana <axel.nana@workbud.com>
30
+ */
31
+ export class MakeCommandCommand extends Command {
32
+ public static readonly command: string = 'make:command';
33
+ public static readonly description: string = 'Creates a new command.';
34
+
35
+ @Command.arg({
36
+ description: 'The name of the command to create',
37
+ type: CommandArgumentType.STRING
38
+ })
39
+ private name?: string;
40
+
41
+ @Command.arg({
42
+ description: 'The module where the command will be created',
43
+ type: CommandArgumentType.STRING
44
+ })
45
+ private module?: string;
46
+
47
+ @Command.arg({
48
+ description: 'The command to create',
49
+ type: CommandArgumentType.STRING
50
+ })
51
+ private command?: string;
52
+
53
+ @Command.arg({
54
+ description: 'The description of the command',
55
+ type: CommandArgumentType.STRING
56
+ })
57
+ private description?: string;
58
+
59
+ public async run(): Promise<void> {
60
+ if (!this.command || !this.name) {
61
+ return this.setup();
62
+ }
63
+
64
+ const config = await parseProjectConfig();
65
+
66
+ const answers: Record<string, any> = {
67
+ module: this.module,
68
+ name: this.name,
69
+ command: this.command,
70
+ description: this.description
71
+ };
72
+
73
+ if (!answers.module && !config.mono) {
74
+ const module = await prompts({
75
+ type: 'select',
76
+ name: 'module',
77
+ message: 'Module:',
78
+ choices: Object.keys(config.modules ?? {}).map((moduleName) => ({
79
+ title: moduleName,
80
+ value: moduleName
81
+ }))
82
+ });
83
+
84
+ answers.module = module.module;
85
+ }
86
+
87
+ return this.build(answers);
88
+ }
89
+
90
+ private async setup(): Promise<void> {
91
+ const config = await parseProjectConfig();
92
+
93
+ const items: PromptObject[] = [
94
+ {
95
+ type() {
96
+ return config.mono ? null : 'select';
97
+ },
98
+ name: 'module',
99
+ message: 'Module:',
100
+ choices() {
101
+ return Object.keys(config.modules ?? {}).map((moduleName) => ({
102
+ title: moduleName,
103
+ value: moduleName
104
+ }));
105
+ }
106
+ },
107
+ {
108
+ type: 'text',
109
+ name: 'command',
110
+ message: 'Command Name:',
111
+ initial: 'user:say',
112
+ validate(value: string) {
113
+ if (value.length < 1) {
114
+ return 'Command name cannot be empty';
115
+ }
116
+
117
+ return true;
118
+ }
119
+ },
120
+ {
121
+ type: 'text',
122
+ name: 'description',
123
+ message: 'Command Description:',
124
+ initial: 'Say something',
125
+ validate(value: string) {
126
+ if (value.length < 1) {
127
+ return 'Command description cannot be empty';
128
+ }
129
+
130
+ return true;
131
+ }
132
+ }
133
+ ];
134
+
135
+ const answers = await prompts(items, {
136
+ onCancel: () => {
137
+ this.error('Operation cancelled.');
138
+ process.exit(0);
139
+ }
140
+ });
141
+
142
+ answers.name = pascal(answers.command.replaceAll(':', '_'));
143
+
144
+ return this.build(answers);
145
+ }
146
+
147
+ private async build(answers: Record<string, any>): Promise<void> {
148
+ if (!answers.name) {
149
+ this.error('Operation cancelled.');
150
+ return;
151
+ }
152
+
153
+ if (!answers.name.endsWith('Command')) {
154
+ answers.name += 'Command';
155
+ }
156
+
157
+ // Get stub file
158
+ const stubFile = Bun.file(join(getRootPath(), 'stubs/command.stub'));
159
+
160
+ // Format the stub content
161
+ const stub = formatter(await stubFile.text(), answers);
162
+
163
+ const path = answers.module ? await getModulePath(answers.module) : './src';
164
+
165
+ // Write to file
166
+ const name = snake(answers.name.replace('Command', ''));
167
+ const file = Bun.file(`${path}/commands/${name}.command.ts`);
168
+ await file.write(stub);
169
+
170
+ this.success(`Command ${this.bold(file.name!)} created successfully.`);
171
+ }
172
+ }
@@ -0,0 +1,277 @@
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 type { PromptObject } from 'prompts';
16
+
17
+ import { join } from 'node:path';
18
+
19
+ import { Command, CommandArgumentType } from '@elysiumjs/core';
20
+ import prompts from 'prompts';
21
+ import { snake } from 'radash';
22
+ import formatter from 'string-template';
23
+
24
+ import { getModulePath, parseProjectConfig } from '../config';
25
+ import { getRootPath } from '../utils';
26
+
27
+ /**
28
+ * Maker class for creating Elysium.js controllers.
29
+ * @author Axel Nana <axel.nana@workbud.com>
30
+ */
31
+ export class MakeControllerCommand extends Command {
32
+ public static readonly command: string = 'make:controller';
33
+ public static readonly description: string = 'Creates a new controller.';
34
+
35
+ @Command.arg({
36
+ description: 'The name of the controller to create',
37
+ type: CommandArgumentType.STRING
38
+ })
39
+ private name?: string;
40
+
41
+ @Command.arg({
42
+ description: 'The module where the controller will be created',
43
+ type: CommandArgumentType.STRING
44
+ })
45
+ private module?: string;
46
+
47
+ @Command.arg({
48
+ description: 'Whether to create a HTTP controller',
49
+ type: CommandArgumentType.BOOLEAN,
50
+ default: false
51
+ })
52
+ private http: boolean = false;
53
+
54
+ @Command.arg({
55
+ description: 'Whether to create a WAMP controller',
56
+ type: CommandArgumentType.BOOLEAN,
57
+ default: false
58
+ })
59
+ private wamp: boolean = false;
60
+
61
+ @Command.arg({
62
+ description: 'Whether to create a WebSocket controller',
63
+ type: CommandArgumentType.BOOLEAN,
64
+ default: false
65
+ })
66
+ private ws: boolean = false;
67
+
68
+ @Command.arg({
69
+ description: 'Whether to create a SERVER scope controller',
70
+ type: CommandArgumentType.BOOLEAN,
71
+ default: false
72
+ })
73
+ private server: boolean = false;
74
+
75
+ @Command.arg({
76
+ description: 'Whether to create a REQUEST scope controller',
77
+ type: CommandArgumentType.BOOLEAN,
78
+ default: false
79
+ })
80
+ private request: boolean = false;
81
+
82
+ @Command.arg({
83
+ description: 'The path or URL of the controller',
84
+ type: CommandArgumentType.STRING
85
+ })
86
+ private path: string = '/users';
87
+
88
+ @Command.arg({
89
+ description: 'The realm of the controller (only applicable to WAMP controllers)',
90
+ type: CommandArgumentType.STRING
91
+ })
92
+ private realm?: string;
93
+
94
+ public async run(): Promise<void> {
95
+ if (!this.name) {
96
+ return this.setup();
97
+ }
98
+
99
+ const config = await parseProjectConfig();
100
+
101
+ const answers: Record<string, any> = {
102
+ module: this.module,
103
+ type: 'http',
104
+ scope: 'SERVER',
105
+ name: this.name,
106
+ path: this.path,
107
+ realm: this.realm ?? 'realm1'
108
+ };
109
+
110
+ if (!answers.module && !config.mono) {
111
+ const module = await prompts({
112
+ type: 'select',
113
+ name: 'module',
114
+ message: 'Module:',
115
+ choices: Object.keys(config.modules ?? {}).map((moduleName) => ({
116
+ title: moduleName,
117
+ value: moduleName
118
+ }))
119
+ });
120
+
121
+ answers.module = module.module;
122
+ }
123
+
124
+ if (this.http) {
125
+ answers.type = 'http';
126
+ } else if (this.wamp) {
127
+ answers.type = 'wamp';
128
+ } else if (this.ws) {
129
+ answers.type = 'ws';
130
+ }
131
+
132
+ if (this.server) {
133
+ answers.scope = 'SERVER';
134
+ } else if (this.request) {
135
+ answers.scope = 'REQUEST';
136
+ }
137
+
138
+ return this.build(answers);
139
+ }
140
+
141
+ private async setup(): Promise<void> {
142
+ let mode: 'http' | 'wamp' | 'ws' = 'http';
143
+
144
+ const config = await parseProjectConfig();
145
+
146
+ const items: PromptObject[] = [
147
+ {
148
+ type() {
149
+ return config.mono ? null : 'select';
150
+ },
151
+ name: 'module',
152
+ message: 'Module:',
153
+ choices() {
154
+ return Object.keys(config.modules ?? {}).map((moduleName) => ({
155
+ title: moduleName,
156
+ value: moduleName
157
+ }));
158
+ }
159
+ },
160
+ {
161
+ type: 'select',
162
+ name: 'type',
163
+ message: 'Controller Type:',
164
+ choices: [
165
+ { title: 'HTTP', value: 'http' },
166
+ { title: 'WAMP', value: 'wamp' },
167
+ { title: 'WebSocket', value: 'ws' }
168
+ ]
169
+ },
170
+ {
171
+ type: 'text',
172
+ name: 'name',
173
+ message: 'Controller Name:',
174
+ initial: 'UserController',
175
+ validate: (value: string) => {
176
+ if (value.length < 1) {
177
+ return 'Controller name cannot be empty';
178
+ }
179
+
180
+ return true;
181
+ }
182
+ },
183
+ {
184
+ type: 'text',
185
+ name: 'path',
186
+ message(_prev, values) {
187
+ mode = values.type;
188
+ return values.type === 'wamp' ? 'Controller URL' : 'Controller Path:';
189
+ },
190
+ initial(_prev, values) {
191
+ return values.type === 'wamp' ? 'ws://localhost:8000' : '/users';
192
+ },
193
+ validate(value) {
194
+ if (mode === 'wamp') {
195
+ return value.match(/^wss?:\/\//) ? true : 'The URL must start with ws:// or wss://';
196
+ }
197
+
198
+ if (value[0] !== '/') {
199
+ return 'The path must start with a slash (/)';
200
+ }
201
+
202
+ return true;
203
+ }
204
+ },
205
+ {
206
+ type(_prev, values) {
207
+ return values.type === 'http' ? 'select' : null;
208
+ },
209
+ name: 'scope',
210
+ message: 'Controller Scope:',
211
+ choices: [
212
+ {
213
+ title: 'SERVER',
214
+ value: 'SERVER',
215
+ description:
216
+ 'An unique instance of the controller is created for the entire lifecycle of the server.'
217
+ },
218
+ {
219
+ title: 'REQUEST',
220
+ value: 'REQUEST',
221
+ description: 'An instance of the controller is created for each request.'
222
+ }
223
+ ]
224
+ },
225
+ {
226
+ type(_prev, values) {
227
+ return values.type === 'wamp' ? 'text' : null;
228
+ },
229
+ name: 'realm',
230
+ message: 'Controller Realm:',
231
+ initial: 'realm1',
232
+ validate(value) {
233
+ if (value.length < 1) {
234
+ return 'Realm name cannot be empty';
235
+ }
236
+
237
+ return true;
238
+ }
239
+ }
240
+ ];
241
+
242
+ const answers = await prompts(items, {
243
+ onCancel: () => {
244
+ this.error('Operation cancelled.');
245
+ process.exit(0);
246
+ }
247
+ });
248
+
249
+ return this.build(answers);
250
+ }
251
+
252
+ private async build(answers: Record<string, any>): Promise<void> {
253
+ if (!answers.type || !answers.name || !answers.path) {
254
+ this.error('Operation cancelled.');
255
+ return;
256
+ }
257
+
258
+ if (!answers.name.endsWith('Controller')) {
259
+ answers.name += 'Controller';
260
+ }
261
+
262
+ // Get stub file
263
+ const stubFile = Bun.file(join(getRootPath(), `stubs/${answers.type}.controller.stub`));
264
+
265
+ // Format the stub content
266
+ const stub = formatter(await stubFile.text(), answers);
267
+
268
+ const path = answers.module ? await getModulePath(answers.module) : './src';
269
+
270
+ // Write to file
271
+ const name = snake(answers.name.replace('Controller', ''));
272
+ const file = Bun.file(`${path}/controllers/${answers.type}/${name}.controller.ts`);
273
+ await file.write(stub);
274
+
275
+ this.success(`Controller ${this.bold(file.name!)} created successfully.`);
276
+ }
277
+ }