@robsun/create-keystone-app 0.1.13 → 0.1.14
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 +19 -10
- package/bin/create-keystone-app.js +199 -60
- package/package.json +1 -1
- package/template/.husky/pre-commit +4 -0
- package/template/.lintstagedrc.json +5 -0
- package/template/.prettierrc +9 -0
- package/template/README.md +45 -23
- package/template/apps/server/.air.toml +44 -0
- package/template/apps/server/README.md +27 -0
- package/template/apps/server/cmd/server/main.go +213 -0
- package/template/apps/server/config.yaml +51 -0
- package/template/apps/server/docs/docs.go +34 -0
- package/template/apps/server/go.mod +13 -0
- package/template/apps/server/go.sum +72 -0
- package/template/apps/server/internal/app/routes/module_routes.go +16 -0
- package/template/apps/server/internal/app/routes/routes.go +226 -0
- package/template/apps/server/internal/app/startup/startup.go +74 -0
- package/template/apps/server/internal/frontend/dist/.gitkeep +1 -0
- package/template/apps/server/internal/frontend/embed.go +28 -0
- package/template/apps/server/internal/frontend/handler.go +122 -0
- package/template/apps/server/internal/{demo/demo.go → modules/demo/handlers.go} +4 -1
- package/template/apps/server/internal/modules/demo/module.go +55 -0
- package/template/apps/server/internal/modules/manifest.go +11 -0
- package/template/apps/server/internal/modules/registry.go +145 -0
- package/template/apps/web/.env.example +3 -0
- package/template/apps/web/README.md +29 -0
- package/template/apps/web/eslint.config.js +35 -0
- package/template/apps/web/package.json +27 -10
- package/template/apps/web/postcss.config.js +6 -0
- package/template/apps/web/src/index.css +3 -0
- package/template/apps/web/src/main.tsx +1 -0
- package/template/apps/web/src/modules/demo/help/overview.md +12 -0
- package/template/apps/web/src/modules/demo/routes.tsx +2 -0
- package/template/apps/web/tailwind.config.js +18 -0
- package/template/apps/web/tests/setup.ts +37 -0
- package/template/apps/web/tsconfig.app.json +3 -3
- package/template/apps/web/vite.config.ts +28 -2
- package/template/docker-compose.yml +45 -0
- package/template/docs/CONVENTIONS.md +61 -88
- package/template/package.json +15 -3
- package/template/scripts/build.bat +133 -0
- package/template/scripts/build.js +25 -0
- package/template/scripts/build.sh +99 -0
- package/template/scripts/clean.bat +35 -0
- package/template/scripts/clean.js +25 -0
- package/template/scripts/clean.sh +34 -0
- package/template/scripts/dev.bat +82 -0
- package/template/scripts/dev.js +25 -0
- package/template/scripts/dev.sh +88 -0
- package/template/scripts/test.bat +86 -0
- package/template/scripts/test.js +25 -0
- package/template/scripts/test.sh +86 -0
- package/template/apps/server/main.go +0 -28
- /package/template/{config.yaml → apps/server/config.example.yaml} +0 -0
package/README.md
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
|
-
# @robsun/create-keystone-app
|
|
1
|
+
# @robsun/create-keystone-app
|
|
2
2
|
|
|
3
3
|
## 用法
|
|
4
4
|
```bash
|
|
5
|
-
npx @robsun/create-keystone-app <dir> [
|
|
6
|
-
pnpm dlx @robsun/create-keystone-app <dir> [
|
|
5
|
+
npx @robsun/create-keystone-app <dir> [options]
|
|
6
|
+
pnpm dlx @robsun/create-keystone-app <dir> [options]
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
##
|
|
10
|
-
- `<dir
|
|
11
|
-
- `--
|
|
12
|
-
-
|
|
9
|
+
## 选项
|
|
10
|
+
- `<dir>`:目标目录(必填),可为新目录名或 `.`(当前目录)。
|
|
11
|
+
- `--profile <starter|full>`:模板档位,默认 `starter`。
|
|
12
|
+
- `--demo` / `--no-demo`:是否包含 Demo 模块(`full` 默认包含)。
|
|
13
|
+
- `--db <sqlite|postgres>`:数据库驱动(默认 `sqlite`)。
|
|
14
|
+
- `--queue <memory|redis>`:队列驱动(默认 `memory`)。
|
|
15
|
+
- `--storage <local|s3>`:存储驱动(默认 `local`)。
|
|
16
|
+
|
|
17
|
+
## 示例
|
|
18
|
+
```bash
|
|
19
|
+
npx @robsun/create-keystone-app my-app --profile=full --db=postgres --queue=redis
|
|
20
|
+
```
|
|
13
21
|
|
|
14
22
|
## 初始化后操作
|
|
15
23
|
```bash
|
|
16
24
|
cd <dir>
|
|
17
25
|
pnpm install
|
|
18
|
-
pnpm server:dev
|
|
19
26
|
pnpm dev
|
|
20
27
|
```
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
|
|
29
|
+
## 端口与 Demo
|
|
30
|
+
- Web 默认端口:`3000`;后端默认端口:`8080`。
|
|
31
|
+
- Demo API:`/api/v1/demo/tasks`(仅在包含 Demo 时可用)。
|
|
@@ -3,45 +3,44 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
|
|
5
5
|
const usage = [
|
|
6
|
-
'Usage: create-keystone-app <dir> [
|
|
6
|
+
'Usage: create-keystone-app <dir> [options]',
|
|
7
7
|
'',
|
|
8
8
|
'Options:',
|
|
9
|
-
' --
|
|
9
|
+
' --profile <starter|full> Template profile (default: starter)',
|
|
10
|
+
' --demo Include demo module',
|
|
11
|
+
' --no-demo Exclude demo module',
|
|
12
|
+
' --db <sqlite|postgres> Database driver (default: sqlite)',
|
|
13
|
+
' --queue <memory|redis> Queue driver (default: memory)',
|
|
14
|
+
' --storage <local|s3> Storage driver (default: local)',
|
|
15
|
+
' -h, --help Show help',
|
|
10
16
|
].join('\n');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (arg === '--demo') {
|
|
17
|
-
includeDemo = true;
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
if (arg === '--help' || arg === '-h') {
|
|
21
|
-
console.log(usage);
|
|
22
|
-
process.exit(0);
|
|
23
|
-
}
|
|
24
|
-
if (arg.startsWith('-')) {
|
|
25
|
-
console.error(`Unknown option: ${arg}`);
|
|
26
|
-
console.error(usage);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
if (!rawTarget) {
|
|
30
|
-
rawTarget = arg;
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
console.error('Too many arguments.');
|
|
34
|
-
console.error(usage);
|
|
35
|
-
process.exit(1);
|
|
17
|
+
|
|
18
|
+
const args = parseArgs(process.argv.slice(2));
|
|
19
|
+
if (args.help) {
|
|
20
|
+
console.log(usage);
|
|
21
|
+
process.exit(0);
|
|
36
22
|
}
|
|
37
23
|
|
|
38
|
-
if (!
|
|
24
|
+
if (!args.target) {
|
|
39
25
|
console.error(usage);
|
|
40
26
|
process.exit(1);
|
|
41
27
|
}
|
|
42
28
|
|
|
43
|
-
const
|
|
44
|
-
const
|
|
29
|
+
const profile = normalizeChoice(args.profile, ['starter', 'full'], 'profile') || 'starter';
|
|
30
|
+
const db = normalizeChoice(args.db, ['sqlite', 'postgres'], 'db') || 'sqlite';
|
|
31
|
+
const queue = normalizeChoice(args.queue, ['memory', 'redis'], 'queue') || 'memory';
|
|
32
|
+
const storage = normalizeChoice(args.storage, ['local', 's3'], 'storage') || 'local';
|
|
33
|
+
|
|
34
|
+
let includeDemo = profile === 'full';
|
|
35
|
+
if (args.demo === true) {
|
|
36
|
+
includeDemo = true;
|
|
37
|
+
}
|
|
38
|
+
if (args.demo === false) {
|
|
39
|
+
includeDemo = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const targetDir = path.resolve(process.cwd(), args.target);
|
|
43
|
+
const targetName = args.target === '.'
|
|
45
44
|
? path.basename(process.cwd())
|
|
46
45
|
: path.basename(targetDir);
|
|
47
46
|
|
|
@@ -61,13 +60,15 @@ copyDir(templateDir, targetDir, {
|
|
|
61
60
|
'__RAW_NAME__': targetName,
|
|
62
61
|
});
|
|
63
62
|
|
|
63
|
+
applyConfigOptions(targetDir, { db, queue, storage });
|
|
64
|
+
|
|
64
65
|
if (!includeDemo) {
|
|
65
66
|
stripDemo(targetDir);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
console.log(`Created ${targetName}`);
|
|
69
70
|
console.log('Next steps:');
|
|
70
|
-
console.log(` cd ${
|
|
71
|
+
console.log(` cd ${args.target}`);
|
|
71
72
|
console.log(' pnpm install');
|
|
72
73
|
console.log(' pnpm server:dev');
|
|
73
74
|
console.log(' pnpm dev');
|
|
@@ -102,11 +103,18 @@ function copyFile(src, dest, replacements) {
|
|
|
102
103
|
content = content.split(key).join(value);
|
|
103
104
|
}
|
|
104
105
|
fs.writeFileSync(dest, content, 'utf8');
|
|
106
|
+
if (shouldMakeExecutable(dest)) {
|
|
107
|
+
try {
|
|
108
|
+
fs.chmodSync(dest, 0o755);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
// Ignore chmod errors on platforms that don't support it.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
function stripDemo(targetDir) {
|
|
108
116
|
removePath(path.join(targetDir, 'apps', 'web', 'src', 'modules', 'demo'));
|
|
109
|
-
removePath(path.join(targetDir, 'apps', 'server', 'internal', 'demo'));
|
|
117
|
+
removePath(path.join(targetDir, 'apps', 'server', 'internal', 'modules', 'demo'));
|
|
110
118
|
|
|
111
119
|
updateFile(path.join(targetDir, 'apps', 'web', 'src', 'main.tsx'), (content) =>
|
|
112
120
|
content.replace(/^\s*import ['"]\.\/modules\/demo['"];?\r?\n/m, '')
|
|
@@ -114,38 +122,71 @@ function stripDemo(targetDir) {
|
|
|
114
122
|
updateFile(path.join(targetDir, 'apps', 'web', 'src', 'app.config.ts'), (content) =>
|
|
115
123
|
content.replace(/,\s*['"]demo['"]/, '')
|
|
116
124
|
);
|
|
117
|
-
updateFile(path.join(targetDir, 'config.yaml'), (content) =>
|
|
125
|
+
updateFile(path.join(targetDir, 'apps', 'server', 'config.yaml'), (content) =>
|
|
126
|
+
content.replace(/\r?\n\s*-\s*['"]demo['"]\s*/g, '')
|
|
127
|
+
);
|
|
128
|
+
updateFile(path.join(targetDir, 'apps', 'server', 'config.example.yaml'), (content) =>
|
|
118
129
|
content.replace(/\r?\n\s*-\s*['"]demo['"]\s*/g, '')
|
|
119
130
|
);
|
|
120
|
-
updateFile(path.join(targetDir, 'README.md'), (content) =>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
func main() {
|
|
139
|
-
\tapp, err := server.New()
|
|
140
|
-
\tif err != nil {
|
|
141
|
-
\t\tlog.Fatalf("failed to initialize server: %v", err)
|
|
142
|
-
\t}
|
|
143
|
-
\tif err := app.Run(); err != nil {
|
|
144
|
-
\t\tlog.Fatalf("server stopped: %v", err)
|
|
145
|
-
\t}
|
|
131
|
+
updateFile(path.join(targetDir, 'README.md'), (content) =>
|
|
132
|
+
content.replace(/<!-- DEMO_START -->[\s\S]*?<!-- DEMO_END -->\s*/m, '')
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const manifestPath = path.join(
|
|
136
|
+
targetDir,
|
|
137
|
+
'apps',
|
|
138
|
+
'server',
|
|
139
|
+
'internal',
|
|
140
|
+
'modules',
|
|
141
|
+
'manifest.go'
|
|
142
|
+
);
|
|
143
|
+
const manifest = `package modules
|
|
144
|
+
|
|
145
|
+
// RegisterAll wires the module registry for this app.
|
|
146
|
+
func RegisterAll() {
|
|
147
|
+
\tClear()
|
|
146
148
|
}
|
|
147
149
|
`;
|
|
148
|
-
fs.writeFileSync(
|
|
150
|
+
fs.writeFileSync(manifestPath, manifest, 'utf8');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function applyConfigOptions(targetDir, options) {
|
|
154
|
+
const configFiles = [
|
|
155
|
+
path.join(targetDir, 'apps', 'server', 'config.yaml'),
|
|
156
|
+
path.join(targetDir, 'apps', 'server', 'config.example.yaml'),
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
for (const filePath of configFiles) {
|
|
160
|
+
updateFile(filePath, (content) => applyYamlOptions(content, options));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function applyYamlOptions(content, options) {
|
|
166
|
+
let next = content;
|
|
167
|
+
if (options.db) {
|
|
168
|
+
next = updateYamlSectionValue(next, 'database', 'driver', options.db);
|
|
169
|
+
if (options.db === 'sqlite') {
|
|
170
|
+
next = updateYamlSectionValue(next, 'database', 'path', './data/keystone-local.db');
|
|
171
|
+
} else if (options.db === 'postgres') {
|
|
172
|
+
next = updateYamlSectionValue(next, 'database', 'path', '');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (options.queue) {
|
|
176
|
+
next = updateYamlSectionValue(next, 'queue', 'driver', options.queue);
|
|
177
|
+
}
|
|
178
|
+
if (options.storage) {
|
|
179
|
+
next = updateYamlSectionValue(next, 'storage', 'driver', options.storage);
|
|
180
|
+
}
|
|
181
|
+
return next;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function updateYamlSectionValue(content, section, key, value) {
|
|
185
|
+
const pattern = new RegExp(`(^${section}:\\s*[\\s\\S]*?^\\s*${key}:\\s*\")([^\"]*)(\")`, 'm');
|
|
186
|
+
if (!pattern.test(content)) {
|
|
187
|
+
return content;
|
|
188
|
+
}
|
|
189
|
+
return content.replace(pattern, `$1${value}$3`);
|
|
149
190
|
}
|
|
150
191
|
|
|
151
192
|
function removePath(target) {
|
|
@@ -164,3 +205,101 @@ function updateFile(filePath, updater) {
|
|
|
164
205
|
fs.writeFileSync(filePath, next, 'utf8');
|
|
165
206
|
}
|
|
166
207
|
}
|
|
208
|
+
|
|
209
|
+
function shouldMakeExecutable(filePath) {
|
|
210
|
+
const normalized = String(filePath).replace(/\\/g, '/');
|
|
211
|
+
if (normalized.endsWith('.sh')) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
return normalized.includes('/.husky/');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseArgs(argv) {
|
|
218
|
+
const out = { demo: null };
|
|
219
|
+
for (let i = 0; i < argv.length; i++) {
|
|
220
|
+
const arg = argv[i];
|
|
221
|
+
if (arg === '--demo') {
|
|
222
|
+
out.demo = true;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (arg === '--no-demo') {
|
|
226
|
+
out.demo = false;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (arg === '--help' || arg === '-h') {
|
|
230
|
+
out.help = true;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const profile = readValueOption(arg, argv, i, '--profile');
|
|
235
|
+
if (profile) {
|
|
236
|
+
out.profile = profile.value;
|
|
237
|
+
i += profile.skip;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const db = readValueOption(arg, argv, i, '--db');
|
|
241
|
+
if (db) {
|
|
242
|
+
out.db = db.value;
|
|
243
|
+
i += db.skip;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const queue = readValueOption(arg, argv, i, '--queue');
|
|
247
|
+
if (queue) {
|
|
248
|
+
out.queue = queue.value;
|
|
249
|
+
i += queue.skip;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const storage = readValueOption(arg, argv, i, '--storage');
|
|
253
|
+
if (storage) {
|
|
254
|
+
out.storage = storage.value;
|
|
255
|
+
i += storage.skip;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (arg.startsWith('-')) {
|
|
260
|
+
fail(`Unknown option: ${arg}`);
|
|
261
|
+
}
|
|
262
|
+
if (!out.target) {
|
|
263
|
+
out.target = arg;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
fail('Too many arguments.');
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function readValueOption(arg, argv, index, name) {
|
|
272
|
+
const prefix = `${name}=`;
|
|
273
|
+
if (arg.startsWith(prefix)) {
|
|
274
|
+
const value = arg.slice(prefix.length);
|
|
275
|
+
if (!value) {
|
|
276
|
+
fail(`Missing value for ${name}`);
|
|
277
|
+
}
|
|
278
|
+
return { value, skip: 0 };
|
|
279
|
+
}
|
|
280
|
+
if (arg === name) {
|
|
281
|
+
const value = argv[index + 1];
|
|
282
|
+
if (!value || value.startsWith('-')) {
|
|
283
|
+
fail(`Missing value for ${name}`);
|
|
284
|
+
}
|
|
285
|
+
return { value, skip: 1 };
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function normalizeChoice(value, choices, label) {
|
|
291
|
+
if (!value) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
const normalized = String(value).trim().toLowerCase();
|
|
295
|
+
if (!choices.includes(normalized)) {
|
|
296
|
+
fail(`Invalid ${label}: ${value}`);
|
|
297
|
+
}
|
|
298
|
+
return normalized;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function fail(message) {
|
|
302
|
+
console.error(message);
|
|
303
|
+
console.error(usage);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
package/package.json
CHANGED
package/template/README.md
CHANGED
|
@@ -1,41 +1,63 @@
|
|
|
1
|
-
# __RAW_NAME__
|
|
2
|
-
Minimal Keystone platform shell (web + server) with a demo module.
|
|
1
|
+
# __RAW_NAME__
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
- `docs/CONVENTIONS.md` - module structure, routing/menu/permission rules, API patterns, and tests.
|
|
3
|
+
Keystone 平台脚手架项目(Web + Server)。
|
|
6
4
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
- Go 1.23+
|
|
10
|
-
|
|
11
|
-
## Setup
|
|
12
|
-
1) Install dependencies:
|
|
5
|
+
## 快速开始
|
|
6
|
+
1) 安装依赖
|
|
13
7
|
```bash
|
|
14
8
|
pnpm install
|
|
15
9
|
```
|
|
16
10
|
|
|
17
|
-
2)
|
|
11
|
+
2) 启动开发环境(前后端一起)
|
|
18
12
|
```bash
|
|
19
|
-
pnpm
|
|
13
|
+
pnpm dev
|
|
20
14
|
```
|
|
21
15
|
|
|
22
|
-
3)
|
|
16
|
+
3) 单独启动
|
|
23
17
|
```bash
|
|
24
|
-
pnpm dev
|
|
18
|
+
pnpm server:dev
|
|
19
|
+
pnpm web:dev
|
|
25
20
|
```
|
|
26
21
|
|
|
27
|
-
##
|
|
28
|
-
- `apps/web
|
|
29
|
-
- `apps/server
|
|
30
|
-
- `
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
## 初始化后建议
|
|
23
|
+
- 复制 `apps/web/.env.example` 为 `apps/web/.env`(Vite 会读取)。
|
|
24
|
+
- 检查 `apps/server/config.yaml`(Go 运行配置)。
|
|
25
|
+
- 需要外部依赖时参考 `docker-compose.yml`。
|
|
26
|
+
- 建议阅读:`docs/CONVENTIONS.md`、`apps/web/README.md`、`apps/server/README.md`。
|
|
27
|
+
|
|
28
|
+
## 常用命令
|
|
29
|
+
- `pnpm dev`:跨平台开发启动(Air + Vite)。
|
|
30
|
+
- `pnpm build`:构建并嵌入前端,生成可执行后端产物。
|
|
31
|
+
- `pnpm test`:Web 类型检查 + Lint + Vitest + Go 测试。
|
|
32
|
+
- `pnpm clean`:清理构建产物。
|
|
33
|
+
|
|
34
|
+
## 目录结构
|
|
35
|
+
- `apps/web/`:React + Vite 壳与业务模块(`src/modules/*`)。
|
|
36
|
+
- `apps/server/`:Go API + 模块注册/迁移/种子数据(含 `config.yaml`)。
|
|
37
|
+
- `apps/server/internal/frontend/`:嵌入式前端目录(`dist/`)。
|
|
38
|
+
- `docs/CONVENTIONS.md`:项目规范与最佳实践。
|
|
39
|
+
- `apps/web/README.md`:前端壳与模块开发说明。
|
|
40
|
+
- `apps/server/README.md`:后端模块与运行说明。
|
|
41
|
+
- `scripts/`:跨平台 dev/build/test/clean。
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
```
|
|
44
|
+
.
|
|
45
|
+
├─ apps/
|
|
46
|
+
│ ├─ web/ # 前端壳与模块
|
|
47
|
+
│ └─ server/ # Go 服务与模块
|
|
48
|
+
├─ docs/ # 开发规范与说明
|
|
49
|
+
└─ scripts/ # 跨平台脚本
|
|
50
|
+
```
|
|
36
51
|
|
|
37
|
-
|
|
52
|
+
<!-- DEMO_START -->
|
|
53
|
+
## Demo 模块(可选)
|
|
54
|
+
如果需要 Demo,可在创建时使用 `--demo` 或 `--profile=full`。
|
|
55
|
+
- 菜单:Demo Tasks
|
|
56
|
+
- API:`/api/v1/demo/tasks`
|
|
57
|
+
- 权限:`demo:task:view`、`demo:task:manage`
|
|
58
|
+
<!-- DEMO_END -->
|
|
38
59
|
|
|
60
|
+
## 默认登录
|
|
39
61
|
- Tenant code: `default`
|
|
40
62
|
- Identifier: `admin`
|
|
41
63
|
- Password: `Admin123!`
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Air - Live reload for Go apps
|
|
2
|
+
# https://github.com/air-verse/air
|
|
3
|
+
root = "."
|
|
4
|
+
testdata_dir = "testdata"
|
|
5
|
+
tmp_dir = "tmp"
|
|
6
|
+
|
|
7
|
+
[build]
|
|
8
|
+
bin = "./tmp/main.exe"
|
|
9
|
+
cmd = "go build -o ./tmp/main.exe ./cmd/server"
|
|
10
|
+
delay = 1000
|
|
11
|
+
exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"]
|
|
12
|
+
exclude_file = []
|
|
13
|
+
exclude_regex = ["_test.go"]
|
|
14
|
+
exclude_unchanged = false
|
|
15
|
+
follow_symlink = false
|
|
16
|
+
include_dir = []
|
|
17
|
+
include_ext = ["go", "tpl", "tmpl", "html", "yaml", "yml"]
|
|
18
|
+
include_file = []
|
|
19
|
+
kill_delay = "0s"
|
|
20
|
+
log = "build-errors.log"
|
|
21
|
+
poll = false
|
|
22
|
+
poll_interval = 0
|
|
23
|
+
rerun = false
|
|
24
|
+
rerun_delay = 500
|
|
25
|
+
send_interrupt = false
|
|
26
|
+
stop_on_error = false
|
|
27
|
+
|
|
28
|
+
[color]
|
|
29
|
+
app = ""
|
|
30
|
+
build = "yellow"
|
|
31
|
+
main = "magenta"
|
|
32
|
+
runner = "green"
|
|
33
|
+
watcher = "cyan"
|
|
34
|
+
|
|
35
|
+
[log]
|
|
36
|
+
main_only = false
|
|
37
|
+
time = false
|
|
38
|
+
|
|
39
|
+
[misc]
|
|
40
|
+
clean_on_exit = true
|
|
41
|
+
|
|
42
|
+
[screen]
|
|
43
|
+
clear_on_rebuild = false
|
|
44
|
+
keep_scroll = true
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Server App
|
|
2
|
+
|
|
3
|
+
## 定位
|
|
4
|
+
- Go 后端应用入口在 `cmd/server/main.go`。
|
|
5
|
+
- 业务模块统一放在 `internal/modules/*`。
|
|
6
|
+
|
|
7
|
+
## 配置
|
|
8
|
+
- 主配置:`apps/server/config.yaml`。
|
|
9
|
+
- 模板配置:`apps/server/config.example.yaml`。
|
|
10
|
+
- 环境变量:需通过 Shell/工具注入(服务端不读取 `.env`)。
|
|
11
|
+
|
|
12
|
+
## 数据与存储
|
|
13
|
+
- SQLite:`apps/server/data/keystone-local.db`。
|
|
14
|
+
- 本地存储:`apps/server/storage/`(`storage.driver=local`)。
|
|
15
|
+
|
|
16
|
+
## 模块开发
|
|
17
|
+
1) 在 `internal/modules/<module>` 增加模块实现。
|
|
18
|
+
2) 在 `internal/modules/manifest.go` 注册模块。
|
|
19
|
+
3) 在 `apps/server/config.yaml` 的 `modules.enabled` 启用模块。
|
|
20
|
+
|
|
21
|
+
## 嵌入式前端
|
|
22
|
+
- 前端产物输出到 `internal/frontend/dist`。
|
|
23
|
+
- Go 使用 `//go:embed` 直接嵌入静态资源。
|
|
24
|
+
|
|
25
|
+
## 常用命令
|
|
26
|
+
- 开发:`pnpm server:dev`
|
|
27
|
+
- 测试:`go -C apps/server test ./...`
|