@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.
Files changed (54) hide show
  1. package/README.md +19 -10
  2. package/bin/create-keystone-app.js +199 -60
  3. package/package.json +1 -1
  4. package/template/.husky/pre-commit +4 -0
  5. package/template/.lintstagedrc.json +5 -0
  6. package/template/.prettierrc +9 -0
  7. package/template/README.md +45 -23
  8. package/template/apps/server/.air.toml +44 -0
  9. package/template/apps/server/README.md +27 -0
  10. package/template/apps/server/cmd/server/main.go +213 -0
  11. package/template/apps/server/config.yaml +51 -0
  12. package/template/apps/server/docs/docs.go +34 -0
  13. package/template/apps/server/go.mod +13 -0
  14. package/template/apps/server/go.sum +72 -0
  15. package/template/apps/server/internal/app/routes/module_routes.go +16 -0
  16. package/template/apps/server/internal/app/routes/routes.go +226 -0
  17. package/template/apps/server/internal/app/startup/startup.go +74 -0
  18. package/template/apps/server/internal/frontend/dist/.gitkeep +1 -0
  19. package/template/apps/server/internal/frontend/embed.go +28 -0
  20. package/template/apps/server/internal/frontend/handler.go +122 -0
  21. package/template/apps/server/internal/{demo/demo.go → modules/demo/handlers.go} +4 -1
  22. package/template/apps/server/internal/modules/demo/module.go +55 -0
  23. package/template/apps/server/internal/modules/manifest.go +11 -0
  24. package/template/apps/server/internal/modules/registry.go +145 -0
  25. package/template/apps/web/.env.example +3 -0
  26. package/template/apps/web/README.md +29 -0
  27. package/template/apps/web/eslint.config.js +35 -0
  28. package/template/apps/web/package.json +27 -10
  29. package/template/apps/web/postcss.config.js +6 -0
  30. package/template/apps/web/src/index.css +3 -0
  31. package/template/apps/web/src/main.tsx +1 -0
  32. package/template/apps/web/src/modules/demo/help/overview.md +12 -0
  33. package/template/apps/web/src/modules/demo/routes.tsx +2 -0
  34. package/template/apps/web/tailwind.config.js +18 -0
  35. package/template/apps/web/tests/setup.ts +37 -0
  36. package/template/apps/web/tsconfig.app.json +3 -3
  37. package/template/apps/web/vite.config.ts +28 -2
  38. package/template/docker-compose.yml +45 -0
  39. package/template/docs/CONVENTIONS.md +61 -88
  40. package/template/package.json +15 -3
  41. package/template/scripts/build.bat +133 -0
  42. package/template/scripts/build.js +25 -0
  43. package/template/scripts/build.sh +99 -0
  44. package/template/scripts/clean.bat +35 -0
  45. package/template/scripts/clean.js +25 -0
  46. package/template/scripts/clean.sh +34 -0
  47. package/template/scripts/dev.bat +82 -0
  48. package/template/scripts/dev.js +25 -0
  49. package/template/scripts/dev.sh +88 -0
  50. package/template/scripts/test.bat +86 -0
  51. package/template/scripts/test.js +25 -0
  52. package/template/scripts/test.sh +86 -0
  53. package/template/apps/server/main.go +0 -28
  54. /package/template/{config.yaml → apps/server/config.example.yaml} +0 -0
@@ -0,0 +1,29 @@
1
+ # Web Shell
2
+
3
+ ## 定位
4
+ - 负责渲染平台 UI,业务模块统一放在 `src/modules/*`。
5
+ - 平台核心能力由 `@robsun/keystone-web-core` 提供。
6
+
7
+ ## 入口
8
+ - `src/main.tsx`:应用入口。
9
+ - `src/app.config.ts`:品牌与模块启用配置。
10
+ - `src/modules/<module>/index.ts`:模块注册入口。
11
+ - `apps/web/.env.example`:前端环境变量模板(Vite 读取)。
12
+
13
+ ## 新建模块
14
+ 1) 创建目录:`src/modules/<module>`。
15
+ 2) 在 `index.ts` 调用 `registerModule` 注册路由。
16
+ 3) 在 `routes.tsx` 配置菜单、权限、帮助文档。
17
+ 4) 在 `app.config.ts` 的 `modules.enabled` 启用模块。
18
+
19
+ ## 帮助文档
20
+ - 放在 `src/modules/<module>/help/**`。
21
+ - `helpKey` 与路由 `handle.helpKey` 保持一致。
22
+
23
+ ## 常用命令
24
+ - 开发:`pnpm web:dev`
25
+ - Lint:`pnpm -C apps/web lint`
26
+ - 测试:`pnpm -C apps/web test`
27
+
28
+ ## 嵌入式构建
29
+ - `pnpm build` 会将产物写入 `apps/server/internal/frontend/dist`。
@@ -0,0 +1,35 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+ import { defineConfig, globalIgnores } from 'eslint/config'
7
+
8
+ export default defineConfig([
9
+ globalIgnores([
10
+ 'dist',
11
+ 'build',
12
+ '*.min.js',
13
+ 'coverage',
14
+ 'node_modules',
15
+ '.turbo',
16
+ '.pnpm-store',
17
+ 'apps/server/tmp',
18
+ 'data',
19
+ '*.log',
20
+ '.env*',
21
+ ]),
22
+ {
23
+ files: ['**/*.{ts,tsx}'],
24
+ extends: [
25
+ js.configs.recommended,
26
+ tseslint.configs.recommended,
27
+ reactHooks.configs.flat.recommended,
28
+ reactRefresh.configs.vite,
29
+ ],
30
+ languageOptions: {
31
+ ecmaVersion: 2020,
32
+ globals: globals.browser,
33
+ },
34
+ },
35
+ ])
@@ -6,28 +6,45 @@
6
6
  "scripts": {
7
7
  "dev": "vite",
8
8
  "build": "vite build",
9
- "preview": "vite preview"
9
+ "build:strict": "tsc -b && vite build",
10
+ "lint": "eslint .",
11
+ "preview": "vite preview",
12
+ "lint:fix": "eslint . --fix",
13
+ "format": "prettier --write \"src/**/*.{ts,tsx}\"",
14
+ "format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
15
+ "typecheck": "tsc --noEmit",
16
+ "test": "vitest"
10
17
  },
11
18
  "dependencies": {
12
19
  "@ant-design/icons": "^6.1.0",
13
20
  "@robsun/keystone-web-core": "0.1.7",
14
21
  "antd": "^6.0.1",
15
- "cookie": "^1.1.1",
16
22
  "dayjs": "^1.11.19",
17
- "debug": "^4.4.3",
18
- "extend": "^3.0.2",
19
23
  "react": "^19.2.0",
20
24
  "react-dom": "^19.2.0",
21
- "react-router": "^7.10.1",
22
- "react-router-dom": "^7.10.1",
23
- "style-to-js": "^1.1.21"
25
+ "react-router-dom": "^7.10.1"
24
26
  },
25
27
  "devDependencies": {
28
+ "@eslint/js": "^9.39.1",
29
+ "@tailwindcss/postcss": "^4.1.17",
30
+ "@testing-library/jest-dom": "^6.6.3",
31
+ "@testing-library/react": "^16.2.0",
32
+ "@testing-library/user-event": "^14.6.1",
26
33
  "@types/node": "^24.10.1",
27
34
  "@types/react": "^19.2.5",
28
35
  "@types/react-dom": "^19.2.3",
36
+ "@vitejs/plugin-react": "^5.1.1",
37
+ "autoprefixer": "^10.4.22",
38
+ "eslint": "^9.39.1",
39
+ "eslint-plugin-react-hooks": "^7.0.1",
40
+ "eslint-plugin-react-refresh": "^0.4.24",
41
+ "globals": "^16.5.0",
42
+ "jsdom": "^24.1.0",
43
+ "postcss": "^8.5.6",
44
+ "tailwindcss": "^4.1.17",
29
45
  "typescript": "~5.9.3",
30
- "vite": "^7.2.4"
46
+ "typescript-eslint": "^8.46.4",
47
+ "vite": "^7.2.4",
48
+ "vitest": "^2.1.4"
31
49
  }
32
- }
33
-
50
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ '@tailwindcss/postcss': {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -4,6 +4,7 @@ import dayjs from 'dayjs'
4
4
  import { KeystoneApp, getKeystoneConfig, setKeystoneConfig } from '@robsun/keystone-web-core'
5
5
  import { appConfig } from './app.config'
6
6
  import '@robsun/keystone-web-core/styles/keystone.css'
7
+ import './index.css'
7
8
  import './modules/demo'
8
9
 
9
10
  setKeystoneConfig(appConfig)
@@ -0,0 +1,12 @@
1
+ ---
2
+ helpKey: "demo/tasks"
3
+ title: "Demo Tasks"
4
+ description: "Manage sample tasks in the scaffold."
5
+ category: "demo"
6
+ tags: ["demo", "tasks"]
7
+ ---
8
+
9
+ # Demo Tasks
10
+
11
+ Use this module as a reference for list + create + update flows. Replace it
12
+ with real business logic when you build new modules.
@@ -33,6 +33,8 @@ export const demoRoutes: RouteObject[] = [
33
33
  handle: {
34
34
  menu: { label: 'Demo Tasks', icon: <AppstoreOutlined /> },
35
35
  breadcrumb: 'Demo Tasks',
36
+ permission: 'demo:task:view',
37
+ helpKey: 'demo/tasks',
36
38
  },
37
39
  },
38
40
  ].map((route) => ({
@@ -0,0 +1,18 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
4
+ corePlugins: {
5
+ preflight: false,
6
+ },
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ primary: '#1890ff',
11
+ success: '#52c41a',
12
+ warning: '#faad14',
13
+ error: '#ff4d4f',
14
+ },
15
+ },
16
+ },
17
+ plugins: [],
18
+ }
@@ -0,0 +1,37 @@
1
+ import '@testing-library/jest-dom'
2
+ import { vi } from 'vitest'
3
+
4
+ window.matchMedia = vi.fn().mockImplementation((query: string) => ({
5
+ matches: false,
6
+ media: query,
7
+ onchange: null,
8
+ addListener: vi.fn(),
9
+ removeListener: vi.fn(),
10
+ addEventListener: vi.fn(),
11
+ removeEventListener: vi.fn(),
12
+ dispatchEvent: vi.fn(),
13
+ }))
14
+
15
+ if (!('ResizeObserver' in window)) {
16
+ class ResizeObserver {
17
+ observe() {}
18
+ unobserve() {}
19
+ disconnect() {}
20
+ }
21
+ // @ts-expect-error test shim
22
+ window.ResizeObserver = ResizeObserver
23
+ }
24
+
25
+ const originalGetComputedStyle = window.getComputedStyle
26
+ window.getComputedStyle = (elt: Element) => {
27
+ const style = originalGetComputedStyle
28
+ ? originalGetComputedStyle(elt)
29
+ : ({} as CSSStyleDeclaration)
30
+ return {
31
+ ...style,
32
+ getPropertyValue: (prop: string) =>
33
+ style?.getPropertyValue ? style.getPropertyValue(prop) : '',
34
+ } as CSSStyleDeclaration
35
+ }
36
+
37
+ export {}
@@ -5,7 +5,7 @@
5
5
  "useDefineForClassFields": true,
6
6
  "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
7
  "module": "ESNext",
8
- "types": ["vite/client"],
8
+ "types": ["vite/client", "vitest/globals", "@testing-library/jest-dom"],
9
9
  "skipLibCheck": true,
10
10
  "moduleResolution": "bundler",
11
11
  "allowImportingTsExtensions": true,
@@ -19,5 +19,5 @@
19
19
  "noFallthroughCasesInSwitch": true,
20
20
  "noUncheckedSideEffectImports": true
21
21
  },
22
- "include": ["src"]
23
- }
22
+ "include": ["src", "tests"]
23
+ }
@@ -1,6 +1,14 @@
1
+ /// <reference types="vitest" />
1
2
  import { createKeystoneViteConfig } from '@robsun/keystone-web-core/vite'
3
+ import path from 'path'
2
4
 
3
5
  export default createKeystoneViteConfig({
6
+ helpDir: path.resolve(__dirname, './src/modules'),
7
+ resolve: {
8
+ alias: {
9
+ '@app': path.resolve(__dirname, './src'),
10
+ },
11
+ },
4
12
  server: {
5
13
  port: 3000,
6
14
  proxy: {
@@ -10,5 +18,23 @@ export default createKeystoneViteConfig({
10
18
  },
11
19
  },
12
20
  },
13
- })
14
-
21
+ build: {
22
+ outDir: path.resolve(__dirname, '../server/internal/frontend/dist'),
23
+ emptyOutDir: false,
24
+ sourcemap: true,
25
+ rollupOptions: {
26
+ output: {
27
+ manualChunks: {
28
+ vendor: ['react', 'react-dom'],
29
+ },
30
+ },
31
+ },
32
+ },
33
+ test: {
34
+ globals: true,
35
+ environment: 'jsdom',
36
+ setupFiles: './tests/setup.ts',
37
+ include: ['tests/**/*.{test,spec}.{ts,tsx}'],
38
+ exclude: ['**/node_modules/**', 'tests/e2e/**'],
39
+ },
40
+ })
@@ -0,0 +1,45 @@
1
+ version: '3.8'
2
+
3
+ services:
4
+ postgres:
5
+ image: postgres:17
6
+ container_name: keystone-postgres
7
+ restart: unless-stopped
8
+ ports:
9
+ - '5432:5432'
10
+ environment:
11
+ POSTGRES_DB: keystone
12
+ POSTGRES_USER: keystone
13
+ POSTGRES_PASSWORD: keystone
14
+ volumes:
15
+ - pgdata:/var/lib/postgresql/data
16
+ healthcheck:
17
+ test: ['CMD-SHELL', 'pg_isready -U keystone -d keystone']
18
+ interval: 10s
19
+ timeout: 5s
20
+ retries: 5
21
+
22
+ # Redis (optional, uncomment when needed)
23
+ # redis:
24
+ # image: redis:7-alpine
25
+ # container_name: keystone-redis
26
+ # restart: unless-stopped
27
+ # ports:
28
+ # - '6379:6379'
29
+ # volumes:
30
+ # - redisdata:/data
31
+ # healthcheck:
32
+ # test: ['CMD', 'redis-cli', 'ping']
33
+ # interval: 10s
34
+ # timeout: 5s
35
+ # retries: 5
36
+
37
+ volumes:
38
+ pgdata:
39
+ driver: local
40
+ # redisdata:
41
+ # driver: local
42
+
43
+ networks:
44
+ default:
45
+ name: keystone-network
@@ -1,33 +1,42 @@
1
- # Keystone App Conventions
2
-
3
- ## Goals
4
- - Keep modules predictable across web and server.
5
- - Keep routing, menus, permissions, and help content in one place.
6
- - Follow the tradeflow app patterns for structure and naming.
7
-
8
- ## Project Layout
9
- - `apps/web/`: React + Vite app.
10
- - `apps/server/`: Go API app.
11
- - `apps/web/src/app.config.ts`: web runtime config (modules, branding, approval).
12
- - `config.yaml`: server runtime config (modules, database, queue, storage).
13
-
14
- ## Frontend Module Rules
15
- - Each business module lives in `apps/web/src/modules/<module>`.
16
- - `index.ts` must register the module:
17
- - `registerModule({ name, routes, i18n })`.
18
- - `routes.tsx` owns menu/breadcrumb/permission metadata via `handle`.
19
- - Pages go in `pages/`, shared UI in `components/`, API calls in `services/`, types in `types.ts`, shared state in `stores/`.
20
- - Lazy-load page components and wrap with `Suspense`.
21
-
22
- Example module entry:
1
+ # Keystone App Conventions
2
+
3
+ ## 目标
4
+ - Web/Server 模块结构统一,便于复用与维护。
5
+ - 路由、菜单、权限、帮助文档集中管理。
6
+ - 约定优于配置,降低上手成本。
7
+
8
+ ## 项目结构
9
+ - `apps/web/`:React + Vite 壳与业务模块。
10
+ - `apps/server/`:Go 服务端(`cmd/server` 启动,`internal/app` 编排)。
11
+ - `apps/server/internal/modules/`:后端模块注册与实现。
12
+ - `apps/server/internal/frontend/`:嵌入式前端输出目录(`dist/`)。
13
+ - `scripts/`:跨平台 dev/build/test/clean。
14
+ - `apps/server/config.yaml`:服务端配置(支持环境变量覆盖,`docker-compose.yml` 提供依赖)。
15
+ 提示:更细节的入口说明见 `apps/web/README.md` `apps/server/README.md`。
16
+
17
+ ## 开发流程
18
+ - `pnpm dev`:执行 `scripts/dev.*`,检查环境并启动 Air + Vite。
19
+ - `apps/server/.air.toml`:后端热重载配置。
20
+ - `pnpm server:dev` 仅启动后端;`pnpm web:dev` 仅启动前端。
21
+
22
+ ## 配置与环境
23
+ - `apps/server/config.yaml` 为默认配置;`apps/server/config.example.yaml` 用作模板。
24
+ - `apps/server/config.yaml` 供 `go run`/Air 使用。
25
+ - 前端环境变量参考 `apps/web/.env.example`(Vite 会读取)。
26
+ - 后端环境变量需通过 Shell/工具注入(服务端不读取 `.env`)。
27
+ - 本地数据库默认 SQLite:`apps/server/data/keystone-local.db`。
28
+ - 本地存储目录:`apps/server/storage/`(当 `storage.driver=local`)。
29
+
30
+ ## 前端模块规范
31
+ - 模块目录:`apps/web/src/modules/<module>`。
32
+ - 在 `index.ts` 注册模块:
23
33
  ```ts
24
34
  import { registerModule } from '@robsun/keystone-web-core'
25
35
  import { orderRoutes } from './routes'
26
36
 
27
37
  registerModule({ name: 'orders', routes: orderRoutes })
28
38
  ```
29
-
30
- Example route meta:
39
+ - 在 `routes.tsx` 配置菜单/权限/帮助:
31
40
  ```tsx
32
41
  {
33
42
  path: 'orders',
@@ -40,62 +49,15 @@ Example route meta:
40
49
  },
41
50
  }
42
51
  ```
52
+ - 页面放 `pages/`,组件放 `components/`,API 放 `services/`。
43
53
 
44
- ## Permissions and Menu Rules
45
- - Permission format: `domain:resource:action` (wildcards allowed, e.g. `inventory:*:*`).
46
- - Menus are derived from `handle.menu`. If a route should not appear, omit `handle.menu`.
47
- - Breadcrumbs are derived from `handle.breadcrumb`.
48
- - Use `handle.menu.order` to control menu ordering when needed.
49
-
50
- ## Help Docs
51
- - Store help markdown under `apps/web/src/modules/<module>/help/**`.
52
- - Use frontmatter keys: `helpKey`, `title`, `description`, `category`, `tags`, `relatedKeys`.
53
- - Route `handle.helpKey` must match the markdown `helpKey`.
54
- - Enable app help by pointing Vite to your help folder (`helpDir` in `vite.config.ts`).
55
-
56
- Frontmatter example:
57
- ```md
58
- ---
59
- helpKey: "inventory/warehouses"
60
- title: "Warehouse Management"
61
- description: "Manage warehouses and locations."
62
- category: "inventory"
63
- tags: ["warehouse", "location"]
64
- relatedKeys: ["inventory/stock"]
65
- ---
66
- ```
67
-
68
- ## API and Services
69
- - Use `api` from `@robsun/keystone-web-core` for HTTP.
70
- - If your API base path changes, call `setApiBaseUrl` once at startup.
71
- - Keep API calls in `services/*.ts`, return typed data, and let pages handle UI state.
72
- - For paginated endpoints, map `page`/`page_size` (server) to `page`/`pageSize` (web).
73
-
74
- Example service:
75
- ```ts
76
- import { api, type ApiResponse, type PaginatedData } from '@robsun/keystone-web-core'
77
-
78
- export async function listItems(page = 1, pageSize = 20) {
79
- const { data } = await api.get<ApiResponse<PaginatedData<Item>>>('/items', {
80
- params: { page, page_size: pageSize },
81
- })
82
- return data.data
83
- }
84
- ```
85
-
86
- ## State
87
- - Use local state for single pages.
88
- - Use zustand stores for shared state or cross-page flows.
89
- - Store shape should include `loading`, `error`, `filters`, and a `fetch` method.
90
-
91
- ## Naming
92
- - Page components: `*Page.tsx` (e.g. `InventoryListPage.tsx`).
93
- - API modules: `services/api.ts` or `services/<feature>.ts`.
94
- - Store modules: `stores/*Store.ts` or `stores/use*Store.ts`.
95
- - Domain types: `types.ts` (export interfaces and enums).
54
+ ## Help 文档
55
+ - 文档放在 `apps/web/src/modules/<module>/help/**`。
56
+ - `helpKey` 与路由 `handle.helpKey` 保持一致。
57
+ - Vite 已配置 `helpDir` 扫描 `src/modules`。
96
58
 
97
- ## Backend Module Rules
98
- For production modules, mirror this layout:
59
+ ## 后端模块规范
60
+ 推荐结构:
99
61
  ```
100
62
  apps/server/internal/modules/<module>/
101
63
  module.go
@@ -107,7 +69,7 @@ apps/server/internal/modules/<module>/
107
69
  bootstrap/seeds/
108
70
  ```
109
71
 
110
- Module interface (simplified):
72
+ 模块接口:
111
73
  ```go
112
74
  type Module interface {
113
75
  Name() string
@@ -120,15 +82,26 @@ type Module interface {
120
82
  }
121
83
  ```
122
84
 
123
- Register modules and enable them via `config.yaml` (`modules.enabled`).
124
- Keep the enabled list aligned with `app.config.ts`.
85
+ ## 模块注册与启用
86
+ - `apps/server/internal/modules/manifest.go` 注册模块。
87
+ - 在 `apps/server/config.yaml` 的 `modules.enabled` 启用模块。
88
+ - Web 端 `app.config.ts` 保持同名启用列表。
89
+
90
+ ## 迁移 / 种子 / 权限
91
+ - `startup.InitializeDatabase` 统一执行:核心迁移 + 模块迁移 + 种子 + 权限注册。
92
+ - 模块权限通过 `RegisterPermissions` 注入。
93
+
94
+ ## 任务与队列
95
+ - Worker 在 `cmd/server/main.go` 启动。
96
+ - 模块通过 `RegisterJobs` 注册任务处理器。
97
+ - 队列配置在 `apps/server/config.yaml` 的 `queue` 部分。
125
98
 
126
- ## Routing and Responses (Server)
127
- - Mount API under `/api/v1`.
128
- - Use auth, rbac, and tenant guards consistently across module routes.
129
- - Use `github.com/robsuncn/keystone/api/response` helpers for JSON responses.
99
+ ## 嵌入式前端
100
+ - Vite `build.outDir` 指向 `apps/server/internal/frontend/dist`。
101
+ - Go 通过 `//go:embed` 嵌入前端产物。
102
+ - 保留 `apps/server/internal/frontend/dist/.gitkeep`,避免 embed 找不到目录。
130
103
 
131
104
  ## Testing
132
- - Web: `apps/web/tests/{unit,component,e2e,perf}` with `*.test.ts(x)`.
133
- - Server: `apps/server/tests/{unit,integration,e2e,fixtures}` with `*_test.go`.
134
- - Run `go test ./...` for server changes; add a web test runner when needed.
105
+ - Web:`pnpm -C apps/web test`(Vitest)。
106
+ - Server:`go -C apps/server test ./...`。
107
+ - 全量:`pnpm test`。
@@ -3,9 +3,21 @@
3
3
  "private": true,
4
4
  "version": "0.1.0",
5
5
  "scripts": {
6
- "dev": "pnpm -C apps/web dev",
6
+ "dev": "node scripts/dev.js",
7
+ "build": "node scripts/build.js",
8
+ "build:web": "pnpm --filter web build",
9
+ "test": "node scripts/test.js",
10
+ "lint": "pnpm --filter web lint",
11
+ "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
12
+ "clean": "node scripts/clean.js",
7
13
  "web:dev": "pnpm -C apps/web dev",
8
- "server:dev": "go -C apps/server run ."
14
+ "server:dev": "go -C apps/server run ./cmd/server",
15
+ "prepare": "husky"
16
+ },
17
+ "devDependencies": {
18
+ "husky": "^9.1.7",
19
+ "lint-staged": "^16.2.7",
20
+ "prettier": "^3.7.4"
9
21
  },
10
22
  "packageManager": "pnpm@9.15.0"
11
- }
23
+ }
@@ -0,0 +1,133 @@
1
+ @echo off
2
+ setlocal enabledelayedexpansion
3
+
4
+ echo ========================================
5
+ echo Keystone Production Build
6
+ echo (Single Executable with Embedded Frontend)
7
+ echo ========================================
8
+ echo.
9
+
10
+ set "PROJECT_ROOT=%~dp0.."
11
+ set "FRONTEND_DIR=%PROJECT_ROOT%\apps\web"
12
+ set "BACKEND_DIR=%PROJECT_ROOT%\apps\server"
13
+ set "EMBED_DIR=%BACKEND_DIR%\internal\frontend\dist"
14
+ set "OUTPUT_DIR=%PROJECT_ROOT%\dist"
15
+ set "OUTPUT_NAME=__APP_NAME__"
16
+
17
+ echo Checking prerequisites...
18
+ where node >nul 2>&1
19
+ if errorlevel 1 (
20
+ echo [ERROR] Node.js is not installed
21
+ exit /b 1
22
+ )
23
+ echo [OK] Node.js found
24
+
25
+ where pnpm >nul 2>&1
26
+ if errorlevel 1 (
27
+ echo [ERROR] pnpm is not installed
28
+ exit /b 1
29
+ )
30
+ echo [OK] pnpm found
31
+
32
+ where go >nul 2>&1
33
+ if errorlevel 1 (
34
+ echo [ERROR] Go is not installed
35
+ exit /b 1
36
+ )
37
+ echo [OK] Go found
38
+
39
+ echo.
40
+ echo Building production artifacts...
41
+ echo.
42
+
43
+ if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
44
+ if not exist "%EMBED_DIR%" mkdir "%EMBED_DIR%"
45
+
46
+ set FAILED=0
47
+
48
+ echo ----------------------------------------
49
+ echo [1/4] Cleaning embed directory
50
+ echo ----------------------------------------
51
+ for /f "delims=" %%i in ('dir /b /a-d "%EMBED_DIR%\*" 2^>nul ^| findstr /v "^\.gitkeep$"') do del /q "%EMBED_DIR%\%%i" 2>nul
52
+ for /d %%i in ("%EMBED_DIR%\*") do rd /s /q "%%i" 2>nul
53
+ echo [OK] Embed directory cleaned
54
+
55
+ echo ----------------------------------------
56
+ echo [2/4] Building Frontend
57
+ echo ----------------------------------------
58
+ cd /d "%FRONTEND_DIR%"
59
+ call pnpm build
60
+ if errorlevel 1 (
61
+ echo [FAIL] Frontend build failed
62
+ set FAILED=1
63
+ cd /d "%PROJECT_ROOT%"
64
+ exit /b 1
65
+ )
66
+ echo [OK] Frontend build completed
67
+ cd /d "%PROJECT_ROOT%"
68
+
69
+ echo ----------------------------------------
70
+ echo [3/4] Building Backend (with embedded frontend)
71
+ echo ----------------------------------------
72
+ cd /d "%BACKEND_DIR%"
73
+
74
+ echo Building for Windows (amd64)...
75
+ set CGO_ENABLED=0
76
+ set GOOS=windows
77
+ set GOARCH=amd64
78
+ go build -ldflags="-s -w" -o "%OUTPUT_DIR%\%OUTPUT_NAME%.exe" ./cmd/server
79
+ if errorlevel 1 (
80
+ echo [FAIL] Windows build failed
81
+ set FAILED=1
82
+ cd /d "%PROJECT_ROOT%"
83
+ exit /b 1
84
+ )
85
+ echo [OK] Windows build completed
86
+
87
+ echo Building for Linux (amd64)...
88
+ set CGO_ENABLED=0
89
+ set GOOS=linux
90
+ set GOARCH=amd64
91
+ go build -ldflags="-s -w" -o "%OUTPUT_DIR%\%OUTPUT_NAME%" ./cmd/server
92
+ if errorlevel 1 (
93
+ echo [FAIL] Linux build failed
94
+ set FAILED=1
95
+ cd /d "%PROJECT_ROOT%"
96
+ exit /b 1
97
+ )
98
+ echo [OK] Linux build completed
99
+
100
+ cd /d "%PROJECT_ROOT%"
101
+
102
+ echo ----------------------------------------
103
+ echo [4/4] Copying configuration template
104
+ echo ----------------------------------------
105
+ if exist "%BACKEND_DIR%\config.example.yaml" (
106
+ copy /Y "%BACKEND_DIR%\config.example.yaml" "%OUTPUT_DIR%\config.example.yaml" >nul
107
+ echo [OK] Config template copied
108
+ )
109
+
110
+ echo.
111
+ echo ========================================
112
+ if "%FAILED%"=="1" goto :build_failed
113
+
114
+ echo Build completed successfully!
115
+ echo.
116
+ echo Executables (frontend embedded):
117
+ echo - dist\%OUTPUT_NAME%.exe (Windows)
118
+ echo - dist\%OUTPUT_NAME% (Linux)
119
+ echo.
120
+ echo Configuration:
121
+ echo - dist\config.example.yaml
122
+ echo.
123
+ echo Usage:
124
+ echo Windows: cd dist ^&^& %OUTPUT_NAME%.exe
125
+ echo Linux: cd dist ^&^& ./%OUTPUT_NAME%
126
+ goto :build_done
127
+
128
+ :build_failed
129
+ echo Build FAILED
130
+ exit /b 1
131
+
132
+ :build_done
133
+ echo ========================================