gorig-cli 1.0.23 → 1.0.25
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 +112 -22
- package/commands/create.js +267 -112
- package/commands/doc.js +21 -8
- package/commands/init.js +316 -255
- package/commands/skill.js +55 -17
- package/package.json +1 -1
- package/templates/config.go.ejs +6 -5
- package/templates/config.yaml.ejs +3 -38
- package/templates/controller.go.ejs +11 -42
- package/templates/crud.controller.go.ejs +71 -0
- package/templates/crud.doc.md.ejs +60 -0
- package/templates/crud.dto.go.ejs +103 -0
- package/templates/crud.integration.init_test.go.ejs +17 -0
- package/templates/crud.integration_test.go.ejs +66 -0
- package/templates/crud.model.go.ejs +25 -0
- package/templates/crud.module.README.md.ejs +70 -0
- package/templates/crud.mongo.config.yaml.ejs +19 -0
- package/templates/crud.mysql.config.yaml.ejs +14 -0
- package/templates/crud.router.go.ejs +18 -0
- package/templates/crud.service.go.ejs +179 -0
- package/templates/crud.test.go.ejs +52 -0
- package/templates/dto.go.ejs +16 -0
- package/templates/gitignore.ejs +18 -49
- package/templates/hello.controller.go.ejs +19 -0
- package/templates/hello.router.go.ejs +12 -0
- package/templates/hello.service.go.ejs +31 -0
- package/templates/hello.test.go.ejs +34 -0
- package/templates/init.go.ejs +1 -27
- package/templates/main.go.ejs +2 -5
- package/templates/model.go.ejs +3 -4
- package/templates/project.README.md.ejs +35 -0
- package/templates/router.go.ejs +4 -8
- package/templates/service.go.ejs +25 -77
- package/templates/skills/gorig-backend/SKILL.md +89 -0
- package/templates/skills/gorig-backend/agents/openai.yaml +4 -0
- package/templates/skills/gorig-backend/assets/api-doc-template.md +59 -0
- package/templates/skills/gorig-backend/assets/gorig.gitignore +65 -0
- package/templates/skills/gorig-backend/assets/module-readme-template.md +58 -0
- package/templates/skills/gorig-backend/references/advanced-data-access.md +275 -0
- package/templates/skills/gorig-backend/references/auth-security.md +194 -0
- package/templates/skills/gorig-backend/references/business-scenarios.md +155 -0
- package/templates/skills/gorig-backend/references/cache.md +301 -0
- package/templates/skills/gorig-backend/references/capability-matrix.md +37 -0
- package/templates/skills/gorig-backend/references/configuration.md +48 -0
- package/templates/skills/gorig-backend/references/framework-api.md +190 -0
- package/templates/skills/gorig-backend/references/messaging.md +143 -0
- package/templates/skills/gorig-backend/references/onboarding-files.md +46 -0
- package/templates/skills/gorig-backend/references/outbound-http.md +162 -0
- package/templates/skills/gorig-backend/references/persistent-crud.md +332 -0
- package/templates/skills/gorig-backend/references/project-bootstrap.md +128 -0
- package/templates/skills/gorig-backend/references/scheduled-tasks.md +231 -0
- package/templates/skills/gorig-backend/references/service-lifecycle.md +51 -0
- package/templates/skills/gorig-backend/references/source-map.md +43 -0
- package/templates/skills/gorig-backend/references/source-policy.md +58 -0
- package/templates/skills/gorig-backend/references/sse.md +121 -0
- package/templates/skills/gorig-backend/references/testing.md +171 -0
- package/templates/skills/gorig-backend/scripts/check-source-links.sh +48 -0
- package/templates/skills/gorig-backend/scripts/detect-gorig-context.sh +67 -0
- package/templates/skills/gorig-backend/scripts/verify-basic-project.sh +108 -0
- package/npm_publish copy.sh +0 -65
- package/templates/internal.go.ejs +0 -49
- package/templates/req.go.ejs +0 -8
- package/templates/resp.go.ejs +0 -8
- package/templates/service.pub.go.ejs +0 -22
- package/templates/skills/claude/gorig-backend/SKILL.md +0 -189
- package/templates/skills/claude/gorig-backend/assets/api-doc-template.md +0 -50
- package/templates/skills/claude/gorig-backend/assets/module-readme-template.md +0 -46
- package/templates/skills/claude/gorig-backend/references/onboarding-files.md +0 -38
- package/templates/skills/codex/gorig-backend/SKILL.md +0 -189
- package/templates/skills/codex/gorig-backend/agents/openai.yaml +0 -4
- package/templates/skills/codex/gorig-backend/assets/api-doc-template.md +0 -50
- package/templates/skills/codex/gorig-backend/assets/module-readme-template.md +0 -46
- package/templates/skills/codex/gorig-backend/references/onboarding-files.md +0 -38
package/README.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# Gorig CLI
|
|
2
2
|
|
|
3
|
-
Gorig CLI is
|
|
3
|
+
Gorig CLI is the command-line tool for initializing Gorig projects, generating modules, installing the `gorig-backend` skill, and keeping generated projects aligned with the verified Gorig backend roadmap.
|
|
4
|
+
|
|
5
|
+
## Roadmap Status
|
|
6
|
+
|
|
7
|
+
The `gorig-backend` skill roadmap is tracked in [docs/gorig-backend-skill-roadmap.md](docs/gorig-backend-skill-roadmap.md). This README documents the stable CLI workflows and the currently verified skill surface.
|
|
8
|
+
|
|
9
|
+
| Scope | Status |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Skill foundation, source rules, and version detection | verified |
|
|
12
|
+
| Locally runnable basic project generation | verified |
|
|
13
|
+
| HTTP API, modules, database, cache, cron, messaging, SSE, auth, security, and outbound networking guidance | verified |
|
|
14
|
+
| Observability, deployment, and operations guidance | pending |
|
|
4
15
|
|
|
5
16
|
## Installation
|
|
6
17
|
|
|
@@ -18,23 +29,42 @@ npx gorig-cli@latest <command>
|
|
|
18
29
|
|
|
19
30
|
## Quick Start
|
|
20
31
|
|
|
21
|
-
### Initialize a
|
|
32
|
+
### Initialize a Runnable Project
|
|
22
33
|
|
|
23
34
|
Use the `init` command to create a new project:
|
|
24
35
|
|
|
25
36
|
```sh
|
|
26
|
-
gorig-cli init my-new-project
|
|
37
|
+
gorig-cli init my-new-project --gorig-version v0.0.52 --no-start
|
|
27
38
|
```
|
|
28
39
|
|
|
29
40
|
Or use npx:
|
|
30
41
|
|
|
31
42
|
```sh
|
|
32
|
-
npx gorig-cli@latest init my-new-project
|
|
43
|
+
npx gorig-cli@latest init my-new-project --gorig-version v0.0.52 --no-start
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This creates a locally runnable project with `local`, `dev`, and `prod` configuration, one `_cmd` entry point, and a Router-Controller-Service example under `domain/hello`. The basic project does not require MySQL, MongoDB, or Redis.
|
|
47
|
+
|
|
48
|
+
Useful automation options:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
# Use a custom Go module and base port
|
|
52
|
+
gorig-cli init my-new-project \
|
|
53
|
+
--module example.com/my-new-project \
|
|
54
|
+
--gorig-version v0.0.52 \
|
|
55
|
+
--port 9527 \
|
|
56
|
+
--no-start
|
|
57
|
+
|
|
58
|
+
# Framework development against a local Gorig checkout
|
|
59
|
+
gorig-cli init my-new-project \
|
|
60
|
+
--gorig-replace ../gorig \
|
|
61
|
+
--no-start \
|
|
62
|
+
--no-git
|
|
33
63
|
```
|
|
34
64
|
|
|
35
|
-
|
|
65
|
+
Use `--force` only when an existing non-empty destination may be replaced. Use `--start` to start immediately; otherwise initialization is non-interactive and does not start the service.
|
|
36
66
|
|
|
37
|
-
### Create a
|
|
67
|
+
### Create a Basic Module
|
|
38
68
|
|
|
39
69
|
Use the `create` command in the project root directory to create a new module:
|
|
40
70
|
|
|
@@ -48,7 +78,38 @@ Or use npx:
|
|
|
48
78
|
npx gorig-cli@latest create user
|
|
49
79
|
```
|
|
50
80
|
|
|
51
|
-
This
|
|
81
|
+
This creates a flat feature-first module under `domain/user`, using the Gorig Router -> Controller -> Service shape without adding database or Redis requirements:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
domain/user/
|
|
85
|
+
├── router.go
|
|
86
|
+
├── controller.go
|
|
87
|
+
├── service.go
|
|
88
|
+
├── dto.go
|
|
89
|
+
└── model/
|
|
90
|
+
└── user.go
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The generated routes are `GET /user/info` and `POST /user/echo`. More complex persistence-backed CRUD modules should keep the same flat module boundary and add `domainx/dx` access in `service.go` plus storage structs under `model/`.
|
|
94
|
+
|
|
95
|
+
### Create a Persistent CRUD Module
|
|
96
|
+
|
|
97
|
+
To generate a persistent CRUD module backed by Gorig `domainx/dx`, choose MySQL or MongoDB explicitly:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
# MySQL CRUD module, with HTTP routes enabled by default
|
|
101
|
+
gorig-cli create order --crud --db mysql --db-name Main
|
|
102
|
+
|
|
103
|
+
# MongoDB CRUD module
|
|
104
|
+
gorig-cli create order --crud --db mongo --db-name main
|
|
105
|
+
|
|
106
|
+
# Service/model CRUD only, without router/controller/doc HTTP adapter
|
|
107
|
+
gorig-cli create order --crud --db mysql --no-http
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Persistent CRUD generation currently supports MySQL and MongoDB. The generated code compiles without a live database, but effect-level create/list/page/update/delete verification requires a matching development database configuration.
|
|
111
|
+
|
|
112
|
+
The CRUD generator preserves existing named connections and adds non-secret configuration skeletons to `_bin/local.yaml`, `_bin/dev.yaml`, `_bin/prod.yaml`, and `test/_bin/local.yaml`. After filling local values or exporting the documented `GORIG_...` variables, run `go test -tags=integration,mysql ./test/... -v` or `go test -tags=integration,mongo ./test/... -v`.
|
|
52
113
|
|
|
53
114
|
### Generate API Documentation
|
|
54
115
|
|
|
@@ -74,29 +135,42 @@ http://127.0.0.1:8080/redoc.html
|
|
|
74
135
|
|
|
75
136
|
### Install Gorig Skill
|
|
76
137
|
|
|
77
|
-
Use the `skill` command to install the bundled `gorig-backend` skill for Codex or Claude
|
|
138
|
+
Use the `skill` command to install the bundled `gorig-backend` skill for Codex or Claude.
|
|
78
139
|
|
|
79
|
-
|
|
80
|
-
# Install both Codex and Claude user-level skills
|
|
81
|
-
gorig-cli skill install all
|
|
140
|
+
Install the Codex skill globally:
|
|
82
141
|
|
|
83
|
-
|
|
142
|
+
```sh
|
|
84
143
|
gorig-cli skill install codex
|
|
144
|
+
```
|
|
85
145
|
|
|
86
|
-
|
|
87
|
-
gorig-cli skill install claude user
|
|
146
|
+
Install both Codex and Claude skills globally:
|
|
88
147
|
|
|
89
|
-
|
|
90
|
-
gorig-cli skill install
|
|
148
|
+
```sh
|
|
149
|
+
gorig-cli skill install all
|
|
91
150
|
```
|
|
92
151
|
|
|
93
|
-
|
|
152
|
+
Install the Codex skill into the current repository:
|
|
94
153
|
|
|
95
154
|
```sh
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
155
|
+
gorig-cli skill install codex project
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The install scope defaults to `user`. Use `project` only when the skill should be stored in the current repository.
|
|
159
|
+
|
|
160
|
+
### Use with AI Agents
|
|
161
|
+
|
|
162
|
+
After installing the skill, open a Gorig project in Codex or Claude and ask for framework-specific backend work:
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
Use the gorig-backend skill to add a MySQL order CRUD module with tests and API docs.
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
Use the gorig-backend skill to review startup, routing, config, and middleware usage.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
Use the gorig-backend skill to add login, protected routes, logout, and security tests.
|
|
100
174
|
```
|
|
101
175
|
|
|
102
176
|
### Run the Project
|
|
@@ -105,7 +179,7 @@ After entering the project directory, you can run the project using the followin
|
|
|
105
179
|
|
|
106
180
|
```sh
|
|
107
181
|
cd my-new-project
|
|
108
|
-
go run _cmd
|
|
182
|
+
GORIG_SYS_MODE=local go run ./_cmd
|
|
109
183
|
```
|
|
110
184
|
|
|
111
185
|
Or run it after building:
|
|
@@ -113,3 +187,19 @@ Or run it after building:
|
|
|
113
187
|
```sh
|
|
114
188
|
go build -o my-new-project _cmd/main.go && ./my-new-project
|
|
115
189
|
```
|
|
190
|
+
|
|
191
|
+
## Verified Capabilities
|
|
192
|
+
|
|
193
|
+
- Dependency-free project bootstrap with `local`, `dev`, and `prod` configuration.
|
|
194
|
+
- Flat feature-first module generation under `domain/<module>/`.
|
|
195
|
+
- MySQL and MongoDB CRUD generation through Gorig `domainx/dx`.
|
|
196
|
+
- API documentation generation for generated HTTP modules.
|
|
197
|
+
- Skill installation for Codex and Claude from one canonical bundled source.
|
|
198
|
+
- Skill references and fixtures for cache, scheduled tasks, messaging, SSE, authentication, security, and outbound networking.
|
|
199
|
+
|
|
200
|
+
## Current Boundaries
|
|
201
|
+
|
|
202
|
+
- Persistent CRUD generation is verified for MySQL and MongoDB.
|
|
203
|
+
- Runtime verification for MySQL, MongoDB, and Redis requires configured development infrastructure.
|
|
204
|
+
- Observability, deployment, and operations guidance is the next roadmap phase.
|
|
205
|
+
- Real remote deployment or production mutation requires explicit user authorization.
|
package/commands/create.js
CHANGED
|
@@ -8,6 +8,88 @@ import { fileURLToPath } from 'url';
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = path.dirname(__filename);
|
|
10
10
|
|
|
11
|
+
const readOptionValue = (args, index, name) => {
|
|
12
|
+
const arg = args[index];
|
|
13
|
+
const prefix = `${name}=`;
|
|
14
|
+
if (arg.startsWith(prefix)) {
|
|
15
|
+
return { value: arg.slice(prefix.length), nextIndex: index };
|
|
16
|
+
}
|
|
17
|
+
if (index + 1 >= args.length || args[index + 1].startsWith('--')) {
|
|
18
|
+
throw new Error(`Missing value for ${name}`);
|
|
19
|
+
}
|
|
20
|
+
return { value: args[index + 1], nextIndex: index + 1 };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const toModuleName = (moduleName) => moduleName
|
|
24
|
+
.split('_')
|
|
25
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
26
|
+
.join('');
|
|
27
|
+
|
|
28
|
+
export const parseCreateArgs = (args) => {
|
|
29
|
+
if (args.length < 1) {
|
|
30
|
+
throw new Error('Please use the correct command format: gorig-cli create <module name>');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const moduleName = args[0];
|
|
34
|
+
if (!/^[a-z][a-z0-9_]*$/.test(moduleName)) {
|
|
35
|
+
throw new Error('Module name must use lower snake_case, for example: user or supply_order');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const options = {
|
|
39
|
+
moduleName,
|
|
40
|
+
ModuleName: toModuleName(moduleName),
|
|
41
|
+
crud: false,
|
|
42
|
+
db: '',
|
|
43
|
+
dbName: '',
|
|
44
|
+
http: true,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
for (let i = 1; i < args.length; i += 1) {
|
|
48
|
+
const arg = args[i];
|
|
49
|
+
if (arg === '--crud') {
|
|
50
|
+
options.crud = true;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (arg === '--no-http') {
|
|
54
|
+
options.http = false;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const valueOptions = ['--db', '--database', '--store', '--db-name'];
|
|
59
|
+
const optionName = valueOptions.find((name) => arg === name || arg.startsWith(`${name}=`));
|
|
60
|
+
if (!optionName) {
|
|
61
|
+
throw new Error(`Unknown create option: ${arg}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { value, nextIndex } = readOptionValue(args, i, optionName);
|
|
65
|
+
i = nextIndex;
|
|
66
|
+
|
|
67
|
+
if (['--db', '--database', '--store'].includes(optionName)) {
|
|
68
|
+
options.crud = true;
|
|
69
|
+
options.db = value.toLowerCase();
|
|
70
|
+
} else if (optionName === '--db-name') {
|
|
71
|
+
options.dbName = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (options.crud) {
|
|
76
|
+
if (!['mysql', 'mongo', 'mongodb'].includes(options.db)) {
|
|
77
|
+
throw new Error('Persistent CRUD modules require --db mysql or --db mongo');
|
|
78
|
+
}
|
|
79
|
+
if (options.db === 'mongodb') {
|
|
80
|
+
options.db = 'mongo';
|
|
81
|
+
}
|
|
82
|
+
if (!options.dbName) {
|
|
83
|
+
options.dbName = options.db === 'mysql' ? 'Main' : 'main';
|
|
84
|
+
}
|
|
85
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(options.dbName)) {
|
|
86
|
+
throw new Error('Database connection name must start with a letter and contain only letters, numbers, or underscores');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return options;
|
|
91
|
+
};
|
|
92
|
+
|
|
11
93
|
// Get the module name from the go.mod file
|
|
12
94
|
const getGoModModuleName = async () => {
|
|
13
95
|
try {
|
|
@@ -25,135 +107,208 @@ const getGoModModuleName = async () => {
|
|
|
25
107
|
}
|
|
26
108
|
};
|
|
27
109
|
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
110
|
+
const renderTemplate = async (templateName, destination, data) => {
|
|
111
|
+
const templatePath = path.join(__dirname, '../templates', templateName);
|
|
112
|
+
const content = await ejs.renderFile(templatePath, data);
|
|
113
|
+
await fs.ensureDir(path.dirname(destination));
|
|
114
|
+
await fs.writeFile(destination, content);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const renderTemplateContent = async (templateName, data) => {
|
|
118
|
+
const templatePath = path.join(__dirname, '../templates', templateName);
|
|
119
|
+
return ejs.renderFile(templatePath, data);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const insertYamlConnection = (content, topKey, connectionName, connectionBlock) => {
|
|
123
|
+
const normalized = content.endsWith('\n') ? content : `${content}\n`;
|
|
124
|
+
const lines = normalized.split('\n');
|
|
125
|
+
const topIndex = lines.findIndex((line) => line === `${topKey}:`);
|
|
126
|
+
|
|
127
|
+
if (topIndex < 0) {
|
|
128
|
+
return `${normalized.trimEnd()}\n\n${topKey}:\n${connectionBlock.trimEnd()}\n`;
|
|
33
129
|
}
|
|
34
130
|
|
|
35
|
-
|
|
36
|
-
|
|
131
|
+
let endIndex = lines.length - 1;
|
|
132
|
+
for (let i = topIndex + 1; i < lines.length; i += 1) {
|
|
133
|
+
if (/^[A-Za-z0-9_.-]+\s*:/.test(lines[i])) {
|
|
134
|
+
endIndex = i;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
37
138
|
|
|
38
|
-
|
|
39
|
-
|
|
139
|
+
const connectionPattern = new RegExp(`^ ${connectionName}:\\s*$`);
|
|
140
|
+
if (lines.slice(topIndex + 1, endIndex).some((line) => connectionPattern.test(line))) {
|
|
141
|
+
return content;
|
|
142
|
+
}
|
|
40
143
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
144
|
+
lines.splice(endIndex, 0, ...connectionBlock.trimEnd().split('\n'));
|
|
145
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
146
|
+
};
|
|
44
147
|
|
|
45
|
-
|
|
46
|
-
const
|
|
148
|
+
const ensureCrudConfig = async (projectDir, data) => {
|
|
149
|
+
const topKey = data.db === 'mysql' ? 'Mysql' : 'mongo';
|
|
150
|
+
const templateName = data.db === 'mysql'
|
|
151
|
+
? 'crud.mysql.config.yaml.ejs'
|
|
152
|
+
: 'crud.mongo.config.yaml.ejs';
|
|
153
|
+
const connectionBlock = await renderTemplateContent(templateName, data);
|
|
154
|
+
const configPaths = [
|
|
155
|
+
'_bin/local.yaml',
|
|
156
|
+
'_bin/dev.yaml',
|
|
157
|
+
'_bin/prod.yaml',
|
|
158
|
+
'test/_bin/local.yaml',
|
|
159
|
+
];
|
|
47
160
|
|
|
48
|
-
|
|
161
|
+
for (const relativePath of configPaths) {
|
|
162
|
+
const configPath = path.join(projectDir, relativePath);
|
|
163
|
+
if (!await fs.pathExists(configPath)) {
|
|
164
|
+
console.log(chalk.yellow(`Could not find ${relativePath}, skipping database configuration`));
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
49
167
|
|
|
50
|
-
|
|
51
|
-
const
|
|
168
|
+
const original = await fs.readFile(configPath, 'utf8');
|
|
169
|
+
const updated = insertYamlConnection(original, topKey, data.dbName, connectionBlock);
|
|
170
|
+
if (updated !== original) {
|
|
171
|
+
await fs.writeFile(configPath, updated);
|
|
172
|
+
console.log(chalk.green(`Successfully updated ${chalk.bold(relativePath)} database configuration`));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
52
176
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
177
|
+
const updateDomainInit = async (domainDir, projectName, moduleName) => {
|
|
178
|
+
const initFilePath = path.join(domainDir, 'init.go');
|
|
179
|
+
if (!await fs.pathExists(initFilePath)) {
|
|
180
|
+
console.log(chalk.yellow(`Could not find ${chalk.bold('init.go')} file, skipping import statement addition`));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let initFileContent = await fs.readFile(initFilePath, 'utf-8');
|
|
185
|
+
const importStatement = `import _ "${projectName}/domain/${moduleName}"`;
|
|
186
|
+
|
|
187
|
+
if (initFileContent.includes(importStatement)) {
|
|
188
|
+
console.log(chalk.yellow('The corresponding import statement already exists in init.go, no need to add it again'));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const packageMatch = initFileContent.match(/^package\s+\w+\s*$/m);
|
|
193
|
+
if (!packageMatch) {
|
|
194
|
+
console.log(chalk.yellow('package declaration not found, skipping import statement addition'));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const insertPosition = packageMatch.index + packageMatch[0].length;
|
|
199
|
+
const rest = initFileContent.slice(insertPosition).trimStart();
|
|
200
|
+
initFileContent =
|
|
201
|
+
`${initFileContent.slice(0, insertPosition).trimEnd()}\n\n` +
|
|
202
|
+
`${importStatement}\n` +
|
|
203
|
+
(rest ? `\n${rest}` : '');
|
|
204
|
+
|
|
205
|
+
await fs.writeFile(initFilePath, initFileContent);
|
|
206
|
+
console.log(chalk.green(`Successfully updated ${chalk.bold('init.go')} file, added import statement`));
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const createSimpleModule = async (moduleDir, data) => {
|
|
210
|
+
const { moduleName } = data;
|
|
211
|
+
|
|
212
|
+
await renderTemplate('model.go.ejs', path.join(moduleDir, 'model', `${moduleName}.go`), data);
|
|
213
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('model.go')} file`));
|
|
214
|
+
|
|
215
|
+
await renderTemplate('dto.go.ejs', path.join(moduleDir, 'dto.go'), data);
|
|
216
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('dto.go')} file`));
|
|
217
|
+
|
|
218
|
+
await renderTemplate('service.go.ejs', path.join(moduleDir, 'service.go'), data);
|
|
219
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('service.go')} file`));
|
|
220
|
+
|
|
221
|
+
await renderTemplate('controller.go.ejs', path.join(moduleDir, 'controller.go'), data);
|
|
222
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('controller.go')} file`));
|
|
223
|
+
|
|
224
|
+
await renderTemplate('router.go.ejs', path.join(moduleDir, 'router.go'), data);
|
|
225
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('router.go')} file`));
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const createCrudModule = async (moduleDir, projectDir, data, options) => {
|
|
229
|
+
const { moduleName } = data;
|
|
230
|
+
const crudData = {
|
|
231
|
+
...data,
|
|
232
|
+
...options,
|
|
233
|
+
dbTitle: options.db === 'mysql' ? 'MySQL' : 'MongoDB',
|
|
234
|
+
dbEnvName: options.dbName.replace(/[^A-Za-z0-9]/g, '_').toUpperCase(),
|
|
235
|
+
dbSchema: path.basename(projectDir).replace(/[^a-z0-9]/gi, '_').toLowerCase(),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
await ensureCrudConfig(projectDir, crudData);
|
|
239
|
+
|
|
240
|
+
await renderTemplate('crud.model.go.ejs', path.join(moduleDir, 'model', `${moduleName}.go`), crudData);
|
|
241
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('model.go')} file`));
|
|
242
|
+
|
|
243
|
+
await renderTemplate('crud.dto.go.ejs', path.join(moduleDir, 'dto.go'), crudData);
|
|
244
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('dto.go')} file`));
|
|
245
|
+
|
|
246
|
+
await renderTemplate('crud.service.go.ejs', path.join(moduleDir, 'service.go'), crudData);
|
|
247
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('service.go')} file`));
|
|
248
|
+
|
|
249
|
+
await renderTemplate('crud.module.README.md.ejs', path.join(moduleDir, 'README.md'), crudData);
|
|
250
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('README.md')} file`));
|
|
251
|
+
|
|
252
|
+
await renderTemplate('crud.test.go.ejs', path.join(projectDir, 'test', `${moduleName}_test.go`), crudData);
|
|
253
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('test file')} file`));
|
|
254
|
+
|
|
255
|
+
const integrationInitPath = path.join(projectDir, 'test', `init_${options.db}_integration_test.go`);
|
|
256
|
+
if (!await fs.pathExists(integrationInitPath)) {
|
|
257
|
+
await renderTemplate('crud.integration.init_test.go.ejs', integrationInitPath, crudData);
|
|
258
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('database integration init file')} file`));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
await renderTemplate(
|
|
262
|
+
'crud.integration_test.go.ejs',
|
|
263
|
+
path.join(projectDir, 'test', `${moduleName}_integration_test.go`),
|
|
264
|
+
crudData,
|
|
265
|
+
);
|
|
266
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('database integration test file')} file`));
|
|
267
|
+
|
|
268
|
+
if (options.http) {
|
|
269
|
+
await renderTemplate('crud.controller.go.ejs', path.join(moduleDir, 'controller.go'), crudData);
|
|
114
270
|
console.log(chalk.green(`Successfully created ${chalk.bold('controller.go')} file`));
|
|
115
271
|
|
|
116
|
-
|
|
117
|
-
const routerGoFilePath = path.join(apiDir, `router.go`);
|
|
118
|
-
const routerGoTemplatePath = path.join(__dirname, '../templates/router.go.ejs');
|
|
119
|
-
const routerGoContent = await ejs.renderFile(routerGoTemplatePath, { moduleName, ModuleName, projectName });
|
|
120
|
-
await fs.writeFile(routerGoFilePath, routerGoContent);
|
|
272
|
+
await renderTemplate('crud.router.go.ejs', path.join(moduleDir, 'router.go'), crudData);
|
|
121
273
|
console.log(chalk.green(`Successfully created ${chalk.bold('router.go')} file`));
|
|
122
274
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
} else {
|
|
148
|
-
console.log(chalk.yellow(`The corresponding import statement already exists in init.go, no need to add it again`));
|
|
149
|
-
}
|
|
275
|
+
await renderTemplate('crud.doc.md.ejs', path.join(projectDir, 'doc', `${moduleName}.md`), crudData);
|
|
276
|
+
console.log(chalk.green(`Successfully created ${chalk.bold('doc file')} file`));
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Create module main function
|
|
281
|
+
const createModule = async (args) => {
|
|
282
|
+
try {
|
|
283
|
+
const options = parseCreateArgs(args);
|
|
284
|
+
const { moduleName, ModuleName } = options;
|
|
285
|
+
const currentDir = process.cwd();
|
|
286
|
+
const domainDir = path.join(currentDir, 'domain');
|
|
287
|
+
const moduleDir = path.join(domainDir, moduleName);
|
|
288
|
+
|
|
289
|
+
console.log(chalk.blue(`\nStarting to create module: ${chalk.bold(moduleName)}`));
|
|
290
|
+
|
|
291
|
+
const projectName = await getGoModModuleName();
|
|
292
|
+
const data = { moduleName, ModuleName, projectName };
|
|
293
|
+
|
|
294
|
+
await fs.ensureDir(domainDir);
|
|
295
|
+
await fs.ensureDir(path.join(moduleDir, 'model'));
|
|
296
|
+
|
|
297
|
+
if (options.crud) {
|
|
298
|
+
await createCrudModule(moduleDir, currentDir, data, options);
|
|
150
299
|
} else {
|
|
151
|
-
|
|
300
|
+
await createSimpleModule(moduleDir, data);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!options.crud || options.http) {
|
|
304
|
+
await updateDomainInit(domainDir, projectName, moduleName);
|
|
152
305
|
}
|
|
153
306
|
|
|
154
|
-
|
|
307
|
+
const profile = options.crud ? `persistent CRUD (${options.db})` : 'basic';
|
|
308
|
+
console.log(chalk.blue(`\nModule ${chalk.bold(moduleName)} has been successfully created in ${chalk.bold(`domain/${moduleName}`)} directory [${profile}]`));
|
|
155
309
|
} catch (error) {
|
|
156
310
|
console.error(chalk.red('Error creating module:'), chalk.redBright(error.message));
|
|
311
|
+
process.exitCode = 1;
|
|
157
312
|
}
|
|
158
313
|
};
|
|
159
314
|
|
package/commands/doc.js
CHANGED
|
@@ -422,16 +422,27 @@ const generateOpenAPIDocs = async (moduleName = null) => {
|
|
|
422
422
|
|
|
423
423
|
for (const module of modulesToProcess) {
|
|
424
424
|
const modulePath = path.join(domainDir, module);
|
|
425
|
-
const
|
|
426
|
-
const
|
|
425
|
+
const flatRouterFilePath = path.join(modulePath, 'router.go');
|
|
426
|
+
const flatControllerFilePath = path.join(modulePath, 'controller.go');
|
|
427
|
+
const legacyRouterFilePath = path.join(modulePath, 'api/router.go');
|
|
428
|
+
const legacyControllerFilePath = path.join(modulePath, 'api/controller.go');
|
|
429
|
+
const routerFilePath = await fs.pathExists(flatRouterFilePath) ? flatRouterFilePath : legacyRouterFilePath;
|
|
430
|
+
const controllerFilePath = await fs.pathExists(flatControllerFilePath) ? flatControllerFilePath : legacyControllerFilePath;
|
|
427
431
|
const modelFilePath = path.join(modulePath, 'model', `${module}.go`);
|
|
432
|
+
const dtoFilePath = path.join(modulePath, 'dto.go');
|
|
428
433
|
const reqFilePath = path.join(modulePath, 'api', 'req', 'req.go');
|
|
429
434
|
|
|
430
435
|
// 检查文件是否存在
|
|
431
|
-
if (!(await fs.pathExists(routerFilePath)) || !(await fs.pathExists(controllerFilePath)) || !(await fs.pathExists(modelFilePath))
|
|
436
|
+
if (!(await fs.pathExists(routerFilePath)) || !(await fs.pathExists(controllerFilePath)) || !(await fs.pathExists(modelFilePath))) {
|
|
432
437
|
console.warn(chalk.yellow(`Module "${module}" is missing required files, skipping...`));
|
|
433
438
|
continue;
|
|
434
439
|
}
|
|
440
|
+
const schemaFilePaths = [modelFilePath];
|
|
441
|
+
if (await fs.pathExists(dtoFilePath)) {
|
|
442
|
+
schemaFilePaths.push(dtoFilePath);
|
|
443
|
+
} else if (await fs.pathExists(reqFilePath)) {
|
|
444
|
+
schemaFilePaths.push(reqFilePath);
|
|
445
|
+
}
|
|
435
446
|
|
|
436
447
|
// 扫描 router.go 获取路由信息
|
|
437
448
|
const routes = await scanRouterFile(routerFilePath);
|
|
@@ -439,13 +450,15 @@ const generateOpenAPIDocs = async (moduleName = null) => {
|
|
|
439
450
|
|
|
440
451
|
// 扫描 controller.go 获取方法参数
|
|
441
452
|
const controllerContent = await fs.readFile(controllerFilePath, 'utf-8');
|
|
442
|
-
const
|
|
453
|
+
const reqDir = await fs.pathExists(reqFilePath) ? path.dirname(reqFilePath) : modulePath;
|
|
454
|
+
const controllerParams = await extractParamsFromController(controllerContent, reqDir, path.dirname(modelFilePath));
|
|
443
455
|
// console.log(`Extracted controller parameters for module "${module}":`, controllerParams);
|
|
444
456
|
|
|
445
|
-
// 扫描 model.go 和 req.go 获取字段
|
|
446
|
-
const
|
|
447
|
-
const
|
|
448
|
-
|
|
457
|
+
// 扫描 model.go 和 dto.go/req.go 获取字段
|
|
458
|
+
const allFields = [];
|
|
459
|
+
for (const schemaFilePath of schemaFilePaths) {
|
|
460
|
+
allFields.push(...await extractModelFields(schemaFilePath));
|
|
461
|
+
}
|
|
449
462
|
|
|
450
463
|
// 生成 OpenAPI 格式文档
|
|
451
464
|
const openAPIDoc = {
|