tshex-cli 1.0.28 → 1.0.30

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/build/main.js CHANGED
@@ -1,159 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { program } from 'commander';
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import readline from 'node:readline/promises';
6
- import { stdin as input, stdout as output } from 'node:process';
7
- function readPackageJson() {
8
- const filePath = path.join(import.meta.dirname, '..', 'package.json');
9
- const fileContents = fs.readFileSync(filePath, 'utf-8');
10
- return JSON.parse(fileContents);
11
- }
12
- function executeCreateProject(templatesDir, projectDir) {
13
- try {
14
- fs.cpSync(path.join(templatesDir, 'lib'), projectDir, {
15
- recursive: true,
16
- filter: (src) => src.endsWith('.gitkeep') === false
17
- });
18
- console.log('Project created successfully');
19
- }
20
- catch (err) {
21
- console.error(err);
22
- }
23
- }
24
- function executeCreateContext(templatesDir, contextDir) {
25
- try {
26
- fs.cpSync(path.join(templatesDir, 'ctx'), contextDir, {
27
- recursive: true,
28
- filter: (src) => src.endsWith('.gitkeep') === false
29
- });
30
- console.log('Context created successfully');
31
- }
32
- catch (err) {
33
- console.error(err);
34
- }
35
- }
36
- function executeCreateReactContext(templatesDir, contextDir) {
37
- try {
38
- fs.cpSync(path.join(templatesDir, 'ctx-react'), contextDir, {
39
- recursive: true,
40
- filter: (src) => src.endsWith('.gitkeep') === false
41
- });
42
- console.log('React context created successfully');
43
- }
44
- catch (err) {
45
- console.error(err);
46
- }
47
- }
48
- function executeCreateTests(sourceDir, destinationDir, fileContents, ignoredSourceDir, rootSourceDir = sourceDir) {
49
- const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
50
- if (fs.existsSync(destinationDir) === false) {
51
- fs.mkdirSync(destinationDir, { recursive: true });
52
- }
53
- for (const entry of entries) {
54
- const sourcePath = path.join(sourceDir, entry.name);
55
- const destinationPath = path.join(destinationDir, entry.name);
56
- if (entry.isDirectory()) {
57
- if (entry.name === 'shared' && sourceDir === rootSourceDir) {
58
- continue;
59
- }
60
- if (ignoredSourceDir !== undefined && sourcePath.startsWith(ignoredSourceDir)) {
61
- continue;
62
- }
63
- if (fs.existsSync(destinationPath) && fs.statSync(destinationPath).isDirectory() === false) {
64
- continue;
65
- }
66
- executeCreateTests(sourcePath, destinationPath, fileContents, ignoredSourceDir, rootSourceDir);
67
- continue;
68
- }
69
- if (entry.isFile() && entry.name.endsWith('.ts') && fs.existsSync(destinationPath) === false) {
70
- fs.writeFileSync(destinationPath, fileContents);
71
- }
72
- }
73
- }
74
- async function ensureTestsDirectory(testsRootDir) {
75
- if (fs.existsSync(testsRootDir)) {
76
- if (fs.statSync(testsRootDir).isDirectory() === false) {
77
- throw new Error(`Tests path exists but is not a directory: ${testsRootDir}`);
78
- }
79
- return;
80
- }
81
- const rl = readline.createInterface({ input, output });
82
- try {
83
- const answer = await rl.question(`Tests directory does not exist at ${testsRootDir}. Create it? (y/N) `);
84
- if (answer.trim().toLowerCase() !== 'y') {
85
- return false;
86
- }
87
- }
88
- finally {
89
- rl.close();
90
- }
91
- fs.mkdirSync(testsRootDir, { recursive: true });
92
- return true;
93
- }
94
- async function main(program) {
95
- const templatesDir = path.join(import.meta.dirname, '..', 'templates');
96
- const options = program.opts();
97
- let targetDir = path.resolve(options.dir ?? process.cwd());
98
- if (Object.keys(options).length === 0) {
99
- program.help();
100
- }
101
- if (fs.existsSync(targetDir) === false) {
102
- fs.mkdirSync(targetDir, { recursive: true });
103
- }
104
- if (options.project !== undefined) {
105
- targetDir = path.join(targetDir, options.project);
106
- executeCreateProject(templatesDir, targetDir);
107
- }
108
- if (options.react === true && options.context === undefined) {
109
- program.error('Option --react requires --context <name>');
110
- }
111
- if (options.context !== undefined) {
112
- targetDir = path.join(targetDir, options.context);
113
- if (options.react === true) {
114
- executeCreateReactContext(templatesDir, targetDir);
115
- }
116
- else {
117
- executeCreateContext(templatesDir, targetDir);
118
- }
119
- }
120
- if (options.tests !== undefined) {
121
- const sourceDir = path.resolve(options.tests);
122
- const testsTemplateFile = path.join(templatesDir, 'tests', 'content.ts');
123
- if (fs.existsSync(sourceDir) === false) {
124
- program.error(`Tests source directory does not exist: ${sourceDir}`);
125
- }
126
- if (fs.statSync(sourceDir).isDirectory() === false) {
127
- program.error(`Tests source path is not a directory: ${sourceDir}`);
128
- }
129
- if (fs.existsSync(testsTemplateFile) === false) {
130
- program.error(`Tests template file does not exist: ${testsTemplateFile}`);
131
- }
132
- const testsTemplateContents = fs.readFileSync(testsTemplateFile, 'utf-8');
133
- const testsRootDir = path.join(targetDir, 'tests');
134
- const testsDirectoryCreated = await ensureTestsDirectory(testsRootDir);
135
- if (testsDirectoryCreated === false) {
136
- program.error('Tests directory creation cancelled');
137
- }
138
- const testsDir = path.join(testsRootDir, path.basename(sourceDir));
139
- if (testsDir === sourceDir) {
140
- program.error('Tests destination directory cannot be the same as the source directory');
141
- }
142
- executeCreateTests(sourceDir, testsDir, testsTemplateContents, testsRootDir);
143
- console.log('Tests structure created successfully');
144
- }
145
- }
146
- const packageJson = readPackageJson();
147
- program
148
- .name('tshex')
149
- .version(packageJson.version)
150
- .option('-P, --project <name>', "creates a new project with it's shared directory")
151
- .option('-C, --context <name>', 'creates a new context')
152
- .option('-R, --react', 'creates a React context with --context')
153
- .option('-T, --tests <path>', 'creates a .ts tests structure from an existing directory')
154
- .option('--dir <path>', 'sets the directory to create the new item')
155
- .parse(process.argv);
156
- void main(program).catch((err) => {
157
- const message = err instanceof Error ? err.message : String(err);
158
- program.error(message);
159
- });
2
+ import{program as l}from"commander";import r from"node:fs";import n from"node:path";import p from"node:readline/promises";import{stdin as m,stdout as x}from"node:process";function h(){let e=n.join(import.meta.dirname,"..","package.json"),s=r.readFileSync(e,"utf-8");return JSON.parse(s)}function g(e,s){try{r.cpSync(n.join(e,"lib"),s,{recursive:!0,filter:t=>t.endsWith(".gitkeep")===!1}),console.log("Project created successfully")}catch(t){console.error(t)}}function j(e,s){try{r.cpSync(n.join(e,"ctx"),s,{recursive:!0,filter:t=>t.endsWith(".gitkeep")===!1}),console.log("Context created successfully")}catch(t){console.error(t)}}function S(e,s){try{r.cpSync(n.join(e,"ctx-react"),s,{recursive:!0,filter:t=>t.endsWith(".gitkeep")===!1}),console.log("React context created successfully")}catch(t){console.error(t)}}function d(e,s,t,i,c=e){let u=r.readdirSync(e,{withFileTypes:!0});r.existsSync(s)===!1&&r.mkdirSync(s,{recursive:!0});for(let o of u){let a=n.join(e,o.name),f=n.join(s,o.name);if(o.isDirectory()){if(o.name==="shared"&&e===c||i!==void 0&&a.startsWith(i)||r.existsSync(f)&&r.statSync(f).isDirectory()===!1)continue;d(a,f,t,i,c);continue}o.isFile()&&o.name.endsWith(".ts")&&r.existsSync(f)===!1&&r.writeFileSync(f,t)}}async function w(e){if(r.existsSync(e)){if(r.statSync(e).isDirectory()===!1)throw new Error(`Tests path exists but is not a directory: ${e}`);return}let s=p.createInterface({input:m,output:x});try{if((await s.question(`Tests directory does not exist at ${e}. Create it? (y/N) `)).trim().toLowerCase()!=="y")return!1}finally{s.close()}return r.mkdirSync(e,{recursive:!0}),!0}async function T(e){let s=n.join(import.meta.dirname,"..","templates"),t=e.opts(),i=n.resolve(t.dir??process.cwd());if(Object.keys(t).length===0&&e.help(),r.existsSync(i)===!1&&r.mkdirSync(i,{recursive:!0}),t.project!==void 0&&(i=n.join(i,t.project),g(s,i)),t.react===!0&&t.context===void 0&&e.error("Option --react requires --context <name>"),t.context!==void 0&&(i=n.join(i,t.context),t.react===!0?S(s,i):j(s,i)),t.tests!==void 0){let c=n.resolve(t.tests),u=n.join(s,"tests","content.ts");r.existsSync(c)===!1&&e.error(`Tests source directory does not exist: ${c}`),r.statSync(c).isDirectory()===!1&&e.error(`Tests source path is not a directory: ${c}`),r.existsSync(u)===!1&&e.error(`Tests template file does not exist: ${u}`);let o=r.readFileSync(u,"utf-8"),a=n.join(i,"tests");await w(a)===!1&&e.error("Tests directory creation cancelled");let y=n.join(a,n.basename(c));y===c&&e.error("Tests destination directory cannot be the same as the source directory"),d(c,y,o,a),console.log("Tests structure created successfully")}}var v=h();l.name("tshex").version(v.version).option("-P, --project <name>","creates a new project with it's shared directory").option("-C, --context <name>","creates a new context").option("-R, --react","creates a React context with --context").option("-T, --tests <path>","creates a .ts tests structure from an existing directory").option("--dir <path>","sets the directory to create the new item").parse(process.argv);T(l).catch(e=>{let s=e instanceof Error?e.message:String(e);l.error(s)});
@@ -21,7 +21,7 @@ surface that another actor can call or observe.
21
21
 
22
22
  The generated context starts with a single root file for ports.
23
23
 
24
- ```ts title="users/example-ports.ts"
24
+ ```ts title="enrollment/example-ports.ts"
25
25
  export class ExamplePort {
26
26
  public doSomething(): void {
27
27
  // ...
@@ -39,32 +39,56 @@ expected to be a module that defines one or more context ports.
39
39
  #### First Port
40
40
 
41
41
  In the following example we replace the placeholder with a concrete port for
42
- creating a user.
42
+ managing course enrollments.
43
43
 
44
- ```ts title="users/example-ports.ts"
45
- export interface User {
46
- id: string
47
- email: string
48
- active: boolean | null
49
- }
44
+ ```ts title="enrollment/example-ports.ts"
45
+ import { InMemoryDatabaseDriver } from './application/database.ts'
46
+ import { Course } from './domain/courses.ts'
47
+ import { Student } from './domain/students.ts'
48
+ import { InscriptionAggregate } from './domain/inscriptions.ts'
50
49
 
51
- export class UsersRegistry {
52
- public async createUser(user: User): Promise<User> {
53
- // ...
50
+ const database: Record<string, Record<string, unknown>[]> = {}
51
+
52
+ export class Example {
53
+ [property: string]: unknown
54
+
55
+ private readonly driver: InMemoryDatabaseDriver
56
+
57
+ constructor() {
58
+ this.driver = new InMemoryDatabaseDriver(database)
59
+ }
60
+
61
+ public createStudent(student: Student) {
62
+ const result = this.driver.connect('students').create(student.toJSON())
63
+ this.driver.disconnect()
64
+ return result
65
+ }
66
+
67
+ public createCourse(course: Course) {
68
+ const result = this.driver.connect('courses').create(course.toJSON())
69
+ this.driver.disconnect()
70
+ return result
54
71
  }
55
- }
56
- ```
57
72
 
58
- `User` expresses the user data that crosses the boundary. `UsersRegistry` is a
59
- boundary object of the context and `createUser()` is one concrete capability
60
- that it exposes.
73
+ public listInscriptions() {
74
+ const result = this.driver.connect('inscriptions').all()
75
+ this.driver.disconnect()
76
+ return result
77
+ }
61
78
 
62
- This definition focuses on the interaction the context makes available. The
63
- port keeps its identity as one boundary capability while making its data and
64
- action explicit.
79
+ public createInscription(student: Student, course: Course) {
80
+ const inscription = InscriptionAggregate.enroll(student, course)
81
+ const result = this.driver.connect('inscriptions').create(inscription.toJSON())
82
+ this.driver.disconnect()
83
+ return result
84
+ }
85
+ }
86
+ ```
65
87
 
66
- The port is the executable object that this context exposes. Types help
67
- describe it and make its boundary explicit.
88
+ `Example` is a boundary object of the context. Each static method is one
89
+ concrete capability it exposes. The port connects the boundary to a driver,
90
+ uses domain concepts internally, and keeps infrastructure details out of the
91
+ caller.
68
92
 
69
93
  This is the normal flow inside the context boundary:
70
94
 
@@ -83,10 +107,23 @@ capability and uses domain capabilities.
83
107
 
84
108
  As the context grows, you can keep several port modules at the context root.
85
109
 
86
- ```ts title="users/registry.ts"
87
- export class UsersRegistry {
88
- public async listUsers(): Promise<User[]> {
89
- // ...
110
+ ```ts title="enrollment/courses.ts"
111
+ import { CoursesService } from './application/services.ts'
112
+ import { Course } from './domain/courses.ts'
113
+
114
+ export class CoursesPort {
115
+ constructor(private readonly service: CoursesService) {}
116
+
117
+ public all(): Record<string, unknown>[] {
118
+ return this.service.all()
119
+ }
120
+
121
+ public create(name: string, description: string, hours: number): boolean {
122
+ return this.service.create(new Course(name, description, hours))
123
+ }
124
+
125
+ public delete(course: Course): boolean {
126
+ return this.service.delete(course)
90
127
  }
91
128
  }
92
129
  ```
@@ -110,9 +147,10 @@ in the generated folders.
110
147
 
111
148
  ```mermaid
112
149
  flowchart TD
113
- users["users/"] --> registry["registry.ts"]
114
- users --> application["application/"]
115
- users --> domain["domain/"]
150
+ enrollment["enrollment/"] --> examplePort["example-ports.ts"]
151
+ enrollment --> courses["courses.ts"]
152
+ enrollment --> application["application/"]
153
+ enrollment --> domain["domain/"]
116
154
  ```
117
155
 
118
156
  This layout keeps the context boundary visible from the top level. It also
@@ -85,8 +85,9 @@ The generated template also includes a small set of data-access abstractions.
85
85
 
86
86
  | File | Responsibility |
87
87
  | --- | --- |
88
+ | `shared/application/data/capabilities.ts` | Declares the operation capability interfaces: `Listable`, `Filterable`, `Sortable`, `Creatable`, `Updatable`, `Deletable`, `Aggregatable`, and `Relatable`. |
88
89
  | `shared/application/data/drivers.ts` | Declares `DriverAdapter`, the connection contract with a data source. |
89
- | `shared/application/data/managers.ts` | Declares `DataManager`, `DatasetManager`, and plain-record operations. |
90
+ | `shared/application/data/managers.ts` | Declares `DataManager` and `DatasetManager`. |
90
91
  | `shared/application/data/repositories.ts` | Declares `Repository`, which transforms raw records into domain representations. |
91
92
 
92
93
  These contracts belong to the application layer because they define how the
@@ -18,7 +18,7 @@ flowchart TD
18
18
  root["Library root"] --> types["types/"]
19
19
  root --> main["main.ts"]
20
20
  root --> shared["shared/"]
21
- root --> users["users/"]
21
+ root --> enrollment["enrollment/"]
22
22
  ```
23
23
 
24
24
  `types/` groups the root-level type declarations. `main.ts` starts as a
@@ -69,7 +69,7 @@ capability.
69
69
 
70
70
  ```mermaid
71
71
  flowchart TD
72
- contexts["Contexts"] --> users["users/"]
72
+ contexts["Contexts"] --> enrollment["enrollment/"]
73
73
  contexts --> billing["billing/"]
74
74
  contexts --> inventory["inventory/"]
75
75
  contexts --> sales["sales/"]
@@ -85,10 +85,10 @@ Every generated context starts with the same internal structure.
85
85
 
86
86
  ```mermaid
87
87
  flowchart TD
88
- users["users/"] --> ports["example-ports.ts"]
89
- users --> adapters["adapters/"]
90
- users --> application["application/"]
91
- users --> domain["domain/"]
88
+ enrollment["enrollment/"] --> ports["example-ports.ts"]
89
+ enrollment --> adapters["adapters/"]
90
+ enrollment --> application["application/"]
91
+ enrollment --> domain["domain/"]
92
92
  ```
93
93
 
94
94
  `example-ports.ts` is an example module in the root communication surface of
@@ -189,6 +189,22 @@ application services use domain capabilities, while adapters can depend on
189
189
  ports and third-party libraries. The port branch stops at the boundary because
190
190
  what exists beyond that port depends on the system that implements it.
191
191
 
192
+ ```ts title="main.ts"
193
+ import { Example } from './enrollment/example-ports.ts'
194
+ import { Student } from './enrollment/domain/students.ts'
195
+ import { Course } from './enrollment/domain/courses.ts'
196
+
197
+ const example = new Example()
198
+ const student = new Student('Ada Lovelace', 'ada@example.com')
199
+ const course = new Course('Mathematics', 'Fundamentals of algebra and calculus', 40)
200
+
201
+ example.createStudent(student)
202
+ example.createCourse(course)
203
+ example.createInscription(student, course)
204
+
205
+ console.log(example.listInscriptions())
206
+ ```
207
+
192
208
  #### Next Step
193
209
 
194
210
  Use this structure as the default layout for new code. When you need to inspect