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.
Files changed (73) hide show
  1. package/README.md +112 -22
  2. package/commands/create.js +267 -112
  3. package/commands/doc.js +21 -8
  4. package/commands/init.js +316 -255
  5. package/commands/skill.js +55 -17
  6. package/package.json +1 -1
  7. package/templates/config.go.ejs +6 -5
  8. package/templates/config.yaml.ejs +3 -38
  9. package/templates/controller.go.ejs +11 -42
  10. package/templates/crud.controller.go.ejs +71 -0
  11. package/templates/crud.doc.md.ejs +60 -0
  12. package/templates/crud.dto.go.ejs +103 -0
  13. package/templates/crud.integration.init_test.go.ejs +17 -0
  14. package/templates/crud.integration_test.go.ejs +66 -0
  15. package/templates/crud.model.go.ejs +25 -0
  16. package/templates/crud.module.README.md.ejs +70 -0
  17. package/templates/crud.mongo.config.yaml.ejs +19 -0
  18. package/templates/crud.mysql.config.yaml.ejs +14 -0
  19. package/templates/crud.router.go.ejs +18 -0
  20. package/templates/crud.service.go.ejs +179 -0
  21. package/templates/crud.test.go.ejs +52 -0
  22. package/templates/dto.go.ejs +16 -0
  23. package/templates/gitignore.ejs +18 -49
  24. package/templates/hello.controller.go.ejs +19 -0
  25. package/templates/hello.router.go.ejs +12 -0
  26. package/templates/hello.service.go.ejs +31 -0
  27. package/templates/hello.test.go.ejs +34 -0
  28. package/templates/init.go.ejs +1 -27
  29. package/templates/main.go.ejs +2 -5
  30. package/templates/model.go.ejs +3 -4
  31. package/templates/project.README.md.ejs +35 -0
  32. package/templates/router.go.ejs +4 -8
  33. package/templates/service.go.ejs +25 -77
  34. package/templates/skills/gorig-backend/SKILL.md +89 -0
  35. package/templates/skills/gorig-backend/agents/openai.yaml +4 -0
  36. package/templates/skills/gorig-backend/assets/api-doc-template.md +59 -0
  37. package/templates/skills/gorig-backend/assets/gorig.gitignore +65 -0
  38. package/templates/skills/gorig-backend/assets/module-readme-template.md +58 -0
  39. package/templates/skills/gorig-backend/references/advanced-data-access.md +275 -0
  40. package/templates/skills/gorig-backend/references/auth-security.md +194 -0
  41. package/templates/skills/gorig-backend/references/business-scenarios.md +155 -0
  42. package/templates/skills/gorig-backend/references/cache.md +301 -0
  43. package/templates/skills/gorig-backend/references/capability-matrix.md +37 -0
  44. package/templates/skills/gorig-backend/references/configuration.md +48 -0
  45. package/templates/skills/gorig-backend/references/framework-api.md +190 -0
  46. package/templates/skills/gorig-backend/references/messaging.md +143 -0
  47. package/templates/skills/gorig-backend/references/onboarding-files.md +46 -0
  48. package/templates/skills/gorig-backend/references/outbound-http.md +162 -0
  49. package/templates/skills/gorig-backend/references/persistent-crud.md +332 -0
  50. package/templates/skills/gorig-backend/references/project-bootstrap.md +128 -0
  51. package/templates/skills/gorig-backend/references/scheduled-tasks.md +231 -0
  52. package/templates/skills/gorig-backend/references/service-lifecycle.md +51 -0
  53. package/templates/skills/gorig-backend/references/source-map.md +43 -0
  54. package/templates/skills/gorig-backend/references/source-policy.md +58 -0
  55. package/templates/skills/gorig-backend/references/sse.md +121 -0
  56. package/templates/skills/gorig-backend/references/testing.md +171 -0
  57. package/templates/skills/gorig-backend/scripts/check-source-links.sh +48 -0
  58. package/templates/skills/gorig-backend/scripts/detect-gorig-context.sh +67 -0
  59. package/templates/skills/gorig-backend/scripts/verify-basic-project.sh +108 -0
  60. package/npm_publish copy.sh +0 -65
  61. package/templates/internal.go.ejs +0 -49
  62. package/templates/req.go.ejs +0 -8
  63. package/templates/resp.go.ejs +0 -8
  64. package/templates/service.pub.go.ejs +0 -22
  65. package/templates/skills/claude/gorig-backend/SKILL.md +0 -189
  66. package/templates/skills/claude/gorig-backend/assets/api-doc-template.md +0 -50
  67. package/templates/skills/claude/gorig-backend/assets/module-readme-template.md +0 -46
  68. package/templates/skills/claude/gorig-backend/references/onboarding-files.md +0 -38
  69. package/templates/skills/codex/gorig-backend/SKILL.md +0 -189
  70. package/templates/skills/codex/gorig-backend/agents/openai.yaml +0 -4
  71. package/templates/skills/codex/gorig-backend/assets/api-doc-template.md +0 -50
  72. package/templates/skills/codex/gorig-backend/assets/module-readme-template.md +0 -46
  73. 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 a scaffolding tool based on Node.js, used for quickly creating the project structure and modules based on the Gorig framework for Go.
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 New Project
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
- This will create a new project in the current directory, including basic files and directories like `_cmd/main.go`, `domain/init.go`, `cron/cron.go`, etc.
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 New Module
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 will create a module named `user` in the project, including folders like `api/`, `internal/`, `model/`, and necessary code.
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
- ```sh
80
- # Install both Codex and Claude user-level skills
81
- gorig-cli skill install all
140
+ Install the Codex skill globally:
82
141
 
83
- # Install only the Codex skill
142
+ ```sh
84
143
  gorig-cli skill install codex
144
+ ```
85
145
 
86
- # Install Claude user-level skill
87
- gorig-cli skill install claude user
146
+ Install both Codex and Claude skills globally:
88
147
 
89
- # Install Claude project-level skill into the current repository
90
- gorig-cli skill install claude project
148
+ ```sh
149
+ gorig-cli skill install all
91
150
  ```
92
151
 
93
- Or use npx:
152
+ Install the Codex skill into the current repository:
94
153
 
95
154
  ```sh
96
- npx gorig-cli@latest skill install all
97
- npx gorig-cli@latest skill install codex
98
- npx gorig-cli@latest skill install claude user
99
- npx gorig-cli@latest skill install claude project
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/main.go
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.
@@ -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
- // Create module main function
29
- const createModule = async (args) => {
30
- if (args.length < 1) {
31
- console.error(chalk.yellow('Please use the correct command format: npx <your package name> create <module name>'));
32
- process.exit(1);
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
- const moduleName = args[0];
36
- const ModuleName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
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
- // Get the current working directory, which is the directory where the command is executed
39
- const currentDir = process.cwd();
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
- // Build the paths for the domain directory and module directory
42
- const domainDir = path.join(currentDir, 'domain');
43
- const moduleDir = path.join(domainDir, moduleName);
144
+ lines.splice(endIndex, 0, ...connectionBlock.trimEnd().split('\n'));
145
+ return `${lines.join('\n').trimEnd()}\n`;
146
+ };
44
147
 
45
- // Define the subdirectories to be created
46
- const subDirs = ['api', 'model', 'internal', 'api/req'];
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
- console.log(chalk.blue(`\nStarting to create module: ${chalk.bold(moduleName)}`));
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
- try {
51
- const projectName = await getGoModModuleName();
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
- await fs.ensureDir(domainDir);
54
- await fs.ensureDir(moduleDir);
55
-
56
- // Create subdirectories
57
- const createSubDirs = subDirs.map((subDir) => {
58
- const subDirPath = path.join(moduleDir, subDir);
59
- return fs.ensureDir(subDirPath);
60
- });
61
- await Promise.all(createSubDirs);
62
-
63
- // Create and write model.go file
64
- const modelDir = path.join(moduleDir, 'model');
65
- const modelFilePath = path.join(modelDir, `${moduleName}.go`);
66
- const modelTemplatePath = path.join(__dirname, '../templates/model.go.ejs');
67
- const modelContent = await ejs.renderFile(modelTemplatePath, { moduleName, ModuleName, projectName });
68
- await fs.writeFile(modelFilePath, modelContent);
69
- console.log(chalk.green(`Successfully created ${chalk.bold('model.go')} file`));
70
-
71
- // Create and write req.go file
72
- const apiDir = path.join(moduleDir, 'api');
73
- const reqDir = path.join(apiDir, 'req');
74
- const reqFilePath = path.join(reqDir, `req.go`);
75
- const reqTemplatePath = path.join(__dirname, '../templates/req.go.ejs');
76
- const reqContent = await ejs.renderFile(reqTemplatePath, { moduleName, ModuleName, projectName });
77
- await fs.writeFile(reqFilePath, reqContent);
78
- console.log(chalk.green(`Successfully created ${chalk.bold('req.go')} file`));
79
-
80
- // Create and write resp.go file
81
- const respFilePath = path.join(reqDir, `resp.go`);
82
- const respTemplatePath = path.join(__dirname, '../templates/resp.go.ejs');
83
- const respContent = await ejs.renderFile(respTemplatePath, { moduleName, ModuleName, projectName });
84
- await fs.writeFile(respFilePath, respContent);
85
- console.log(chalk.green(`Successfully created ${chalk.bold('resp.go')} file`));
86
-
87
- // Create and write internal.go file
88
- const internalDir = path.join(moduleDir, 'internal');
89
- const internalFilePath = path.join(internalDir, `${moduleName}.go`);
90
- const internalTemplatePath = path.join(__dirname, '../templates/internal.go.ejs');
91
- const internalContent = await ejs.renderFile(internalTemplatePath, { moduleName, ModuleName, projectName });
92
- await fs.writeFile(internalFilePath, internalContent);
93
- console.log(chalk.green(`Successfully created ${chalk.bold('internal.go')} file`));
94
-
95
- // Create and write service.pub.go file
96
- const serviceFilePath = path.join(apiDir, `service.pub.go`);
97
- const serviceTemplatePath = path.join(__dirname, '../templates/service.pub.go.ejs');
98
- const serviceContent = await ejs.renderFile(serviceTemplatePath, { moduleName, ModuleName, projectName });
99
- await fs.writeFile(serviceFilePath, serviceContent);
100
- console.log(chalk.green(`Successfully created ${chalk.bold('service.pub.go')} file`));
101
-
102
- // Create and write service.go file
103
- const serviceGoFilePath = path.join(apiDir, `service.go`);
104
- const serviceGoTemplatePath = path.join(__dirname, '../templates/service.go.ejs');
105
- const serviceGoContent = await ejs.renderFile(serviceGoTemplatePath, { moduleName, ModuleName, projectName });
106
- await fs.writeFile(serviceGoFilePath, serviceGoContent);
107
- console.log(chalk.green(`Successfully created ${chalk.bold('service.go')} file`));
108
-
109
- // Create and write controller.go file
110
- const controllerGoFilePath = path.join(apiDir, `controller.go`);
111
- const controllerGoTemplatePath = path.join(__dirname, '../templates/controller.go.ejs');
112
- const controllerGoContent = await ejs.renderFile(controllerGoTemplatePath, { moduleName, ModuleName, projectName });
113
- await fs.writeFile(controllerGoFilePath, controllerGoContent);
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
- // Create and write router.go file
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
- // Check and update domain/init.go file
124
- const initFilePath = path.join(domainDir, 'init.go');
125
- if (await fs.pathExists(initFilePath)) {
126
- let initFileContent = await fs.readFile(initFilePath, 'utf-8');
127
-
128
- const importStatement = `import _ "${projectName}/domain/${moduleName}/api"`;
129
-
130
- if (!initFileContent.includes(importStatement)) {
131
- const initFuncIndex = initFileContent.indexOf('func init()');
132
-
133
- if (initFuncIndex !== -1) {
134
- // Find init function, ensure import statement is added above init function
135
- const insertPosition = initFuncIndex;
136
- const headCode = initFileContent.slice(0, insertPosition).trimEnd();
137
- initFileContent =
138
- `${headCode}\n` +
139
- `${importStatement}\n\n` +
140
- initFileContent.slice(insertPosition);
141
-
142
- await fs.writeFile(initFilePath, initFileContent);
143
- console.log(chalk.green(`Successfully updated ${chalk.bold('init.go')} file, added import statement`));
144
- } else {
145
- console.log(chalk.yellow(`init() function not found, skipping import statement addition`));
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
- console.log(chalk.yellow(`Could not find ${chalk.bold('init.go')} file, skipping import statement addition`));
300
+ await createSimpleModule(moduleDir, data);
301
+ }
302
+
303
+ if (!options.crud || options.http) {
304
+ await updateDomainInit(domainDir, projectName, moduleName);
152
305
  }
153
306
 
154
- console.log(chalk.blue(`\nModule ${chalk.bold(moduleName)} has been successfully created in ${chalk.bold(`domain/${moduleName}`)} directory`));
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 routerFilePath = path.join(modulePath, 'api/router.go');
426
- const controllerFilePath = path.join(modulePath, 'api/controller.go');
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)) || !(await fs.pathExists(reqFilePath))) {
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 controllerParams = await extractParamsFromController(controllerContent, path.dirname(reqFilePath), path.dirname(modelFilePath));
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 modelFields = await extractModelFields(modelFilePath);
447
- const reqFields = await extractModelFields(reqFilePath);
448
- const allFields = [...modelFields, ...reqFields];
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 = {