@elysiumjs/styx 0.2.0 → 0.4.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
@@ -60,4 +60,4 @@ Feel free to open issues for any bugs or questions you may have. Pull requests a
60
60
 
61
61
  ## License
62
62
 
63
- This project is licensed under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for more information.
63
+ This project is licensed under the Apache License, Version 2.0. See the [LICENSE](../../LICENSE) file for more information.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elysiumjs/styx",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The CLI tool for the Elysium framework.",
6
6
  "private": false,
@@ -0,0 +1,167 @@
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 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
+
137
+ answers.name = pascal(answers.command.replaceAll(':', '_'));
138
+
139
+ return this.build(answers);
140
+ }
141
+
142
+ private async build(answers: Record<string, any>): Promise<void> {
143
+ if (!answers.name) {
144
+ this.error('Operation cancelled.');
145
+ return;
146
+ }
147
+
148
+ if (!answers.name.endsWith('Command')) {
149
+ answers.name += 'Command';
150
+ }
151
+
152
+ // Get stub file
153
+ const stubFile = Bun.file(join(getRootPath(), 'stubs/command.stub'));
154
+
155
+ // Format the stub content
156
+ const stub = formatter(await stubFile.text(), answers);
157
+
158
+ const path = answers.module ? await getModulePath(answers.module) : './src';
159
+
160
+ // Write to file
161
+ const name = snake(answers.name.replace('Command', ''));
162
+ const file = Bun.file(`${path}/commands/${name}.command.ts`);
163
+ await file.write(stub);
164
+
165
+ this.success(`Command ${this.bold(file.name!)} created successfully.`);
166
+ }
167
+ }
@@ -0,0 +1,272 @@
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 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
+
244
+ return this.build(answers);
245
+ }
246
+
247
+ private async build(answers: Record<string, any>): Promise<void> {
248
+ if (!answers.type || !answers.name || !answers.path) {
249
+ this.error('Operation cancelled.');
250
+ return;
251
+ }
252
+
253
+ if (!answers.name.endsWith('Controller')) {
254
+ answers.name += 'Controller';
255
+ }
256
+
257
+ // Get stub file
258
+ const stubFile = Bun.file(join(getRootPath(), `stubs/${answers.type}.controller.stub`));
259
+
260
+ // Format the stub content
261
+ const stub = formatter(await stubFile.text(), answers);
262
+
263
+ const path = answers.module ? await getModulePath(answers.module) : './src';
264
+
265
+ // Write to file
266
+ const name = snake(answers.name.replace('Controller', ''));
267
+ const file = Bun.file(`${path}/controllers/${answers.type}/${name}.controller.ts`);
268
+ await file.write(stub);
269
+
270
+ this.success(`Controller ${this.bold(file.name!)} created successfully.`);
271
+ }
272
+ }
@@ -0,0 +1,178 @@
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 jobs.
29
+ * @author Axel Nana <axel.nana@workbud.com>
30
+ */
31
+ export class MakeJobCommand extends Command {
32
+ public static readonly command: string = 'make:job';
33
+ public static readonly description: string = 'Creates a new job.';
34
+
35
+ @Command.arg({
36
+ description: 'The name of the job to create',
37
+ type: CommandArgumentType.STRING
38
+ })
39
+ private name?: string;
40
+
41
+ @Command.arg({
42
+ description: 'The module where the job will be created',
43
+ type: CommandArgumentType.STRING
44
+ })
45
+ private module?: string;
46
+
47
+ @Command.arg({
48
+ description: 'The alias of the job to create',
49
+ type: CommandArgumentType.STRING
50
+ })
51
+ private alias?: string;
52
+
53
+ @Command.arg({
54
+ description: 'The name of the queue where the job will be dispatched',
55
+ type: CommandArgumentType.STRING,
56
+ default: 'default'
57
+ })
58
+ private queue: string = 'default';
59
+
60
+ public async run(): Promise<void> {
61
+ if (!this.name) {
62
+ return this.setup();
63
+ }
64
+
65
+ const config = await parseProjectConfig();
66
+
67
+ const answers: Record<string, any> = {
68
+ module: this.module,
69
+ name: this.name,
70
+ alias: this.alias,
71
+ queue: this.queue ?? 'default'
72
+ };
73
+
74
+ if (!answers.module && !config.mono) {
75
+ const module = await prompts({
76
+ type: 'select',
77
+ name: 'module',
78
+ message: 'Module:',
79
+ choices: Object.keys(config.modules ?? {}).map((moduleName) => ({
80
+ title: moduleName,
81
+ value: moduleName
82
+ }))
83
+ });
84
+
85
+ answers.module = module.module;
86
+ }
87
+
88
+ return this.build(answers);
89
+ }
90
+
91
+ private async setup(): Promise<void> {
92
+ const config = await parseProjectConfig();
93
+
94
+ const items: PromptObject[] = [
95
+ {
96
+ type() {
97
+ return config.mono ? null : 'select';
98
+ },
99
+ name: 'module',
100
+ message: 'Module:',
101
+ choices() {
102
+ return Object.keys(config.modules ?? {}).map((moduleName) => ({
103
+ title: moduleName,
104
+ value: moduleName
105
+ }));
106
+ }
107
+ },
108
+ {
109
+ type: 'text',
110
+ name: 'name',
111
+ message: 'Job Name:',
112
+ initial: 'SendEmailJob',
113
+ validate(value: string) {
114
+ if (value.length < 1) {
115
+ return 'Job name cannot be empty';
116
+ }
117
+
118
+ return true;
119
+ }
120
+ },
121
+ {
122
+ type: 'text',
123
+ name: 'alias',
124
+ message: 'Job Alias:',
125
+ initial(_, values) {
126
+ return values.name;
127
+ },
128
+ validate(value: string) {
129
+ if (value.length < 1) {
130
+ return 'Job alias cannot be empty';
131
+ }
132
+
133
+ return true;
134
+ }
135
+ },
136
+ {
137
+ type: 'text',
138
+ name: 'queue',
139
+ message: 'Job Queue:',
140
+ initial: 'default'
141
+ }
142
+ ];
143
+
144
+ const answers = await prompts(items);
145
+
146
+ return this.build(answers);
147
+ }
148
+
149
+ private async build(answers: Record<string, any>): Promise<void> {
150
+ if (!answers.name) {
151
+ this.error('Operation cancelled.');
152
+ return;
153
+ }
154
+
155
+ if (!answers.name.endsWith('Job')) {
156
+ answers.name += 'Job';
157
+ }
158
+
159
+ if (!answers.alias) {
160
+ answers.alias = answers.name;
161
+ }
162
+
163
+ // Get stub file
164
+ const stubFile = Bun.file(join(getRootPath(), 'stubs/job.stub'));
165
+
166
+ // Format the stub content
167
+ const stub = formatter(await stubFile.text(), answers);
168
+
169
+ const path = answers.module ? await getModulePath(answers.module) : './src';
170
+
171
+ // Write to file
172
+ const name = snake(answers.name.replace('Job', ''));
173
+ const file = Bun.file(`${path}/jobs/${name}.job.ts`);
174
+ await file.write(stub);
175
+
176
+ this.success(`Job ${this.bold(file.name!)} created successfully.`);
177
+ }
178
+ }