polyforge-cli 0.1.0

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 (114) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/dist/commands/create.js +46 -0
  4. package/dist/commands/doctor.js +136 -0
  5. package/dist/commands/list.js +32 -0
  6. package/dist/core/git.js +25 -0
  7. package/dist/core/installer.js +64 -0
  8. package/dist/core/prompts.js +147 -0
  9. package/dist/core/renderer.js +175 -0
  10. package/dist/core/validator.js +86 -0
  11. package/dist/index.js +47 -0
  12. package/dist/templates/backend/go-gin/apps/api/Makefile +10 -0
  13. package/dist/templates/backend/go-gin/apps/api/cmd/server/main.go +29 -0
  14. package/dist/templates/backend/go-gin/apps/api/go.mod +5 -0
  15. package/dist/templates/backend/go-gin/apps/api/internal/config/config.go +22 -0
  16. package/dist/templates/backend/go-gin/apps/api/internal/handler/health.go +27 -0
  17. package/dist/templates/backend/go-gin/apps/api/internal/handler/helpers.go +8 -0
  18. package/dist/templates/backend/go-gin/apps/api/internal/handler/ping.go +15 -0
  19. package/dist/templates/backend/go-gin/apps/api/internal/middleware/logger.go +18 -0
  20. package/dist/templates/backend/go-gin/apps/api/internal/middleware/recovery.go +23 -0
  21. package/dist/templates/backend/go-gin/apps/api/internal/middleware/trace.go +28 -0
  22. package/dist/templates/backend/go-gin/apps/api/internal/repository/ping.go +11 -0
  23. package/dist/templates/backend/go-gin/apps/api/internal/service/ping.go +17 -0
  24. package/dist/templates/backend/go-gin/apps/api/pkg/response/response.go +35 -0
  25. package/dist/templates/backend/go-gin/apps/api/pkg/response/response_test.go +21 -0
  26. package/dist/templates/backend/springboot/apps/api/pom.xml +44 -0
  27. package/dist/templates/backend/springboot/apps/api/src/main/java/com/scaffold/api/Application.java +11 -0
  28. package/dist/templates/backend/springboot/apps/api/src/main/java/com/scaffold/api/common/ApiResponse.java +19 -0
  29. package/dist/templates/backend/springboot/apps/api/src/main/java/com/scaffold/api/common/GlobalExceptionHandler.java +22 -0
  30. package/dist/templates/backend/springboot/apps/api/src/main/java/com/scaffold/api/common/TraceIdHolder.java +19 -0
  31. package/dist/templates/backend/springboot/apps/api/src/main/java/com/scaffold/api/config/TraceIdFilter.java +38 -0
  32. package/dist/templates/backend/springboot/apps/api/src/main/java/com/scaffold/api/controller/HealthController.java +17 -0
  33. package/dist/templates/backend/springboot/apps/api/src/main/java/com/scaffold/api/controller/PingController.java +23 -0
  34. package/dist/templates/backend/springboot/apps/api/src/main/java/com/scaffold/api/repository/PingRepository.java +10 -0
  35. package/dist/templates/backend/springboot/apps/api/src/main/java/com/scaffold/api/service/PingService.java +17 -0
  36. package/dist/templates/backend/springboot/apps/api/src/main/resources/application-dev.yml +2 -0
  37. package/dist/templates/backend/springboot/apps/api/src/main/resources/application-prod.yml +2 -0
  38. package/dist/templates/backend/springboot/apps/api/src/main/resources/application-test.yml +2 -0
  39. package/dist/templates/backend/springboot/apps/api/src/main/resources/application.yml +8 -0
  40. package/dist/templates/backend/springboot/apps/api/src/test/java/com/scaffold/api/ApplicationTests.java +12 -0
  41. package/dist/templates/base/.env.example +5 -0
  42. package/dist/templates/base/docs/ARCHITECTURE.md +9 -0
  43. package/dist/templates/base/infra/scripts/build.sh +39 -0
  44. package/dist/templates/base/infra/scripts/check.sh +58 -0
  45. package/dist/templates/base/infra/scripts/dev.sh +32 -0
  46. package/dist/templates/base/infra/scripts/test.sh +41 -0
  47. package/dist/templates/base/package.json +11 -0
  48. package/dist/templates/data/mongodb/infra/data/mongodb/README.md +4 -0
  49. package/dist/templates/data/mysql/infra/data/mysql/README.md +4 -0
  50. package/dist/templates/data/mysql/infra/data/mysql/schema.sql +5 -0
  51. package/dist/templates/data/postgresql/infra/data/postgresql/README.md +4 -0
  52. package/dist/templates/data/postgresql/infra/data/postgresql/schema.sql +5 -0
  53. package/dist/templates/data/redis/infra/data/redis/README.md +4 -0
  54. package/dist/templates/data/sqlite/infra/data/sqlite/README.md +4 -0
  55. package/dist/templates/frontend/react/apps/web/.env.example +4 -0
  56. package/dist/templates/frontend/react/apps/web/index.html +12 -0
  57. package/dist/templates/frontend/react/apps/web/package.json +23 -0
  58. package/dist/templates/frontend/react/apps/web/src/App.test.tsx +7 -0
  59. package/dist/templates/frontend/react/apps/web/src/App.tsx +59 -0
  60. package/dist/templates/frontend/react/apps/web/src/api/client.ts +20 -0
  61. package/dist/templates/frontend/react/apps/web/src/api/request.ts +104 -0
  62. package/dist/templates/frontend/react/apps/web/src/api/services/api.ts +15 -0
  63. package/dist/templates/frontend/react/apps/web/src/api/services/bff.ts +11 -0
  64. package/dist/templates/frontend/react/apps/web/src/main.tsx +9 -0
  65. package/dist/templates/frontend/react/apps/web/tsconfig.json +12 -0
  66. package/dist/templates/frontend/react/apps/web/vite.config.ts +22 -0
  67. package/dist/templates/frontend/vue/apps/web/.env.example +4 -0
  68. package/dist/templates/frontend/vue/apps/web/index.html +12 -0
  69. package/dist/templates/frontend/vue/apps/web/package.json +20 -0
  70. package/dist/templates/frontend/vue/apps/web/src/App.test.ts +7 -0
  71. package/dist/templates/frontend/vue/apps/web/src/App.vue +59 -0
  72. package/dist/templates/frontend/vue/apps/web/src/api/client.ts +20 -0
  73. package/dist/templates/frontend/vue/apps/web/src/api/request.ts +104 -0
  74. package/dist/templates/frontend/vue/apps/web/src/api/services/api.ts +15 -0
  75. package/dist/templates/frontend/vue/apps/web/src/api/services/bff.ts +11 -0
  76. package/dist/templates/frontend/vue/apps/web/src/env.d.ts +3 -0
  77. package/dist/templates/frontend/vue/apps/web/src/main.ts +4 -0
  78. package/dist/templates/frontend/vue/apps/web/tsconfig.json +11 -0
  79. package/dist/templates/frontend/vue/apps/web/vite.config.ts +22 -0
  80. package/dist/templates/modules/auth-center/apps/auth-center/.env.example +3 -0
  81. package/dist/templates/modules/auth-center/apps/auth-center/README.md +5 -0
  82. package/dist/templates/modules/auth-center/apps/auth-center/package.json +14 -0
  83. package/dist/templates/modules/auth-center/apps/auth-center/server.js +21 -0
  84. package/dist/templates/modules/cache-redis/infra/cache/README.md +5 -0
  85. package/dist/templates/modules/cache-redis/infra/cache/policies.md +5 -0
  86. package/dist/templates/modules/gateway-bff/apps/gateway-bff/README.md +5 -0
  87. package/dist/templates/modules/gateway-bff/apps/gateway-bff/package.json +13 -0
  88. package/dist/templates/modules/gateway-bff/apps/gateway-bff/server.js +17 -0
  89. package/dist/templates/modules/grpc-service/apps/grpc-service/README.md +5 -0
  90. package/dist/templates/modules/grpc-service/contracts/proto/greeter.proto +17 -0
  91. package/dist/templates/modules/grpc-service/infra/scripts/gen-proto.sh +9 -0
  92. package/dist/templates/modules/mq/infra/mq/README.md +5 -0
  93. package/dist/templates/modules/mq/infra/mq/kafka-topics.md +5 -0
  94. package/dist/templates/modules/mq/infra/mq/nats-subjects.md +4 -0
  95. package/dist/templates/modules/mq/infra/mq/rabbitmq-exchanges.md +5 -0
  96. package/dist/templates/modules/observability/infra/observability/README.md +5 -0
  97. package/dist/templates/modules/observability/infra/observability/grafana/README.md +3 -0
  98. package/dist/templates/modules/observability/infra/observability/otel-collector.yaml +15 -0
  99. package/dist/templates/modules/observability/infra/observability/prometheus/prometheus.yml +7 -0
  100. package/dist/templates/modules/python-ai/apps/python-ai/README.md +5 -0
  101. package/dist/templates/modules/python-ai/apps/python-ai/app/main.py +27 -0
  102. package/dist/templates/modules/python-ai/apps/python-ai/requirements.txt +2 -0
  103. package/dist/templates/modules/python-ai/apps/python-ai/scripts/batch_infer.py +6 -0
  104. package/dist/templates/modules/python-worker/apps/worker-python/README.md +5 -0
  105. package/dist/templates/modules/python-worker/apps/worker-python/requirements.txt +1 -0
  106. package/dist/templates/modules/python-worker/apps/worker-python/tasks/sample_task.py +6 -0
  107. package/dist/templates/modules/python-worker/apps/worker-python/tests/test_worker.py +13 -0
  108. package/dist/templates/modules/python-worker/apps/worker-python/worker.py +10 -0
  109. package/dist/templates/modules/worker-go/apps/worker-go/README.md +5 -0
  110. package/dist/templates/modules/worker-go/apps/worker-go/cmd/worker/main.go +16 -0
  111. package/dist/templates/modules/worker-go/apps/worker-go/go.mod +3 -0
  112. package/dist/templates/modules/worker-go/apps/worker-go/internal/tasks/heartbeat.go +7 -0
  113. package/dist/types/config.js +18 -0
  114. package/package.json +52 -0
@@ -0,0 +1,104 @@
1
+ export type ApiPayload<T = unknown> = {
2
+ code: number;
3
+ message: string;
4
+ data: T;
5
+ traceId: string;
6
+ timestamp: string;
7
+ };
8
+
9
+ export class ApiRequestError extends Error {
10
+ status: number;
11
+ code: number | null;
12
+ traceId: string | null;
13
+ retryable: boolean;
14
+
15
+ constructor(message: string, status: number, code: number | null, traceId: string | null, retryable: boolean) {
16
+ super(message);
17
+ this.status = status;
18
+ this.code = code;
19
+ this.traceId = traceId;
20
+ this.retryable = retryable;
21
+ }
22
+ }
23
+
24
+ export type RequestOptions = {
25
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
26
+ body?: unknown;
27
+ headers?: Record<string, string>;
28
+ timeoutMs?: number;
29
+ retries?: number;
30
+ };
31
+
32
+ const DEFAULT_TIMEOUT_MS = 5000;
33
+
34
+ function randomTraceId(): string {
35
+ return `web-${Math.random().toString(16).slice(2, 10)}`;
36
+ }
37
+
38
+ function delay(ms: number): Promise<void> {
39
+ return new Promise((resolve) => setTimeout(resolve, ms));
40
+ }
41
+
42
+ function makeErrorMessage(status: number, payload: unknown): string {
43
+ if (payload && typeof payload === "object" && "message" in payload) {
44
+ const message = (payload as { message?: unknown }).message;
45
+ if (typeof message === "string" && message.trim()) return message;
46
+ }
47
+ return `HTTP ${status}`;
48
+ }
49
+
50
+ export async function requestJson<T = unknown>(url: string, options: RequestOptions = {}): Promise<ApiPayload<T>> {
51
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
52
+ const retries = options.retries ?? 0;
53
+
54
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
55
+ const traceId = randomTraceId();
56
+ const controller = new AbortController();
57
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
58
+
59
+ try {
60
+ const headers: Record<string, string> = {
61
+ "Content-Type": "application/json",
62
+ "X-Trace-Id": traceId,
63
+ ...(options.headers ?? {}),
64
+ };
65
+
66
+ const response = await fetch(url, {
67
+ method: options.method ?? "GET",
68
+ headers,
69
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
70
+ signal: controller.signal,
71
+ });
72
+
73
+ const raw = (await response.json()) as unknown;
74
+ if (!response.ok) {
75
+ const retryable = response.status >= 500;
76
+ const code = typeof raw === "object" && raw && "code" in raw ? Number((raw as { code: unknown }).code) : null;
77
+ const respTraceId = typeof raw === "object" && raw && "traceId" in raw ? String((raw as { traceId: unknown }).traceId) : null;
78
+ const error = new ApiRequestError(makeErrorMessage(response.status, raw), response.status, code, respTraceId, retryable);
79
+ if (retryable && attempt < retries) {
80
+ await delay(200 * (attempt + 1));
81
+ continue;
82
+ }
83
+ throw error;
84
+ }
85
+
86
+ return raw as ApiPayload<T>;
87
+ } catch (error) {
88
+ const retryable = error instanceof ApiRequestError ? error.retryable : true;
89
+ if (attempt < retries && retryable) {
90
+ await delay(200 * (attempt + 1));
91
+ continue;
92
+ }
93
+ if (error instanceof ApiRequestError) throw error;
94
+ if (error instanceof Error && error.name === "AbortError") {
95
+ throw new ApiRequestError("request timeout", 408, null, null, true);
96
+ }
97
+ throw new ApiRequestError(error instanceof Error ? error.message : String(error), 0, null, null, true);
98
+ } finally {
99
+ clearTimeout(timeout);
100
+ }
101
+ }
102
+
103
+ throw new ApiRequestError("request failed", 0, null, null, true);
104
+ }
@@ -0,0 +1,15 @@
1
+ import { requestJson, type ApiPayload } from "../request";
2
+
3
+ const API_BASE = import.meta.env.VITE_API_BASE || "/api";
4
+
5
+ export function getApiBase(): string {
6
+ return API_BASE;
7
+ }
8
+
9
+ export function pingApi(): Promise<ApiPayload<{ message: string }>> {
10
+ return requestJson<{ message: string }>(`${API_BASE}/v1/ping`, { retries: 1, timeoutMs: 5000 });
11
+ }
12
+
13
+ export function healthApi(): Promise<ApiPayload<{ status: string }>> {
14
+ return requestJson<{ status: string }>(`/health`, { retries: 0, timeoutMs: 3000 });
15
+ }
@@ -0,0 +1,11 @@
1
+ import { requestJson, type ApiPayload } from "../request";
2
+
3
+ const BFF_BASE = import.meta.env.VITE_BFF_BASE || "/bff";
4
+
5
+ export function getBffBase(): string {
6
+ return BFF_BASE;
7
+ }
8
+
9
+ export function pingBff(): Promise<ApiPayload<{ message: string }>> {
10
+ return requestJson<{ message: string }>(`${BFF_BASE}/ping`, { retries: 1, timeoutMs: 5000 });
11
+ }
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./App";
4
+
5
+ createRoot(document.getElementById("root")!).render(
6
+ <React.StrictMode>
7
+ <App />
8
+ </React.StrictMode>,
9
+ );
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "types": ["vitest/globals"]
10
+ },
11
+ "include": ["src"]
12
+ }
@@ -0,0 +1,22 @@
1
+ import { defineConfig, loadEnv } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig(({ mode }) => {
5
+ const env = loadEnv(mode, process.cwd(), "");
6
+
7
+ return {
8
+ plugins: [react()],
9
+ server: {
10
+ proxy: {
11
+ "/api": {
12
+ target: env.VITE_DEV_API_TARGET || "http://localhost:8080",
13
+ changeOrigin: true,
14
+ },
15
+ "/bff": {
16
+ target: env.VITE_DEV_BFF_TARGET || "http://localhost:3001",
17
+ changeOrigin: true,
18
+ },
19
+ },
20
+ },
21
+ };
22
+ });
@@ -0,0 +1,4 @@
1
+ VITE_API_BASE=/api
2
+ VITE_BFF_BASE=/bff
3
+ VITE_DEV_API_TARGET=http://localhost:8080
4
+ VITE_DEV_BFF_TARGET=http://localhost:3001
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{PROJECT_NAME}} web</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}-web",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "test": "vitest"
10
+ },
11
+ "dependencies": {
12
+ "vue": "^3.5.10"
13
+ },
14
+ "devDependencies": {
15
+ "@vitejs/plugin-vue": "^5.1.4",
16
+ "typescript": "^5.9.2",
17
+ "vite": "^5.4.8",
18
+ "vitest": "^2.1.3"
19
+ }
20
+ }
@@ -0,0 +1,7 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ describe("app", () => {
4
+ it("basic assertion", () => {
5
+ expect(true).toBe(true);
6
+ });
7
+ });
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import { ref } from "vue";
3
+ import {
4
+ availableServices,
5
+ getApiBase,
6
+ getBffBase,
7
+ hasBffEnabled,
8
+ pingApi,
9
+ pingBff,
10
+ type ApiPayload,
11
+ } from "./api/client";
12
+
13
+ const HAS_BFF = hasBffEnabled();
14
+
15
+ const apiResult = ref<ApiPayload | null>(null);
16
+ const bffResult = ref<ApiPayload | null>(null);
17
+ const apiError = ref("");
18
+ const bffError = ref("");
19
+
20
+ async function callApi() {
21
+ apiError.value = "";
22
+ try {
23
+ apiResult.value = await pingApi();
24
+ } catch (error) {
25
+ apiError.value = error instanceof Error ? error.message : String(error);
26
+ }
27
+ }
28
+
29
+ async function callBff() {
30
+ if (!HAS_BFF) return;
31
+ bffError.value = "";
32
+ try {
33
+ bffResult.value = await pingBff();
34
+ } catch (error) {
35
+ bffError.value = error instanceof Error ? error.message : String(error);
36
+ }
37
+ }
38
+ </script>
39
+
40
+ <template>
41
+ <main style="font-family: sans-serif; padding: 24px; max-width: 900px;">
42
+ <h1>{{PROJECT_NAME}} - Vue</h1>
43
+ <p>API base: <code>{{ getApiBase() }}</code> | BFF base: <code>{{ getBffBase() }}</code></p>
44
+ <p>Available services: <code>{{ availableServices.join(", ") }}</code></p>
45
+
46
+ <section style="margin-bottom: 20px;">
47
+ <button @click="callApi">Call API /api/v1/ping</button>
48
+ <p v-if="apiError" style="color: crimson;">{{ apiError }}</p>
49
+ <pre v-if="apiResult">{{ JSON.stringify(apiResult, null, 2) }}</pre>
50
+ </section>
51
+
52
+ <section>
53
+ <button @click="callBff" :disabled="!HAS_BFF">Call BFF /bff/ping</button>
54
+ <p v-if="!HAS_BFF">BFF module not selected. Enable <code>gateway-bff</code> to use this call.</p>
55
+ <p v-if="bffError" style="color: crimson;">{{ bffError }}</p>
56
+ <pre v-if="bffResult">{{ JSON.stringify(bffResult, null, 2) }}</pre>
57
+ </section>
58
+ </main>
59
+ </template>
@@ -0,0 +1,20 @@
1
+ import { type ApiPayload } from "./request";
2
+ import { getApiBase, healthApi, pingApi } from "./services/api";
3
+ import { getBffBase, pingBff as pingBffService } from "./services/bff";
4
+
5
+ const HAS_BFF = "{{HAS_GATEWAY_BFF}}" === "true";
6
+
7
+ export { type ApiPayload, getApiBase, healthApi, pingApi, getBffBase };
8
+
9
+ export const availableServices = HAS_BFF ? ["api", "bff"] as const : ["api"] as const;
10
+
11
+ export async function pingBff(): Promise<ApiPayload<{ message: string }>> {
12
+ if (!HAS_BFF) {
13
+ throw new Error("gateway-bff module not enabled");
14
+ }
15
+ return pingBffService();
16
+ }
17
+
18
+ export function hasBffEnabled(): boolean {
19
+ return HAS_BFF;
20
+ }
@@ -0,0 +1,104 @@
1
+ export type ApiPayload<T = unknown> = {
2
+ code: number;
3
+ message: string;
4
+ data: T;
5
+ traceId: string;
6
+ timestamp: string;
7
+ };
8
+
9
+ export class ApiRequestError extends Error {
10
+ status: number;
11
+ code: number | null;
12
+ traceId: string | null;
13
+ retryable: boolean;
14
+
15
+ constructor(message: string, status: number, code: number | null, traceId: string | null, retryable: boolean) {
16
+ super(message);
17
+ this.status = status;
18
+ this.code = code;
19
+ this.traceId = traceId;
20
+ this.retryable = retryable;
21
+ }
22
+ }
23
+
24
+ export type RequestOptions = {
25
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
26
+ body?: unknown;
27
+ headers?: Record<string, string>;
28
+ timeoutMs?: number;
29
+ retries?: number;
30
+ };
31
+
32
+ const DEFAULT_TIMEOUT_MS = 5000;
33
+
34
+ function randomTraceId(): string {
35
+ return `web-${Math.random().toString(16).slice(2, 10)}`;
36
+ }
37
+
38
+ function delay(ms: number): Promise<void> {
39
+ return new Promise((resolve) => setTimeout(resolve, ms));
40
+ }
41
+
42
+ function makeErrorMessage(status: number, payload: unknown): string {
43
+ if (payload && typeof payload === "object" && "message" in payload) {
44
+ const message = (payload as { message?: unknown }).message;
45
+ if (typeof message === "string" && message.trim()) return message;
46
+ }
47
+ return `HTTP ${status}`;
48
+ }
49
+
50
+ export async function requestJson<T = unknown>(url: string, options: RequestOptions = {}): Promise<ApiPayload<T>> {
51
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
52
+ const retries = options.retries ?? 0;
53
+
54
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
55
+ const traceId = randomTraceId();
56
+ const controller = new AbortController();
57
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
58
+
59
+ try {
60
+ const headers: Record<string, string> = {
61
+ "Content-Type": "application/json",
62
+ "X-Trace-Id": traceId,
63
+ ...(options.headers ?? {}),
64
+ };
65
+
66
+ const response = await fetch(url, {
67
+ method: options.method ?? "GET",
68
+ headers,
69
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
70
+ signal: controller.signal,
71
+ });
72
+
73
+ const raw = (await response.json()) as unknown;
74
+ if (!response.ok) {
75
+ const retryable = response.status >= 500;
76
+ const code = typeof raw === "object" && raw && "code" in raw ? Number((raw as { code: unknown }).code) : null;
77
+ const respTraceId = typeof raw === "object" && raw && "traceId" in raw ? String((raw as { traceId: unknown }).traceId) : null;
78
+ const error = new ApiRequestError(makeErrorMessage(response.status, raw), response.status, code, respTraceId, retryable);
79
+ if (retryable && attempt < retries) {
80
+ await delay(200 * (attempt + 1));
81
+ continue;
82
+ }
83
+ throw error;
84
+ }
85
+
86
+ return raw as ApiPayload<T>;
87
+ } catch (error) {
88
+ const retryable = error instanceof ApiRequestError ? error.retryable : true;
89
+ if (attempt < retries && retryable) {
90
+ await delay(200 * (attempt + 1));
91
+ continue;
92
+ }
93
+ if (error instanceof ApiRequestError) throw error;
94
+ if (error instanceof Error && error.name === "AbortError") {
95
+ throw new ApiRequestError("request timeout", 408, null, null, true);
96
+ }
97
+ throw new ApiRequestError(error instanceof Error ? error.message : String(error), 0, null, null, true);
98
+ } finally {
99
+ clearTimeout(timeout);
100
+ }
101
+ }
102
+
103
+ throw new ApiRequestError("request failed", 0, null, null, true);
104
+ }
@@ -0,0 +1,15 @@
1
+ import { requestJson, type ApiPayload } from "../request";
2
+
3
+ const API_BASE = import.meta.env.VITE_API_BASE || "/api";
4
+
5
+ export function getApiBase(): string {
6
+ return API_BASE;
7
+ }
8
+
9
+ export function pingApi(): Promise<ApiPayload<{ message: string }>> {
10
+ return requestJson<{ message: string }>(`${API_BASE}/v1/ping`, { retries: 1, timeoutMs: 5000 });
11
+ }
12
+
13
+ export function healthApi(): Promise<ApiPayload<{ status: string }>> {
14
+ return requestJson<{ status: string }>(`/health`, { retries: 0, timeoutMs: 3000 });
15
+ }
@@ -0,0 +1,11 @@
1
+ import { requestJson, type ApiPayload } from "../request";
2
+
3
+ const BFF_BASE = import.meta.env.VITE_BFF_BASE || "/bff";
4
+
5
+ export function getBffBase(): string {
6
+ return BFF_BASE;
7
+ }
8
+
9
+ export function pingBff(): Promise<ApiPayload<{ message: string }>> {
10
+ return requestJson<{ message: string }>(`${BFF_BASE}/ping`, { retries: 1, timeoutMs: 5000 });
11
+ }
@@ -0,0 +1,3 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module "*.vue";
@@ -0,0 +1,4 @@
1
+ import { createApp } from "vue";
2
+ import App from "./App.vue";
3
+
4
+ createApp(App).mount("#app");
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "types": ["vitest/globals"]
9
+ },
10
+ "include": ["src", "src/**/*.vue", "src/env.d.ts"]
11
+ }
@@ -0,0 +1,22 @@
1
+ import { defineConfig, loadEnv } from "vite";
2
+ import vue from "@vitejs/plugin-vue";
3
+
4
+ export default defineConfig(({ mode }) => {
5
+ const env = loadEnv(mode, process.cwd(), "");
6
+
7
+ return {
8
+ plugins: [vue()],
9
+ server: {
10
+ proxy: {
11
+ "/api": {
12
+ target: env.VITE_DEV_API_TARGET || "http://localhost:8080",
13
+ changeOrigin: true,
14
+ },
15
+ "/bff": {
16
+ target: env.VITE_DEV_BFF_TARGET || "http://localhost:3001",
17
+ changeOrigin: true,
18
+ },
19
+ },
20
+ },
21
+ };
22
+ });
@@ -0,0 +1,3 @@
1
+ JWT_SECRET=please-change-in-production
2
+ OAUTH_CLIENT_ID=example-client
3
+ OAUTH_CLIENT_SECRET=example-secret
@@ -0,0 +1,5 @@
1
+ # Auth Center Module
2
+
3
+ - Purpose: centralize login/token/authorization
4
+ - Includes JWT login placeholder endpoint
5
+ - Add OAuth2 client configs before production
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}-auth-center",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "scripts": {
6
+ "dev": "node server.js",
7
+ "build": "node -e \"console.log('auth-center build placeholder')\"",
8
+ "test": "node -e \"console.log('auth-center test placeholder')\""
9
+ },
10
+ "dependencies": {
11
+ "express": "^4.21.2",
12
+ "jsonwebtoken": "^9.0.2"
13
+ }
14
+ }
@@ -0,0 +1,21 @@
1
+ const express = require("express");
2
+ const jwt = require("jsonwebtoken");
3
+
4
+ const app = express();
5
+ app.use(express.json());
6
+
7
+ const secret = process.env.JWT_SECRET || "change-me";
8
+
9
+ app.post("/auth/login", (req, res) => {
10
+ const username = req.body?.username || "demo";
11
+ const token = jwt.sign({ sub: username, role: "user" }, secret, { expiresIn: "1h" });
12
+ res.json({ code: 0, message: "success", data: { accessToken: token }, traceId: req.header("X-Trace-Id") || "auth-trace", timestamp: new Date().toISOString() });
13
+ });
14
+
15
+ app.get("/health", (req, res) => {
16
+ res.json({ code: 0, message: "success", data: { status: "ok" }, traceId: req.header("X-Trace-Id") || "auth-trace", timestamp: new Date().toISOString() });
17
+ });
18
+
19
+ app.listen(8081, () => {
20
+ console.log("auth-center listening on :8081");
21
+ });
@@ -0,0 +1,5 @@
1
+ # Cache Redis Module
2
+
3
+ - Purpose: shared caching conventions across services
4
+ - Key naming sample: `app:{domain}:{entity}:{id}`
5
+ - TTL policy placeholder and hot-key protection notes
@@ -0,0 +1,5 @@
1
+ # Cache Policies
2
+
3
+ - default TTL: 5m
4
+ - user session TTL: 30m
5
+ - protect hot keys with local fallback and jittered TTL
@@ -0,0 +1,5 @@
1
+ # Gateway BFF
2
+
3
+ - Purpose: API aggregation, auth pass-through, rate limit placeholder
4
+ - Default endpoint: `/bff/ping`
5
+ - Integrates between frontend and backend main API
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}-gateway-bff",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "scripts": {
6
+ "dev": "node server.js",
7
+ "build": "node -e \"console.log('gateway-bff build placeholder')\"",
8
+ "test": "node -e \"console.log('gateway-bff test placeholder')\""
9
+ },
10
+ "dependencies": {
11
+ "express": "^4.21.2"
12
+ }
13
+ }
@@ -0,0 +1,17 @@
1
+ const express = require("express");
2
+
3
+ const app = express();
4
+ app.use(express.json());
5
+
6
+ app.get("/health", (_req, res) => {
7
+ res.json({ code: 0, message: "success", data: { status: "ok" }, traceId: "bff-trace", timestamp: new Date().toISOString() });
8
+ });
9
+
10
+ app.get("/bff/ping", (req, res) => {
11
+ const traceId = req.header("X-Trace-Id") || "bff-trace";
12
+ res.json({ code: 0, message: "success", data: { message: "bff pong" }, traceId, timestamp: new Date().toISOString() });
13
+ });
14
+
15
+ app.listen(3001, () => {
16
+ console.log("gateway-bff listening on :3001");
17
+ });
@@ -0,0 +1,5 @@
1
+ # gRPC Service Module
2
+
3
+ - Purpose: stable multi-language internal contracts
4
+ - Includes proto sample and generation script
5
+ - Requires: protoc + language plugins
@@ -0,0 +1,17 @@
1
+ syntax = "proto3";
2
+
3
+ package scaffold.greeter.v1;
4
+
5
+ option go_package = "{{PROJECT_NAME}}/contracts/proto;greeter";
6
+
7
+ service GreeterService {
8
+ rpc SayHello(HelloRequest) returns (HelloReply);
9
+ }
10
+
11
+ message HelloRequest {
12
+ string name = 1;
13
+ }
14
+
15
+ message HelloReply {
16
+ string message = 1;
17
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # placeholder generation command; adjust plugins/language as needed
5
+ protoc \
6
+ --proto_path=contracts/proto \
7
+ --go_out=apps/grpc-service \
8
+ --go-grpc_out=apps/grpc-service \
9
+ contracts/proto/greeter.proto
@@ -0,0 +1,5 @@
1
+ # MQ Module
2
+
3
+ - Purpose: async collaboration between Go/Java/Python services
4
+ - Includes placeholders for Kafka, RabbitMQ, NATS
5
+ - Default capabilities: producer/consumer samples, retry + idempotency + DLQ placeholders
@@ -0,0 +1,5 @@
1
+ # Kafka Topics Placeholder
2
+
3
+ - `app.task.created`
4
+ - `app.task.retry`
5
+ - `app.task.dlq`
@@ -0,0 +1,4 @@
1
+ # NATS Subjects Placeholder
2
+
3
+ - `app.task.created`
4
+ - `app.task.completed`
@@ -0,0 +1,5 @@
1
+ # RabbitMQ Placeholder
2
+
3
+ - exchange: `app.events`
4
+ - queue: `app.task.worker`
5
+ - dead-letter queue: `app.task.dlq`