tshex-cli 1.0.14 → 1.0.16

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.
Files changed (32) hide show
  1. package/build/main.js +54 -5
  2. package/package.json +1 -1
  3. package/readme.md +180 -27
  4. package/source/main.ts +61 -10
  5. package/templates/ctx/index.ts +0 -9
  6. package/templates/react-context/adapters/hooks/.gitkeep +0 -0
  7. package/templates/react-context/adapters/schemas/.gitkeep +0 -0
  8. package/templates/react-context/application/.gitkeep +0 -0
  9. package/templates/react-context/domain/.gitkeep +0 -0
  10. package/templates/react-context/languages/.gitkeep +0 -0
  11. package/templates/react-project/core/context/adapters/api/.gitkeep +0 -0
  12. package/templates/react-project/core/context/adapters/hooks/.gitkeep +0 -0
  13. package/templates/react-project/core/context/adapters/schemas/.gitkeep +0 -0
  14. package/templates/react-project/core/context/application/.gitkeep +0 -0
  15. package/templates/react-project/core/context/domain/.gitkeep +0 -0
  16. package/templates/react-project/core/context/languages/.gitkeep +0 -0
  17. package/templates/react-project/core/shared/adapters/i18n/.gitkeep +0 -0
  18. package/templates/react-project/dom-client/context/Example.tsx +11 -0
  19. package/templates/react-project/dom-client/context/assets/.gitkeep +0 -0
  20. package/templates/react-project/dom-client/context/styles/.gitkeep +0 -0
  21. package/templates/react-project/dom-server/context/Example.tsx +11 -0
  22. package/templates/react-project/dom-server/context/assets/.gitkeep +0 -0
  23. package/templates/react-project/dom-server/context/styles/.gitkeep +0 -0
  24. package/templates/react-project/index.d.ts +1 -0
  25. package/templates/react-project/native/context/Example.tsx +11 -0
  26. package/templates/react-project/native/context/assets/.gitkeep +0 -0
  27. package/templates/react-project/native/context/styles/.gitkeep +0 -0
  28. package/templates/react-project/tests/core/.gitkeep +0 -0
  29. package/templates/react-project/tests/dom-client/.gitkeep +0 -0
  30. package/templates/react-project/tests/dom-server/.gitkeep +0 -0
  31. package/templates/react-project/tests/native/.gitkeep +0 -0
  32. /package/{source/index.d.ts → templates/react-context/adapters/api/.gitkeep} +0 -0
package/build/main.js CHANGED
@@ -3,13 +3,15 @@ import { program } from 'commander';
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  function readPackageJson() {
6
- return JSON.parse(fs.readFileSync(path.join(import.meta.dirname, '..', 'package.json'), 'utf-8'));
6
+ const filePath = path.join(import.meta.dirname, '..', 'package.json');
7
+ const fileContents = fs.readFileSync(filePath, 'utf-8');
8
+ return JSON.parse(fileContents);
7
9
  }
8
10
  function executeCreateLib(templatesDir, libraryDir) {
9
11
  try {
10
12
  fs.cpSync(path.join(templatesDir, 'lib'), libraryDir, {
11
13
  recursive: true,
12
- filter: (src) => !src.endsWith('.gitkeep')
14
+ filter: (src) => src.endsWith('.gitkeep') === false
13
15
  });
14
16
  console.log('Library created successfully');
15
17
  }
@@ -21,7 +23,7 @@ function executeCreateContext(templatesDir, contextDir) {
21
23
  try {
22
24
  fs.cpSync(path.join(templatesDir, 'ctx'), contextDir, {
23
25
  recursive: true,
24
- filter: (src) => !src.endsWith('.gitkeep')
26
+ filter: (src) => src.endsWith('.gitkeep') === false
25
27
  });
26
28
  console.log('Context created successfully');
27
29
  }
@@ -29,15 +31,52 @@ function executeCreateContext(templatesDir, contextDir) {
29
31
  console.error(err);
30
32
  }
31
33
  }
34
+ function copyDirectoryContents(sourceDir, destinationDir) {
35
+ const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
36
+ for (const entry of entries) {
37
+ const sourcePath = path.join(sourceDir, entry.name);
38
+ const destinationPath = path.join(destinationDir, entry.name);
39
+ if (fs.existsSync(destinationPath) === false) {
40
+ fs.cpSync(sourcePath, destinationPath, {
41
+ recursive: true,
42
+ filter: (src) => src.endsWith('.gitkeep') === false
43
+ });
44
+ }
45
+ }
46
+ }
47
+ function executeCreateReactProject(templatesDir, projectDir) {
48
+ try {
49
+ fs.cpSync(path.join(templatesDir, 'react-project'), projectDir, {
50
+ recursive: true,
51
+ filter: (src) => src.endsWith('.gitkeep') === false
52
+ });
53
+ copyDirectoryContents(path.join(templatesDir, 'lib', 'shared'), path.join(projectDir, 'core', 'shared'));
54
+ console.log('React project created successfully');
55
+ }
56
+ catch (err) {
57
+ console.error(err);
58
+ }
59
+ }
60
+ function executeCreateReactContext(templatesDir, contextDir) {
61
+ try {
62
+ fs.cpSync(path.join(templatesDir, 'react-context'), contextDir, {
63
+ recursive: true,
64
+ filter: (src) => src.endsWith('.gitkeep') === false
65
+ });
66
+ console.log('React context created successfully');
67
+ }
68
+ catch (err) {
69
+ console.error(err);
70
+ }
71
+ }
32
72
  function main(program) {
33
73
  const templatesDir = path.join(import.meta.dirname, '..', 'templates');
34
74
  const options = program.opts();
35
75
  let targetDir = path.resolve(options.dir ?? process.cwd());
36
76
  if (Object.keys(options).length === 0) {
37
77
  program.help();
38
- return;
39
78
  }
40
- if (!fs.existsSync(targetDir)) {
79
+ if (fs.existsSync(targetDir) === false) {
41
80
  fs.mkdirSync(targetDir, { recursive: true });
42
81
  }
43
82
  if (options.lib !== undefined) {
@@ -48,6 +87,14 @@ function main(program) {
48
87
  targetDir = path.join(targetDir, options.ctx);
49
88
  executeCreateContext(templatesDir, targetDir);
50
89
  }
90
+ if (options.reactProject !== undefined) {
91
+ targetDir = path.join(targetDir, options.reactProject);
92
+ executeCreateReactProject(templatesDir, targetDir);
93
+ }
94
+ if (options.reactContext !== undefined) {
95
+ targetDir = path.join(targetDir, options.reactContext);
96
+ executeCreateReactContext(templatesDir, targetDir);
97
+ }
51
98
  }
52
99
  const packageJson = readPackageJson();
53
100
  program
@@ -56,5 +103,7 @@ program
56
103
  .option('--lib <name>', "creates a new library with it's shared directory")
57
104
  .option('--ctx <name>', 'creates a new context')
58
105
  .option('--dir <path>', 'sets the directory to create the new item')
106
+ .option('--react-project <name>', 'creates a new React project')
107
+ .option('--react-context <name>', 'creates a new React context')
59
108
  .parse(process.argv);
60
109
  main(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tshex-cli",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "author": "https://github.com/virtualitems/",
5
5
  "description": "Typescript Hexagonal Architecture CLI",
6
6
  "type": "module",
package/readme.md CHANGED
@@ -4,15 +4,11 @@ This CLI will help you to generate a hexagonal architecture structure.
4
4
 
5
5
  # Why?
6
6
 
7
- Hexagonal architecture is a software design pattern that separates the internal domain of the application from the external dependencies.. This separation is achieved by dividing the application into layers. Each layer has a specific responsibility and interacts with the other layers in a specific way.
7
+ Hexagonal architecture is a software design pattern that separates the internal domain of the application from the external dependencies. This separation is achieved by dividing the application into layers. Each layer has a specific responsibility and interacts with the other layers in a specific way.
8
8
 
9
- Because what we are trying to achieve is a separation between the domain code and the installed dependencies, a Hexagonal Architecture Framework is a step in the opposite direction, as it couples the domain code with the framework. This is why we need a tool to help us to create the structure with our own codebase.
9
+ The goal is to separate the domain code from installed dependencies. A Hexagonal Architecture _framework_ would work against that goal, since it couples the domain code to the framework itself. That is why this CLI exists: it scaffolds the structure for you, using your own codebase instead of a framework.
10
10
 
11
- # Usage example
12
-
13
- https://github.com/virtualitems/tshex-cli
14
-
15
- # Note!!!
11
+ # Note
16
12
 
17
13
  This tool and its templates are in constant development, so be aware that some changes may occur and be careful when updating the tool.
18
14
 
@@ -20,57 +16,214 @@ If you have any suggestions or improvements, please let me know.
20
16
 
21
17
  https://github.com/virtualitems/tshex-cli/issues
22
18
 
23
- # Installation
19
+ # Getting started
20
+
21
+ `tshex` scaffolds folders and files for a hexagonal architecture project. It does not write business logic for you; it only generates the structure, so you can fill it in with your own code.
22
+
23
+ There are four things you can generate:
24
+
25
+ - A **library** (`--lib`): the shared foundation of a project. It contains the domain and application contracts (entities, value objects, services, etc.) that the rest of the code depends on.
26
+ - A **context** (`--ctx`): a specific business area (e.g. `users`, `billing`, `orders`). It contains the `domain/`, `application/`, and `adapters/` folders where you implement that area's logic.
27
+ - A **React project** (`--react-project`): a full React-oriented hexagonal structure with `core/`, `dom-client/`, `dom-server/`, and `native/` folders.
28
+ - A **React context** (`--react-context`): a React-ready context scaffold.
29
+
30
+ A typical project has one library and one or more contexts inside it.
31
+
32
+ ## Install
33
+
34
+ Install the CLI globally:
24
35
 
25
36
  ```bash
26
37
  npm install -g tshex-cli
27
38
  ```
28
39
 
29
- # Usage
40
+ Check the installation:
41
+
42
+ ```bash
43
+ tshex --version
44
+ ```
45
+
46
+ Show the command options:
30
47
 
31
- > Remember to replace placeholders `<...>` with the actual data.
32
- >
48
+ ```bash
49
+ tshex --help
50
+ ```
33
51
 
34
- ## Getting started
52
+ ## Usage
35
53
 
36
54
  ```bash
37
- tshex --lib <library-name> --ctx <context-name>
55
+ tshex [--lib <name>] [--ctx <name>] [--react-project <name>] [--react-context <name>] [--dir <path>]
38
56
  ```
39
57
 
40
- ## Help
58
+ | Option | Purpose |
59
+ | -------------- | ------------------------------------------------------------------- |
60
+ | `--lib <name>` | Generate a library called `<name>`, based on `templates/lib`. |
61
+ | `--ctx <name>` | Generate a context called `<name>`, based on `templates/ctx`. |
62
+ | `--react-project <name>` | Generate a React project called `<name>`, based on `templates/react-project` and `templates/lib/shared`. |
63
+ | `--react-context <name>` | Generate a React context called `<name>`, based on `templates/react-context`. |
64
+ | `--dir <path>` | Parent directory where the library/context will be created. Defaults to the current directory. |
65
+ | `--help` | Show the command options. |
66
+ | `--version` | Show the installed version. |
67
+
68
+ `--lib`, `--ctx`, `--react-project`, and `--react-context` can be used together in a single command. Running `tshex` without any options just prints the help text — it does not generate anything.
69
+
70
+ ## Create a library
71
+
72
+ Use this when you are starting a new project and need the shared contracts that the rest of your code will build on:
41
73
 
42
74
  ```bash
43
- tshex -h
75
+ tshex --lib core
76
+ ```
44
77
 
45
- tshex --help
78
+ This generates a `core/` folder in the current directory:
79
+
80
+ ```text
81
+ core/
82
+ ├── index.d.ts
83
+ ├── main.ts
84
+ └── shared/
85
+ ├── application/
86
+ │ ├── databases.ts
87
+ │ ├── events.ts
88
+ │ ├── http.ts
89
+ │ ├── loggers.ts
90
+ │ └── services.ts
91
+ └── domain/
92
+ ├── aggregates.ts
93
+ ├── entities.ts
94
+ ├── errors.ts
95
+ └── value-objects.ts
96
+ ```
97
+
98
+ What each file is for:
99
+
100
+ | File | Purpose |
101
+ | ------------------ | --------------------------------------------- |
102
+ | `index.d.ts` | Declare library types. |
103
+ | `main.ts` | Export the library API. |
104
+ | `entities.ts` | Define entity contracts. |
105
+ | `value-objects.ts` | Define value objects and validation. |
106
+ | `aggregates.ts` | Define aggregate contracts. |
107
+ | `errors.ts` | Define domain errors. |
108
+ | `services.ts` | Define application service contracts. |
109
+ | `databases.ts` | Define data manager and repository contracts. |
110
+ | `events.ts` | Define event contracts. |
111
+ | `http.ts` | Define HTTP response contracts. |
112
+ | `loggers.ts` | Define logger contracts. |
113
+
114
+ `main.ts` is generated with a placeholder `throw new Error('Not implemented yet')`, so the library fails loudly if you try to use it before filling it in. To start using it, open `main.ts`, remove that line, and export your own code, built on top of the shared contracts. For example:
115
+
116
+ ```ts
117
+ export { Entity } from './shared/domain/entities.js'
118
+ export { ValueObject, Email } from './shared/domain/value-objects.js'
119
+
120
+ class User extends Entity {
121
+ constructor(public readonly email: Email) {
122
+ super()
123
+ }
124
+ }
46
125
  ```
47
126
 
48
- ## Create a new library
127
+ ## Create a context
128
+
129
+ Use this to scaffold a specific business area, such as `users`, inside an existing library (or on its own):
49
130
 
50
131
  ```bash
51
- tshex --lib <name>
132
+ tshex --ctx users
133
+ ```
134
+
135
+ This generates a `users/` folder in the current directory:
136
+
137
+ ```text
138
+ users/
139
+ ├── adapters/
140
+ ├── application/
141
+ ├── domain/
142
+ └── index.ts
52
143
  ```
53
144
 
54
- ## Create a new context
145
+ What each part is for:
146
+
147
+ | Path | Purpose |
148
+ | -------------- | ---------------------------------------------------------------- |
149
+ | `domain/` | Entities, value objects, aggregates, and domain errors. |
150
+ | `application/` | Use cases, services, DTOs, and contracts. |
151
+ | `adapters/` | Database, HTTP, event, file, and service implementations. |
152
+ | `index.ts` | Export or compose the context's public API. |
153
+
154
+ ## Choose the destination folder
155
+
156
+ By default, `tshex` creates files in your current directory. Use `--dir` to choose a different parent directory instead.
157
+
158
+ Create a library inside `./src`:
55
159
 
56
160
  ```bash
57
- tshex --ctx <name>
161
+ tshex --lib core --dir ./src
58
162
  ```
59
163
 
60
- ## Create a new class
164
+ This generates the library at:
165
+
166
+ ```text
167
+ ./src/core
168
+ ```
169
+
170
+ Create a context inside that library:
61
171
 
62
172
  ```bash
63
- tshex --cls <name>
173
+ tshex --ctx users --dir ./src/core
174
+ ```
175
+
176
+ This generates the context at:
177
+
178
+ ```text
179
+ ./src/core/users
64
180
  ```
65
181
 
66
- ## Create a new react functional component
182
+ You can also use an absolute path:
67
183
 
68
184
  ```bash
69
- tshex --rfc <name>
185
+ tshex --ctx users --dir /workspace/project/src
70
186
  ```
71
187
 
72
- ## Set the directory to create the new items
188
+ ## Create a library and a context together
189
+
190
+ You can generate both in a single command. `tshex` creates the library first, then creates the context inside it:
73
191
 
74
192
  ```bash
75
- tshex --lib <lib-name> --ctx <ctx-name> --dir <path>
76
- ```
193
+ tshex --dir ./src --lib core --ctx users
194
+ ```
195
+
196
+ Result:
197
+
198
+ ```text
199
+ src/
200
+ └── core/
201
+ ├── index.d.ts
202
+ ├── main.ts
203
+ ├── shared/
204
+ └── users/
205
+ ├── adapters/
206
+ ├── application/
207
+ ├── domain/
208
+ └── index.ts
209
+ ```
210
+
211
+ ## Create a React project
212
+
213
+ Use this when you want the React-specific project scaffold with the shared `core/shared` contracts copied from the library template:
214
+
215
+ ```bash
216
+ tshex --react-project app
217
+ ```
218
+
219
+ This generates a `app/` folder in the current directory.
220
+
221
+ ## Create a React context
222
+
223
+ Use this when you want only the React context scaffold:
224
+
225
+ ```bash
226
+ tshex --react-context users
227
+ ```
228
+
229
+ This generates a `users/` folder in the current directory.
package/source/main.ts CHANGED
@@ -12,19 +12,16 @@ import path from 'node:path'
12
12
  // FUNCTIONS
13
13
 
14
14
  function readPackageJson() {
15
- return JSON.parse(
16
- fs.readFileSync(
17
- path.join(import.meta.dirname, '..', 'package.json'),
18
- 'utf-8'
19
- )
20
- )
15
+ const filePath = path.join(import.meta.dirname, '..', 'package.json')
16
+ const fileContents = fs.readFileSync(filePath, 'utf-8')
17
+ return JSON.parse(fileContents)
21
18
  }
22
19
 
23
20
  function executeCreateLib(templatesDir: string, libraryDir: string) {
24
21
  try {
25
22
  fs.cpSync(path.join(templatesDir, 'lib'), libraryDir, {
26
23
  recursive: true,
27
- filter: (src) => !src.endsWith('.gitkeep')
24
+ filter: (src) => src.endsWith('.gitkeep') === false
28
25
  })
29
26
  console.log('Library created successfully')
30
27
  } catch (err) {
@@ -36,7 +33,7 @@ function executeCreateContext(templatesDir: string, contextDir: string) {
36
33
  try {
37
34
  fs.cpSync(path.join(templatesDir, 'ctx'), contextDir, {
38
35
  recursive: true,
39
- filter: (src) => !src.endsWith('.gitkeep')
36
+ filter: (src) => src.endsWith('.gitkeep') === false
40
37
  })
41
38
  console.log('Context created successfully')
42
39
  } catch (err) {
@@ -44,6 +41,49 @@ function executeCreateContext(templatesDir: string, contextDir: string) {
44
41
  }
45
42
  }
46
43
 
44
+ function copyDirectoryContents(sourceDir: string, destinationDir: string) {
45
+ const entries = fs.readdirSync(sourceDir, { withFileTypes: true })
46
+
47
+ for (const entry of entries) {
48
+ const sourcePath = path.join(sourceDir, entry.name)
49
+ const destinationPath = path.join(destinationDir, entry.name)
50
+
51
+ if (fs.existsSync(destinationPath) === false) {
52
+ fs.cpSync(sourcePath, destinationPath, {
53
+ recursive: true,
54
+ filter: (src) => src.endsWith('.gitkeep') === false
55
+ })
56
+ }
57
+ }
58
+ }
59
+
60
+ function executeCreateReactProject(templatesDir: string, projectDir: string) {
61
+ try {
62
+ fs.cpSync(path.join(templatesDir, 'react-project'), projectDir, {
63
+ recursive: true,
64
+ filter: (src) => src.endsWith('.gitkeep') === false
65
+ })
66
+
67
+ copyDirectoryContents(path.join(templatesDir, 'lib', 'shared'), path.join(projectDir, 'core', 'shared'))
68
+
69
+ console.log('React project created successfully')
70
+ } catch (err) {
71
+ console.error(err)
72
+ }
73
+ }
74
+
75
+ function executeCreateReactContext(templatesDir: string, contextDir: string) {
76
+ try {
77
+ fs.cpSync(path.join(templatesDir, 'react-context'), contextDir, {
78
+ recursive: true,
79
+ filter: (src) => src.endsWith('.gitkeep') === false
80
+ })
81
+ console.log('React context created successfully')
82
+ } catch (err) {
83
+ console.error(err)
84
+ }
85
+ }
86
+
47
87
  function main(program: typeof import('commander').program) {
48
88
  const templatesDir = path.join(import.meta.dirname, '..', 'templates')
49
89
 
@@ -53,10 +93,9 @@ function main(program: typeof import('commander').program) {
53
93
 
54
94
  if (Object.keys(options).length === 0) {
55
95
  program.help()
56
- return
57
96
  }
58
97
 
59
- if (!fs.existsSync(targetDir)) {
98
+ if (fs.existsSync(targetDir) === false) {
60
99
  fs.mkdirSync(targetDir, { recursive: true })
61
100
  }
62
101
 
@@ -69,6 +108,16 @@ function main(program: typeof import('commander').program) {
69
108
  targetDir = path.join(targetDir, options.ctx)
70
109
  executeCreateContext(templatesDir, targetDir)
71
110
  }
111
+
112
+ if (options.reactProject !== undefined) {
113
+ targetDir = path.join(targetDir, options.reactProject)
114
+ executeCreateReactProject(templatesDir, targetDir)
115
+ }
116
+
117
+ if (options.reactContext !== undefined) {
118
+ targetDir = path.join(targetDir, options.reactContext)
119
+ executeCreateReactContext(templatesDir, targetDir)
120
+ }
72
121
  }
73
122
 
74
123
  const packageJson = readPackageJson()
@@ -79,6 +128,8 @@ program
79
128
  .option('--lib <name>', "creates a new library with it's shared directory")
80
129
  .option('--ctx <name>', 'creates a new context')
81
130
  .option('--dir <path>', 'sets the directory to create the new item')
131
+ .option('--react-project <name>', 'creates a new React project')
132
+ .option('--react-context <name>', 'creates a new React context')
82
133
  .parse(process.argv)
83
134
 
84
135
  main(program)
@@ -1,9 +0,0 @@
1
- // Ports layer
2
-
3
- // Adapters layer
4
-
5
- // Application layer
6
-
7
- // Domain layer
8
-
9
- // Constants
File without changes
File without changes
File without changes
@@ -0,0 +1,11 @@
1
+ type Props = {
2
+ children?: React.ReactNode
3
+ }
4
+
5
+ /**
6
+ * @description Example component that renders its children.
7
+ */
8
+ export default function Example(props: Props) {
9
+ const { children } = props
10
+ return children
11
+ }
@@ -0,0 +1,11 @@
1
+ type Props = {
2
+ children?: React.ReactNode
3
+ }
4
+
5
+ /**
6
+ * @description Example component that renders its children.
7
+ */
8
+ export default function Example(props: Props) {
9
+ const { children } = props
10
+ return children
11
+ }
@@ -0,0 +1 @@
1
+ type Generic<T=unknown> = Record<string, T>;
@@ -0,0 +1,11 @@
1
+ type Props = {
2
+ children?: React.ReactNode
3
+ }
4
+
5
+ /**
6
+ * @description Example component that renders its children.
7
+ */
8
+ export default function Example(props: Props) {
9
+ const { children } = props
10
+ return children
11
+ }
File without changes
File without changes