playcademy 0.13.19 → 0.13.20
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/dist/constants.d.ts +28 -1
- package/dist/constants.js +18 -0
- package/dist/db.js +5 -2
- package/dist/edge-play/src/entry/middleware.ts +74 -0
- package/dist/edge-play/src/entry/setup.ts +52 -0
- package/dist/edge-play/src/entry/types.ts +30 -0
- package/dist/edge-play/src/entry.ts +20 -71
- package/dist/edge-play/src/routes/health.ts +56 -27
- package/dist/edge-play/src/routes/index.ts +43 -15
- package/dist/edge-play/src/types.ts +8 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +724 -227
- package/dist/templates/gitignore.template +3 -0
- package/dist/templates/playcademy-env.d.ts.template +1 -2
- package/dist/utils.d.ts +13 -13
- package/dist/utils.js +1069 -395
- package/package.json +2 -2
package/dist/constants.d.ts
CHANGED
|
@@ -9,6 +9,33 @@ export { GAME_WORKER_DOMAINS, PLAYCADEMY_BASE_URLS, PLAYCADEMY_DOMAINS } from '@
|
|
|
9
9
|
*/
|
|
10
10
|
declare const DEFAULT_API_ROUTES_DIRECTORY = "server/api";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Configuration file constants
|
|
14
|
+
*
|
|
15
|
+
* Constants for user configuration files (tsconfig, .env, playcademy.config.js, etc.)
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Environment files loaded in order (later files override earlier ones)
|
|
19
|
+
*
|
|
20
|
+
* Load order:
|
|
21
|
+
* 1. .env ← Loaded first (base, can be committed)
|
|
22
|
+
* 2. .env.development ← Overrides .env
|
|
23
|
+
* 3. .env.local ← Overrides all (gitignored, personal secrets)
|
|
24
|
+
*
|
|
25
|
+
* Example: If KEY exists in both .env and .env.local, .env.local wins.
|
|
26
|
+
*
|
|
27
|
+
* This matches the pattern used by Vite, Bun, and other modern tools.
|
|
28
|
+
*/
|
|
29
|
+
declare const ENV_FILES: readonly [".env", ".env.development", ".env.local"];
|
|
30
|
+
/**
|
|
31
|
+
* TypeScript config files to check (in priority order)
|
|
32
|
+
*
|
|
33
|
+
* Priority:
|
|
34
|
+
* 1. tsconfig.app.json ← Modern tooling (Vite, etc.) - try first
|
|
35
|
+
* 2. tsconfig.json ← Standard TypeScript config - fallback
|
|
36
|
+
*/
|
|
37
|
+
declare const TSCONFIG_FILES: readonly ["tsconfig.app.json", "tsconfig.json"];
|
|
38
|
+
|
|
12
39
|
/**
|
|
13
40
|
* Database-related constants
|
|
14
41
|
*/
|
|
@@ -99,4 +126,4 @@ declare const CONFIG_FILE_NAMES: string[];
|
|
|
99
126
|
*/
|
|
100
127
|
declare const CLOUDFLARE_COMPATIBILITY_DATE = "2024-01-01";
|
|
101
128
|
|
|
102
|
-
export { CALLBACK_PATH, CALLBACK_PORT, CLI_DEFAULT_OUTPUTS, CLI_DIRECTORIES, CLI_FILES, CLI_USER_DIRECTORIES, CLOUDFLARE_COMPATIBILITY_DATE, CONFIG_FILE_NAMES, DEFAULT_API_ROUTES_DIRECTORY, DEFAULT_DATABASE_DIRECTORY, DEFAULT_PORTS, DEFAULT_SCHEMA_DIRECTORY, DEFAULT_SEED_FILE, SSO_AUTH_TIMEOUT_MS, WORKSPACE_NAME };
|
|
129
|
+
export { CALLBACK_PATH, CALLBACK_PORT, CLI_DEFAULT_OUTPUTS, CLI_DIRECTORIES, CLI_FILES, CLI_USER_DIRECTORIES, CLOUDFLARE_COMPATIBILITY_DATE, CONFIG_FILE_NAMES, DEFAULT_API_ROUTES_DIRECTORY, DEFAULT_DATABASE_DIRECTORY, DEFAULT_PORTS, DEFAULT_SCHEMA_DIRECTORY, DEFAULT_SEED_FILE, ENV_FILES, SSO_AUTH_TIMEOUT_MS, TSCONFIG_FILES, WORKSPACE_NAME };
|
package/dist/constants.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
// src/constants/api.ts
|
|
2
2
|
var DEFAULT_API_ROUTES_DIRECTORY = "server/api";
|
|
3
3
|
|
|
4
|
+
// src/constants/config.ts
|
|
5
|
+
var ENV_FILES = [
|
|
6
|
+
".env",
|
|
7
|
+
// Loaded first
|
|
8
|
+
".env.development",
|
|
9
|
+
// Overrides .env
|
|
10
|
+
".env.local"
|
|
11
|
+
// Overrides all (highest priority)
|
|
12
|
+
];
|
|
13
|
+
var TSCONFIG_FILES = [
|
|
14
|
+
"tsconfig.app.json",
|
|
15
|
+
// Modern tooling (try first)
|
|
16
|
+
"tsconfig.json"
|
|
17
|
+
// Standard (fallback)
|
|
18
|
+
];
|
|
19
|
+
|
|
4
20
|
// src/constants/database.ts
|
|
5
21
|
var DEFAULT_DATABASE_DIRECTORY = "db";
|
|
6
22
|
var DEFAULT_SCHEMA_DIRECTORY = "db/schema";
|
|
@@ -121,9 +137,11 @@ export {
|
|
|
121
137
|
DEFAULT_PORTS,
|
|
122
138
|
DEFAULT_SCHEMA_DIRECTORY,
|
|
123
139
|
DEFAULT_SEED_FILE,
|
|
140
|
+
ENV_FILES,
|
|
124
141
|
GAME_WORKER_DOMAINS,
|
|
125
142
|
PLAYCADEMY_BASE_URLS,
|
|
126
143
|
PLAYCADEMY_DOMAINS,
|
|
127
144
|
SSO_AUTH_TIMEOUT_MS,
|
|
145
|
+
TSCONFIG_FILES,
|
|
128
146
|
WORKSPACE_NAME
|
|
129
147
|
};
|
package/dist/db.js
CHANGED
|
@@ -412,6 +412,7 @@ function getPackageManager() {
|
|
|
412
412
|
// src/lib/core/logger.ts
|
|
413
413
|
import {
|
|
414
414
|
blue,
|
|
415
|
+
blueBright,
|
|
415
416
|
bold as bold2,
|
|
416
417
|
cyan,
|
|
417
418
|
dim as dim2,
|
|
@@ -424,8 +425,10 @@ import {
|
|
|
424
425
|
} from "colorette";
|
|
425
426
|
import { colorize } from "json-colorizer";
|
|
426
427
|
function customTransform(text) {
|
|
427
|
-
|
|
428
|
-
|
|
428
|
+
let result = text;
|
|
429
|
+
result = result.replace(/`([^`]+)`/g, (_, code) => greenBright(code));
|
|
430
|
+
result = result.replace(/<([^>]+)>/g, (_, path) => blueBright(path));
|
|
431
|
+
return result;
|
|
429
432
|
}
|
|
430
433
|
function formatTable(data, title) {
|
|
431
434
|
if (data.length === 0) return;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware Configuration
|
|
3
|
+
*
|
|
4
|
+
* All middleware for the game backend worker
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { cors } from 'hono/cors'
|
|
8
|
+
|
|
9
|
+
import { PlaycademyClient } from '@playcademy/sdk/server'
|
|
10
|
+
|
|
11
|
+
import { populateProcessEnv, reconstructSecrets } from './setup'
|
|
12
|
+
|
|
13
|
+
import type { Hono } from 'hono'
|
|
14
|
+
import type { HonoEnv } from '../types'
|
|
15
|
+
import type { RuntimeConfig } from './types'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register CORS middleware
|
|
19
|
+
*
|
|
20
|
+
* TODO: Harden CORS in production - restrict to trusted origins:
|
|
21
|
+
* - Game's assetBundleBase (for hosted games)
|
|
22
|
+
* - Game's externalUrl (for external games)
|
|
23
|
+
* - Platform frontend domains (hub.playcademy.com, hub.dev.playcademy.net)
|
|
24
|
+
* This would require passing game metadata through env bindings during deployment
|
|
25
|
+
*/
|
|
26
|
+
export function registerCors(app: Hono<HonoEnv>): void {
|
|
27
|
+
app.use(
|
|
28
|
+
'*',
|
|
29
|
+
cors({
|
|
30
|
+
origin: '*', // Permissive for now
|
|
31
|
+
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
32
|
+
allowHeaders: ['Content-Type', 'Authorization'],
|
|
33
|
+
}),
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Register environment setup middleware
|
|
39
|
+
*
|
|
40
|
+
* Populates process.env, reconstructs secrets, and sets config
|
|
41
|
+
*/
|
|
42
|
+
export function registerEnvSetup(app: Hono<HonoEnv>, config: RuntimeConfig): void {
|
|
43
|
+
app.use('*', async (c, next) => {
|
|
44
|
+
populateProcessEnv(c.env)
|
|
45
|
+
c.env.secrets = reconstructSecrets(c.env)
|
|
46
|
+
c.set('config', config)
|
|
47
|
+
c.set('routeMetadata', config.__routeMetadata || [])
|
|
48
|
+
await next()
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Register SDK initialization middleware
|
|
54
|
+
*
|
|
55
|
+
* Lazily initializes the Playcademy SDK on first request and
|
|
56
|
+
* makes it available via c.get('sdk')
|
|
57
|
+
*/
|
|
58
|
+
export function registerSdkInit(app: Hono<HonoEnv>, config: RuntimeConfig): void {
|
|
59
|
+
let sdkPromise: Promise<PlaycademyClient> | null = null
|
|
60
|
+
|
|
61
|
+
app.use('*', async (c, next) => {
|
|
62
|
+
if (!sdkPromise) {
|
|
63
|
+
sdkPromise = PlaycademyClient.init({
|
|
64
|
+
apiKey: c.env.PLAYCADEMY_API_KEY,
|
|
65
|
+
gameId: c.env.GAME_ID,
|
|
66
|
+
baseUrl: c.env.PLAYCADEMY_BASE_URL,
|
|
67
|
+
config,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
c.set('sdk', await sdkPromise)
|
|
72
|
+
await next()
|
|
73
|
+
})
|
|
74
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Setup Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for preparing the Worker environment
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ENV_VARS } from '../constants'
|
|
8
|
+
|
|
9
|
+
import type { ServerEnv } from '../types'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Populate process.env from Worker bindings for SDK compatibility
|
|
13
|
+
*/
|
|
14
|
+
export function populateProcessEnv(env: ServerEnv): void {
|
|
15
|
+
globalThis.process.env = {
|
|
16
|
+
[ENV_VARS.PLAYCADEMY_API_KEY]: env.PLAYCADEMY_API_KEY,
|
|
17
|
+
[ENV_VARS.GAME_ID]: env.GAME_ID,
|
|
18
|
+
[ENV_VARS.PLAYCADEMY_BASE_URL]: env.PLAYCADEMY_BASE_URL,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Reconstruct secrets object from flat Cloudflare bindings
|
|
24
|
+
*
|
|
25
|
+
* Cloudflare bindings are flat (secrets_KEY_NAME), but we expose them
|
|
26
|
+
* as c.env.secrets.KEY_NAME for better developer ergonomics.
|
|
27
|
+
*/
|
|
28
|
+
export function reconstructSecrets(env: Record<string, unknown>): Record<string, string> {
|
|
29
|
+
const secrets: Record<string, string> = {}
|
|
30
|
+
|
|
31
|
+
for (const key in env) {
|
|
32
|
+
if (key.startsWith('secrets_')) {
|
|
33
|
+
const secretKey = key.slice(8) // Remove 'secrets_' prefix
|
|
34
|
+
secrets[secretKey] = env[key] as string
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return secrets
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Setup process global polyfill for SDK compatibility
|
|
43
|
+
*
|
|
44
|
+
* SDK code may reference process.env without importing it
|
|
45
|
+
*/
|
|
46
|
+
export function setupProcessGlobal(): void {
|
|
47
|
+
// @ts-expect-error - Adding global for Worker environment
|
|
48
|
+
globalThis.process = {
|
|
49
|
+
env: {}, // Populated per-request from Worker env bindings
|
|
50
|
+
cwd: () => '/',
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry-specific types
|
|
3
|
+
*
|
|
4
|
+
* Types used only within the entry point bundling and runtime
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PlaycademyConfig } from '@playcademy/sdk/server'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Route metadata injected at build time by CLI
|
|
11
|
+
*/
|
|
12
|
+
export interface RouteMetadata {
|
|
13
|
+
/** Route path (e.g., '/hello') */
|
|
14
|
+
path: string
|
|
15
|
+
/** Source file path relative to api directory (e.g., 'hello.ts') */
|
|
16
|
+
file: string
|
|
17
|
+
/** HTTP methods supported by this route */
|
|
18
|
+
methods?: string[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Runtime configuration with CLI-injected metadata
|
|
23
|
+
*
|
|
24
|
+
* This is the config available at runtime after esbuild's `define` injection.
|
|
25
|
+
* It includes both the user's playcademy.config.js and build-time metadata.
|
|
26
|
+
*/
|
|
27
|
+
export interface RuntimeConfig extends PlaycademyConfig {
|
|
28
|
+
/** Route metadata injected by CLI during bundling */
|
|
29
|
+
__routeMetadata?: RouteMetadata[]
|
|
30
|
+
}
|
|
@@ -9,14 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { Hono } from 'hono'
|
|
12
|
-
import { cors } from 'hono/cors'
|
|
13
12
|
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
import { ENV_VARS } from './constants'
|
|
13
|
+
import { registerCors, registerEnvSetup, registerSdkInit } from './entry/middleware'
|
|
14
|
+
import { setupProcessGlobal } from './entry/setup'
|
|
17
15
|
import { registerBuiltinRoutes } from './register-routes'
|
|
18
16
|
|
|
19
|
-
import type {
|
|
17
|
+
import type { RuntimeConfig } from './entry/types'
|
|
20
18
|
import type { HonoEnv } from './types'
|
|
21
19
|
|
|
22
20
|
/**
|
|
@@ -33,76 +31,27 @@ import type { HonoEnv } from './types'
|
|
|
33
31
|
* This enables tree-shaking: if timeback is not configured, those code paths are removed.
|
|
34
32
|
* The bundled Worker only includes the routes that are actually enabled.
|
|
35
33
|
*/
|
|
36
|
-
declare const PLAYCADEMY_CONFIG:
|
|
37
|
-
customRoutes?: Array<{ path: string; file: string }>
|
|
38
|
-
}
|
|
34
|
+
declare const PLAYCADEMY_CONFIG: RuntimeConfig
|
|
39
35
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
// @ts-expect-error - Adding global for Worker environment
|
|
43
|
-
globalThis.process = {
|
|
44
|
-
env: {}, // Populated per-request from Worker env bindings
|
|
45
|
-
cwd: () => '/',
|
|
46
|
-
}
|
|
36
|
+
// Setup process global polyfill for SDK compatibility
|
|
37
|
+
setupProcessGlobal()
|
|
47
38
|
|
|
39
|
+
// Create Hono app
|
|
48
40
|
const app = new Hono<HonoEnv>()
|
|
49
41
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
let sdkPromise: Promise<PlaycademyClient> | null = null
|
|
65
|
-
|
|
66
|
-
app.use('*', async (c, next) => {
|
|
67
|
-
// Populate process.env from Worker bindings for SDK compatibility
|
|
68
|
-
globalThis.process.env = {
|
|
69
|
-
[ENV_VARS.PLAYCADEMY_API_KEY]: c.env.PLAYCADEMY_API_KEY,
|
|
70
|
-
[ENV_VARS.GAME_ID]: c.env.GAME_ID,
|
|
71
|
-
[ENV_VARS.PLAYCADEMY_BASE_URL]: c.env.PLAYCADEMY_BASE_URL,
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Set config for all routes
|
|
75
|
-
c.set('config', PLAYCADEMY_CONFIG)
|
|
76
|
-
c.set('customRoutes', PLAYCADEMY_CONFIG.customRoutes || [])
|
|
77
|
-
|
|
78
|
-
await next()
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
// Initialize SDK lazily on first request
|
|
82
|
-
app.use('*', async (c, next) => {
|
|
83
|
-
if (!sdkPromise) {
|
|
84
|
-
sdkPromise = PlaycademyClient.init({
|
|
85
|
-
apiKey: c.env[ENV_VARS.PLAYCADEMY_API_KEY],
|
|
86
|
-
gameId: c.env[ENV_VARS.GAME_ID],
|
|
87
|
-
baseUrl: c.env[ENV_VARS.PLAYCADEMY_BASE_URL],
|
|
88
|
-
config: PLAYCADEMY_CONFIG,
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
c.set('sdk', await sdkPromise)
|
|
93
|
-
await next()
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Register built-in integration routes based on enabled integrations
|
|
98
|
-
*
|
|
99
|
-
* This function conditionally imports and registers routes like:
|
|
100
|
-
* - POST /api/integrations/timeback/end-activity (if timeback enabled)
|
|
101
|
-
* - GET /api/health (always included)
|
|
102
|
-
*
|
|
103
|
-
* Uses dynamic imports for tree-shaking: if an integration is not enabled,
|
|
104
|
-
* its route code is completely removed from the bundle.
|
|
105
|
-
*/
|
|
42
|
+
// Register middleware
|
|
43
|
+
registerCors(app)
|
|
44
|
+
registerEnvSetup(app, PLAYCADEMY_CONFIG)
|
|
45
|
+
registerSdkInit(app, PLAYCADEMY_CONFIG)
|
|
46
|
+
|
|
47
|
+
// Register built-in integration routes based on enabled integrations
|
|
48
|
+
// This function conditionally imports and registers routes like:
|
|
49
|
+
// - GET /api (always included)
|
|
50
|
+
// - GET /api/health (always included)
|
|
51
|
+
// - POST /api/integrations/timeback/end-activity (if timeback enabled)
|
|
52
|
+
//
|
|
53
|
+
// Uses dynamic imports for tree-shaking: if an integration is not enabled,
|
|
54
|
+
// its route code is completely removed from the bundle.
|
|
106
55
|
await registerBuiltinRoutes(app, PLAYCADEMY_CONFIG.integrations)
|
|
107
56
|
|
|
108
57
|
export default app
|
|
@@ -5,40 +5,69 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Context } from 'hono'
|
|
8
|
-
import type {
|
|
8
|
+
import type { PlaycademyClient, PlaycademyConfig } from '@playcademy/sdk/server'
|
|
9
|
+
import type { RouteMetadata } from '../entry/types'
|
|
10
|
+
import type { HonoEnv, ServerEnv } from '../types'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if all required environment variables are configured
|
|
14
|
+
*/
|
|
15
|
+
function isEnvConfigured(env: ServerEnv): boolean {
|
|
16
|
+
return !!env.PLAYCADEMY_API_KEY && !!env.GAME_ID && !!env.PLAYCADEMY_BASE_URL
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get count of configured secrets
|
|
21
|
+
*/
|
|
22
|
+
function getSecretsCount(env: ServerEnv): number {
|
|
23
|
+
return env.secrets ? Object.keys(env.secrets).length : 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build list of enabled integration names
|
|
28
|
+
*/
|
|
29
|
+
function getEnabledIntegrations(config?: PlaycademyConfig): string[] {
|
|
30
|
+
const enabled: string[] = []
|
|
31
|
+
if (config?.integrations?.timeback) enabled.push('timeback')
|
|
32
|
+
if (config?.integrations?.customRoutes) enabled.push('customRoutes')
|
|
33
|
+
if (config?.integrations?.database) enabled.push('database')
|
|
34
|
+
if (config?.integrations?.kv) enabled.push('kv')
|
|
35
|
+
if (config?.integrations?.bucket) enabled.push('bucket')
|
|
36
|
+
return enabled
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Format routes into readable objects with separate path and methods
|
|
41
|
+
*/
|
|
42
|
+
function formatRoutes(routes: RouteMetadata[]): Array<{ path: string; methods: string[] }> {
|
|
43
|
+
return routes.map(r => ({
|
|
44
|
+
path: r.path,
|
|
45
|
+
methods: r.methods || ['*'],
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get TimeBack debug info if applicable
|
|
51
|
+
*/
|
|
52
|
+
function getTimebackDebugInfo(config?: PlaycademyConfig, sdk?: PlaycademyClient) {
|
|
53
|
+
if (config?.integrations?.timeback && sdk?.timeback?.courseId) {
|
|
54
|
+
return { timeback: { courseId: sdk.timeback.courseId } }
|
|
55
|
+
}
|
|
56
|
+
return {}
|
|
57
|
+
}
|
|
9
58
|
|
|
10
59
|
export async function GET(c: Context<HonoEnv>): Promise<Response> {
|
|
11
60
|
const config = c.get('config')
|
|
12
61
|
const sdk = c.get('sdk')
|
|
62
|
+
const routeMetadata = c.get('routeMetadata')
|
|
13
63
|
|
|
14
64
|
return c.json({
|
|
15
65
|
status: 'ok',
|
|
16
66
|
timestamp: new Date().toISOString(),
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
hasBaseUrl: !!c.env.PLAYCADEMY_BASE_URL,
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
// Config presence
|
|
26
|
-
config: {
|
|
27
|
-
hasConfig: !!config,
|
|
28
|
-
hasIntegrations: !!config?.integrations,
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
// TimeBack status
|
|
32
|
-
timeback: {
|
|
33
|
-
enabled: !!config?.integrations?.timeback,
|
|
34
|
-
courseIdFetched: !!sdk?.timeback?.courseId,
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
// Custom routes info
|
|
38
|
-
customRoutes:
|
|
39
|
-
c.get('customRoutes')?.map(r => ({
|
|
40
|
-
path: r.path,
|
|
41
|
-
methods: r.methods,
|
|
42
|
-
})) || [],
|
|
67
|
+
env: { configured: isEnvConfigured(c.env) },
|
|
68
|
+
secrets: getSecretsCount(c.env),
|
|
69
|
+
integrations: getEnabledIntegrations(config),
|
|
70
|
+
routes: formatRoutes(routeMetadata),
|
|
71
|
+
...getTimebackDebugInfo(config, sdk),
|
|
43
72
|
})
|
|
44
73
|
}
|
|
@@ -2,31 +2,59 @@
|
|
|
2
2
|
* Route discovery endpoint
|
|
3
3
|
* Route: GET /api
|
|
4
4
|
* Always included - lists all available routes
|
|
5
|
-
*
|
|
6
|
-
* NOTE: This pulls from the actual ROUTES constants used for registration,
|
|
7
|
-
* ensuring the advertised routes match what's actually deployed.
|
|
8
5
|
*/
|
|
9
6
|
|
|
10
7
|
import { ROUTES } from '../constants'
|
|
11
8
|
|
|
12
9
|
import type { Context } from 'hono'
|
|
10
|
+
import type { PlaycademyConfig } from '@playcademy/sdk/server'
|
|
11
|
+
import type { RouteMetadata } from '../entry/types'
|
|
13
12
|
import type { HonoEnv } from '../types'
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
interface RouteInfo {
|
|
15
|
+
path: string
|
|
16
|
+
methods: string[]
|
|
17
|
+
}
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Get built-in routes that are always available
|
|
21
|
+
*/
|
|
22
|
+
function getBuiltinRoutes(): RouteInfo[] {
|
|
23
|
+
return [
|
|
24
|
+
{ path: ROUTES.INDEX, methods: ['GET'] },
|
|
25
|
+
{ path: ROUTES.HEALTH, methods: ['GET'] },
|
|
26
|
+
]
|
|
27
|
+
}
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Get TimeBack integration routes if enabled
|
|
31
|
+
*/
|
|
32
|
+
function getTimebackRoutes(config?: PlaycademyConfig): RouteInfo[] {
|
|
33
|
+
if (config?.integrations?.timeback) {
|
|
34
|
+
return [{ path: ROUTES.TIMEBACK.END_ACTIVITY, methods: ['POST'] }]
|
|
29
35
|
}
|
|
36
|
+
return []
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Format custom routes from build-time metadata
|
|
41
|
+
*/
|
|
42
|
+
function formatCustomRoutes(routes: RouteMetadata[]): RouteInfo[] {
|
|
43
|
+
return routes.map(r => ({
|
|
44
|
+
path: r.path,
|
|
45
|
+
methods: r.methods || ['*'],
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function GET(c: Context<HonoEnv>): Promise<Response> {
|
|
50
|
+
const config = c.get('config')
|
|
51
|
+
const routeMetadata = c.get('routeMetadata')
|
|
52
|
+
|
|
53
|
+
const routes = [
|
|
54
|
+
...getBuiltinRoutes(),
|
|
55
|
+
...getTimebackRoutes(config),
|
|
56
|
+
...formatCustomRoutes(routeMetadata),
|
|
57
|
+
]
|
|
30
58
|
|
|
31
59
|
return c.json({
|
|
32
60
|
name: config.name,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
/// <reference types="@cloudflare/workers-types" />
|
|
11
11
|
|
|
12
12
|
import type { PlaycademyClient, PlaycademyConfig } from '@playcademy/sdk/server'
|
|
13
|
+
import type { RouteMetadata } from './entry/types'
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Enabled integrations from playcademy.config.js
|
|
@@ -35,6 +36,9 @@ export interface ServerEnv {
|
|
|
35
36
|
/** Platform base URL (stage-aware: hub.playcademy.net or hub.dev.playcademy.net) */
|
|
36
37
|
PLAYCADEMY_BASE_URL: string
|
|
37
38
|
|
|
39
|
+
/** Game-specific secrets (optional) */
|
|
40
|
+
secrets?: Record<string, string>
|
|
41
|
+
|
|
38
42
|
/** KV namespace binding (optional, Cloudflare-specific) */
|
|
39
43
|
KV?: KVNamespace
|
|
40
44
|
|
|
@@ -43,6 +47,9 @@ export interface ServerEnv {
|
|
|
43
47
|
|
|
44
48
|
/** R2 bucket binding (optional, Cloudflare-specific) */
|
|
45
49
|
BUCKET?: R2Bucket
|
|
50
|
+
|
|
51
|
+
/** Allow dynamic secret bindings (secrets_KEY_NAME) */
|
|
52
|
+
[key: string]: unknown
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
/**
|
|
@@ -51,7 +58,7 @@ export interface ServerEnv {
|
|
|
51
58
|
export interface HonoVariables {
|
|
52
59
|
sdk: PlaycademyClient
|
|
53
60
|
config: PlaycademyConfig
|
|
54
|
-
|
|
61
|
+
routeMetadata: Array<RouteMetadata>
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
/**
|