playcademy 0.27.1-beta.2 → 0.27.1-beta.4
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/bucket.d.ts +30 -0
- package/dist/bucket.js +29 -61
- package/dist/cli.js +98 -56
- package/dist/constants.d.ts +25 -8
- package/dist/constants.js +21 -11
- package/dist/db.js +11 -6
- package/dist/index.d.ts +49 -16
- package/dist/index.js +4712 -1178
- package/dist/runtime/dashboard-runtime/index.js +4165 -0
- package/dist/runtime/dashboard-runtime/manifest.json +7 -0
- package/dist/runtime/{backend-runtime → game-runtime}/index.js +2539 -2526
- package/dist/runtime/game-runtime/manifest.json +7 -0
- package/dist/templates/dashboard-env.d.ts.template +63 -0
- package/dist/templates/dashboard-sample-route.ts.template +21 -0
- package/dist/utils.d.ts +163 -4
- package/dist/utils.js +3531 -619
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/dist/runtime/backend-runtime/manifest.json +0 -7
package/dist/bucket.d.ts
CHANGED
|
@@ -109,6 +109,34 @@ interface IntegrationsConfig {
|
|
|
109
109
|
/** Queues (optional) */
|
|
110
110
|
queues?: Record<string, QueueConfig | boolean>;
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Developer dashboard configuration.
|
|
114
|
+
*
|
|
115
|
+
* The dashboard is a separate app in the game's repo, deployed as a sibling
|
|
116
|
+
* worker with `playcademy dashboard deploy`. Auth and session plumbing are
|
|
117
|
+
* platform-managed; the dashboard app itself is just a static SPA build.
|
|
118
|
+
*/
|
|
119
|
+
interface DashboardConfig {
|
|
120
|
+
/** Path to the dashboard app, relative to the config file (default: './dashboard') */
|
|
121
|
+
directory?: string;
|
|
122
|
+
/** Build command run inside the dashboard directory (defaults to the workspace package manager's build script) */
|
|
123
|
+
buildCommand?: string[];
|
|
124
|
+
/** Path to the dashboard build output, relative to the dashboard directory (default: 'dist') */
|
|
125
|
+
buildPath?: string;
|
|
126
|
+
/** Colors for the platform-managed login/invite page (hex); unset uses the built-in palette */
|
|
127
|
+
theme?: DashboardThemeConfig;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Login-page theming. Two seeds — a primary (button, focus, background
|
|
131
|
+
* wash) and a secondary accent — the platform derives the full palette
|
|
132
|
+
* from. Either may be omitted to keep that half of the built-in palette.
|
|
133
|
+
*/
|
|
134
|
+
interface DashboardThemeConfig {
|
|
135
|
+
/** Primary color as a hex string, e.g. '#e0245e' */
|
|
136
|
+
primary?: string;
|
|
137
|
+
/** Secondary/accent color as a hex string, e.g. '#ffd166' */
|
|
138
|
+
secondary?: string;
|
|
139
|
+
}
|
|
112
140
|
/**
|
|
113
141
|
* Unified Playcademy configuration
|
|
114
142
|
* Used for playcademy.config.{js,json}
|
|
@@ -130,6 +158,8 @@ interface PlaycademyConfig {
|
|
|
130
158
|
externalUrl?: string;
|
|
131
159
|
/** Game platform */
|
|
132
160
|
platform?: 'web' | 'godot';
|
|
161
|
+
/** Developer dashboard app (optional) — `true` uses defaults */
|
|
162
|
+
dashboard?: DashboardConfig | boolean;
|
|
133
163
|
/** Integrations (database, custom routes, external services) */
|
|
134
164
|
integrations?: IntegrationsConfig;
|
|
135
165
|
}
|
package/dist/bucket.js
CHANGED
|
@@ -22,62 +22,12 @@ var init_file_loader = __esm({
|
|
|
22
22
|
import { existsSync } from "fs";
|
|
23
23
|
import { join as join8 } from "path";
|
|
24
24
|
|
|
25
|
-
// ../
|
|
26
|
-
var TIMEBACK_ROUTES = {
|
|
27
|
-
END_ACTIVITY: "/integrations/timeback/end-activity",
|
|
28
|
-
GET_XP: "/integrations/timeback/xp",
|
|
29
|
-
GET_MASTERY: "/integrations/timeback/mastery",
|
|
30
|
-
GET_HIGHEST_GRADE_MASTERED: "/integrations/timeback/highest-grade-mastered",
|
|
31
|
-
HEARTBEAT: "/integrations/timeback/heartbeat",
|
|
32
|
-
ADVANCE_COURSE: "/integrations/timeback/advance-course",
|
|
33
|
-
UNENROLL_COURSE: "/integrations/timeback/unenroll-course"
|
|
34
|
-
};
|
|
35
|
-
var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
|
|
36
|
-
xp: 1,
|
|
37
|
-
mastery: 0,
|
|
38
|
-
score: 2
|
|
39
|
-
};
|
|
40
|
-
var TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE = {
|
|
41
|
-
xp: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.xp,
|
|
42
|
-
mastery: 0,
|
|
43
|
-
time: 60,
|
|
44
|
-
score: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.score
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// ../constants/src/cloudflare.ts
|
|
48
|
-
var CLOUDFLARE_COMPATIBILITY_DATE = "2025-10-11";
|
|
49
|
-
|
|
50
|
-
// ../edge-play/src/constants.ts
|
|
25
|
+
// ../edge-play/src/lib/constants.ts
|
|
51
26
|
var ASSET_PREFIX = "_assets/";
|
|
52
27
|
var MANIFEST_KEY = "_manifest.json";
|
|
53
28
|
var ASSET_ROUTE_PREFIX = "/api/assets/";
|
|
54
|
-
var ROUTES = {
|
|
55
|
-
/** Route index (lists available routes) */
|
|
56
|
-
INDEX: "/api",
|
|
57
|
-
/** Health check endpoint */
|
|
58
|
-
HEALTH: "/api/health",
|
|
59
|
-
/**
|
|
60
|
-
* Built-in content-addressable asset serving.
|
|
61
|
-
*
|
|
62
|
-
* Serves files from the game's BUCKET (R2) binding under their
|
|
63
|
-
* content-hashed keys, with immutable caching. Registered in the
|
|
64
|
-
* terminal tier so a game's own `server/api/assets/[...path].ts`
|
|
65
|
-
* route supersedes it.
|
|
66
|
-
*/
|
|
67
|
-
ASSETS: `${ASSET_ROUTE_PREFIX}*`,
|
|
68
|
-
/** TimeBack integration routes */
|
|
69
|
-
TIMEBACK: {
|
|
70
|
-
END_ACTIVITY: `/api${TIMEBACK_ROUTES.END_ACTIVITY}`,
|
|
71
|
-
GET_XP: `/api${TIMEBACK_ROUTES.GET_XP}`,
|
|
72
|
-
GET_MASTERY: `/api${TIMEBACK_ROUTES.GET_MASTERY}`,
|
|
73
|
-
GET_HIGHEST_GRADE_MASTERED: `/api${TIMEBACK_ROUTES.GET_HIGHEST_GRADE_MASTERED}`,
|
|
74
|
-
HEARTBEAT: `/api${TIMEBACK_ROUTES.HEARTBEAT}`,
|
|
75
|
-
ADVANCE_COURSE: `/api${TIMEBACK_ROUTES.ADVANCE_COURSE}`,
|
|
76
|
-
UNENROLL_COURSE: `/api${TIMEBACK_ROUTES.UNENROLL_COURSE}`
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
29
|
|
|
80
|
-
// src/lib/bundle/backend.ts
|
|
30
|
+
// src/lib/bundle/game-backend.ts
|
|
81
31
|
import { join as join6 } from "node:path";
|
|
82
32
|
|
|
83
33
|
// src/constants/api.ts
|
|
@@ -85,16 +35,16 @@ import { join as join2 } from "node:path";
|
|
|
85
35
|
|
|
86
36
|
// src/constants/server.ts
|
|
87
37
|
import { join } from "node:path";
|
|
88
|
-
var
|
|
89
|
-
var
|
|
38
|
+
var GAME_SERVER_ROOT_DIRECTORY = "server";
|
|
39
|
+
var GAME_SERVER_LIB_DIRECTORY = join(GAME_SERVER_ROOT_DIRECTORY, "lib");
|
|
90
40
|
|
|
91
41
|
// src/constants/api.ts
|
|
92
|
-
var
|
|
42
|
+
var GAME_API_ROUTES_DIRECTORY = join2(GAME_SERVER_ROOT_DIRECTORY, "api");
|
|
93
43
|
|
|
94
44
|
// ../better-auth/package.json
|
|
95
45
|
var package_default = {
|
|
96
46
|
name: "@playcademy/better-auth",
|
|
97
|
-
version: "0.0.20-beta.
|
|
47
|
+
version: "0.0.20-beta.3",
|
|
98
48
|
type: "module",
|
|
99
49
|
exports: {
|
|
100
50
|
"./server": {
|
|
@@ -200,6 +150,22 @@ var CLI_DEFAULT_OUTPUTS = {
|
|
|
200
150
|
WORKER_BUNDLE: join5(WORKSPACE_NAME, "worker-bundle.js")
|
|
201
151
|
};
|
|
202
152
|
|
|
153
|
+
// ../constants/src/timeback.ts
|
|
154
|
+
var TIMEBACK_GAME_METRIC_DECIMAL_PLACES = {
|
|
155
|
+
xp: 1,
|
|
156
|
+
mastery: 0,
|
|
157
|
+
score: 2
|
|
158
|
+
};
|
|
159
|
+
var TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE = {
|
|
160
|
+
xp: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.xp,
|
|
161
|
+
mastery: 0,
|
|
162
|
+
time: 60,
|
|
163
|
+
score: 0.5 / 10 ** TIMEBACK_GAME_METRIC_DECIMAL_PLACES.score
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// ../constants/src/cloudflare.ts
|
|
167
|
+
var CLOUDFLARE_COMPATIBILITY_DATE = "2025-10-11";
|
|
168
|
+
|
|
203
169
|
// src/lib/core/client.ts
|
|
204
170
|
import { PlaycademyClient } from "@playcademy/sdk/internal";
|
|
205
171
|
|
|
@@ -327,11 +293,13 @@ import {
|
|
|
327
293
|
// src/lib/core/transpile.ts
|
|
328
294
|
import * as esbuild2 from "esbuild";
|
|
329
295
|
|
|
330
|
-
// src/lib/bundle/backend.ts
|
|
331
|
-
function
|
|
296
|
+
// src/lib/bundle/game-backend.ts
|
|
297
|
+
function resolveGameRoutesDir(config) {
|
|
332
298
|
const customRoutes = config?.integrations?.customRoutes;
|
|
333
|
-
|
|
334
|
-
|
|
299
|
+
return typeof customRoutes === "object" && customRoutes.directory || GAME_API_ROUTES_DIRECTORY;
|
|
300
|
+
}
|
|
301
|
+
function getGameRoutesDirectory(projectPath, config) {
|
|
302
|
+
return join6(projectPath, resolveGameRoutesDir(config));
|
|
335
303
|
}
|
|
336
304
|
|
|
337
305
|
// src/lib/bucket/local.ts
|
|
@@ -354,7 +322,7 @@ async function openLocalBucket() {
|
|
|
354
322
|
// src/lib/bucket/dev.ts
|
|
355
323
|
var ASSET_DEV_ROUTE_PREFIX = ASSET_ROUTE_PREFIX;
|
|
356
324
|
function hasCustomAssetsRoute(projectRoot, config) {
|
|
357
|
-
const routesDir =
|
|
325
|
+
const routesDir = getGameRoutesDirectory(projectRoot, config);
|
|
358
326
|
return existsSync(join8(routesDir, "assets")) || existsSync(join8(routesDir, "assets.ts"));
|
|
359
327
|
}
|
|
360
328
|
function openLocalAssetBucket() {
|
package/dist/cli.js
CHANGED
|
@@ -506,11 +506,16 @@ function writeBlank() {
|
|
|
506
506
|
_consoleLog();
|
|
507
507
|
lastWasBlank = true;
|
|
508
508
|
}
|
|
509
|
+
var FG_RESET = "\x1B[39m";
|
|
510
|
+
function nestColor(content, color2) {
|
|
511
|
+
const open = color2("").replace(FG_RESET, "");
|
|
512
|
+
return color2(open ? content.replaceAll(FG_RESET, open) : content);
|
|
513
|
+
}
|
|
509
514
|
function customTransform(text) {
|
|
510
515
|
let result = text;
|
|
511
|
-
result = result.replace(/`([^`]+)`/g, (_, code) => greenBright(code));
|
|
512
516
|
result = result.replace(/<<([^>]+)>>/g, (_, url) => blueBright(underline(url)));
|
|
513
517
|
result = result.replace(/<([^>]+)>/g, (_, path2) => blueBright(path2));
|
|
518
|
+
result = result.replace(/`([^`]+)`/g, (_, code) => nestColor(code, greenBright));
|
|
514
519
|
return result;
|
|
515
520
|
}
|
|
516
521
|
function formatTable(data, title) {
|
|
@@ -1180,12 +1185,12 @@ import { join as join3 } from "node:path";
|
|
|
1180
1185
|
|
|
1181
1186
|
// src/constants/server.ts
|
|
1182
1187
|
import { join as join2 } from "node:path";
|
|
1183
|
-
var
|
|
1184
|
-
var
|
|
1185
|
-
var
|
|
1188
|
+
var GAME_SERVER_ROOT_DIRECTORY = "server";
|
|
1189
|
+
var GAME_SERVER_LIB_DIRECTORY = join2(GAME_SERVER_ROOT_DIRECTORY, "lib");
|
|
1190
|
+
var GAME_HOOKS_FILE = "hooks";
|
|
1186
1191
|
|
|
1187
1192
|
// src/constants/api.ts
|
|
1188
|
-
var
|
|
1193
|
+
var GAME_API_ROUTES_DIRECTORY = join3(GAME_SERVER_ROOT_DIRECTORY, "api");
|
|
1189
1194
|
var AUTH_API_SUBDIRECTORY = "auth";
|
|
1190
1195
|
var SAMPLE_API_SUBDIRECTORY = "sample";
|
|
1191
1196
|
var SAMPLE_CUSTOM_FILENAME = "custom.ts";
|
|
@@ -1196,7 +1201,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
|
|
|
1196
1201
|
// ../better-auth/package.json
|
|
1197
1202
|
var package_default = {
|
|
1198
1203
|
name: "@playcademy/better-auth",
|
|
1199
|
-
version: "0.0.20-beta.
|
|
1204
|
+
version: "0.0.20-beta.3",
|
|
1200
1205
|
type: "module",
|
|
1201
1206
|
exports: {
|
|
1202
1207
|
"./server": {
|
|
@@ -1350,7 +1355,7 @@ var NON_INTERACTIVE_HINTS = {
|
|
|
1350
1355
|
PROJECT_DIRECTORY: "Pass a project directory argument or run interactively.",
|
|
1351
1356
|
MIGRATE_MODE: "Pass --migrate keep or --migrate replace.",
|
|
1352
1357
|
BUILD_PATH: "Pass --build or set buildPath in playcademy.config.",
|
|
1353
|
-
|
|
1358
|
+
GAME_METADATA_DRIFT: "Resolve game metadata drift interactively or update local config first.",
|
|
1354
1359
|
TIMEBACK_DRIFT: "Resolve TimeBack config drift interactively before deploying."
|
|
1355
1360
|
};
|
|
1356
1361
|
|
|
@@ -1612,6 +1617,40 @@ function validateQueueKey(queueKey) {
|
|
|
1612
1617
|
}
|
|
1613
1618
|
|
|
1614
1619
|
// src/lib/config/loader.ts
|
|
1620
|
+
function validateDashboardConfig(dashboard) {
|
|
1621
|
+
if (dashboard === true || dashboard === false) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
if (typeof dashboard !== "object" || dashboard === null || Array.isArray(dashboard)) {
|
|
1625
|
+
throw new ConfigError(
|
|
1626
|
+
'The "dashboard" field must be a boolean or an object',
|
|
1627
|
+
"dashboard",
|
|
1628
|
+
"Use dashboard: true, or dashboard: { directory: './dashboard' }"
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
const config = dashboard;
|
|
1632
|
+
if (config.directory !== void 0 && typeof config.directory !== "string") {
|
|
1633
|
+
throw new ConfigError(
|
|
1634
|
+
'The "dashboard.directory" field must be a string',
|
|
1635
|
+
"dashboard.directory",
|
|
1636
|
+
"Use a path relative to your config file: directory: './dashboard'"
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
if (config.buildCommand !== void 0 && (!Array.isArray(config.buildCommand) || config.buildCommand.some((part) => typeof part !== "string"))) {
|
|
1640
|
+
throw new ConfigError(
|
|
1641
|
+
'The "dashboard.buildCommand" field must be an array of strings',
|
|
1642
|
+
"dashboard.buildCommand",
|
|
1643
|
+
"Use command array form: buildCommand: ['bun', 'run', 'build']"
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
if (config.buildPath !== void 0 && typeof config.buildPath !== "string") {
|
|
1647
|
+
throw new ConfigError(
|
|
1648
|
+
'The "dashboard.buildPath" field must be a string',
|
|
1649
|
+
"dashboard.buildPath",
|
|
1650
|
+
"Use a path relative to the dashboard directory: buildPath: 'dist'"
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1615
1654
|
function validateQueuesConfig(configName, queues) {
|
|
1616
1655
|
if (typeof queues !== "object" || queues === null || Array.isArray(queues)) {
|
|
1617
1656
|
throw new ConfigError(
|
|
@@ -1845,6 +1884,9 @@ function validateConfig(config) {
|
|
|
1845
1884
|
'Add a name property to your config: name: "My App Name"'
|
|
1846
1885
|
);
|
|
1847
1886
|
}
|
|
1887
|
+
if (cfg.dashboard !== void 0) {
|
|
1888
|
+
validateDashboardConfig(cfg.dashboard);
|
|
1889
|
+
}
|
|
1848
1890
|
if (cfg.integrations) {
|
|
1849
1891
|
if (typeof cfg.integrations !== "object") {
|
|
1850
1892
|
throw new ConfigError(
|
|
@@ -2546,7 +2588,7 @@ async function promptForTemplate(framework) {
|
|
|
2546
2588
|
});
|
|
2547
2589
|
}
|
|
2548
2590
|
|
|
2549
|
-
// src/lib/deploy/godot.ts
|
|
2591
|
+
// src/lib/deploy/game/godot.ts
|
|
2550
2592
|
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync } from "fs";
|
|
2551
2593
|
import { join as join10 } from "path";
|
|
2552
2594
|
function isGodotProject() {
|
|
@@ -2559,11 +2601,10 @@ import { execSync as execSync3 } from "child_process";
|
|
|
2559
2601
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2560
2602
|
import path from "node:path";
|
|
2561
2603
|
var viteConfigTemplate = loadTemplateString("config/vite-config.ts");
|
|
2562
|
-
async function findViteConfig() {
|
|
2563
|
-
const workspace = getWorkspace();
|
|
2604
|
+
async function findViteConfig(cwd = getWorkspace()) {
|
|
2564
2605
|
const result = await findFile(
|
|
2565
2606
|
["vite.config.ts", "vite.config.js", "vite.config.mts", "vite.config.mjs"],
|
|
2566
|
-
{ cwd
|
|
2607
|
+
{ cwd }
|
|
2567
2608
|
);
|
|
2568
2609
|
return result?.path ?? null;
|
|
2569
2610
|
}
|
|
@@ -2572,23 +2613,22 @@ function isPluginConfigured(configPath) {
|
|
|
2572
2613
|
return content.includes("@playcademy/vite-plugin") || content.includes("playcademy(");
|
|
2573
2614
|
}
|
|
2574
2615
|
async function addVitePlugin(options = {}) {
|
|
2575
|
-
const { install = false } = options;
|
|
2576
|
-
const
|
|
2577
|
-
const pkgPath = path.join(workspace, "package.json");
|
|
2616
|
+
const { install = false, dir = getWorkspace() } = options;
|
|
2617
|
+
const pkgPath = path.join(dir, "package.json");
|
|
2578
2618
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
2579
2619
|
const hasPlugin = pkg.dependencies?.["@playcademy/vite-plugin"] || pkg.devDependencies?.["@playcademy/vite-plugin"];
|
|
2580
2620
|
if (hasPlugin) {
|
|
2581
2621
|
return false;
|
|
2582
2622
|
}
|
|
2583
2623
|
if (install) {
|
|
2584
|
-
const pm = detectPackageManager(
|
|
2624
|
+
const pm = detectPackageManager(dir);
|
|
2585
2625
|
const command = getAddDevDependencyCommand(pm, "@playcademy/vite-plugin");
|
|
2586
2626
|
try {
|
|
2587
2627
|
await runStep(
|
|
2588
2628
|
"Installing @playcademy/vite-plugin...",
|
|
2589
2629
|
async () => {
|
|
2590
2630
|
execSync3(command, {
|
|
2591
|
-
cwd:
|
|
2631
|
+
cwd: dir,
|
|
2592
2632
|
stdio: ["ignore", "ignore", "ignore"]
|
|
2593
2633
|
});
|
|
2594
2634
|
},
|
|
@@ -2607,7 +2647,7 @@ async function addVitePlugin(options = {}) {
|
|
|
2607
2647
|
}
|
|
2608
2648
|
return true;
|
|
2609
2649
|
}
|
|
2610
|
-
async function updateViteConfig(configPath) {
|
|
2650
|
+
async function updateViteConfig(configPath, pluginCall = "playcademy()") {
|
|
2611
2651
|
let content = readFileSync3(configPath, "utf8");
|
|
2612
2652
|
if (!content.includes("@playcademy/vite-plugin")) {
|
|
2613
2653
|
const importMatch = content.match(/import .+ from ['"]vite['"]/);
|
|
@@ -2621,17 +2661,17 @@ import { playcademy } from '@playcademy/vite-plugin'${content.slice(importIndex)
|
|
|
2621
2661
|
${content}`;
|
|
2622
2662
|
}
|
|
2623
2663
|
}
|
|
2624
|
-
if (!content.includes("playcademy(
|
|
2664
|
+
if (!content.includes("playcademy(")) {
|
|
2625
2665
|
const pluginsMatch = content.match(/plugins:\s*\[/);
|
|
2626
2666
|
if (pluginsMatch) {
|
|
2627
2667
|
const pluginsIndex = pluginsMatch.index + pluginsMatch[0].length;
|
|
2628
|
-
content = `${content.slice(0, pluginsIndex)}
|
|
2668
|
+
content = `${content.slice(0, pluginsIndex)}${pluginCall},${content.slice(pluginsIndex)}`;
|
|
2629
2669
|
} else {
|
|
2630
2670
|
const defineConfigMatch = content.match(/defineConfig\(\{/);
|
|
2631
2671
|
if (defineConfigMatch) {
|
|
2632
2672
|
const configIndex = defineConfigMatch.index + defineConfigMatch[0].length;
|
|
2633
2673
|
content = `${content.slice(0, configIndex)}
|
|
2634
|
-
plugins: [
|
|
2674
|
+
plugins: [${pluginCall}],${content.slice(configIndex)}`;
|
|
2635
2675
|
}
|
|
2636
2676
|
}
|
|
2637
2677
|
}
|
|
@@ -2841,14 +2881,14 @@ ${socialProviders.join(",\n")}
|
|
|
2841
2881
|
return authContent;
|
|
2842
2882
|
}
|
|
2843
2883
|
function scaffoldAuthConfig(workspace, authContent) {
|
|
2844
|
-
const libDir = join12(workspace,
|
|
2884
|
+
const libDir = join12(workspace, GAME_SERVER_LIB_DIRECTORY);
|
|
2845
2885
|
if (!existsSync10(libDir)) {
|
|
2846
2886
|
mkdirSync4(libDir, { recursive: true });
|
|
2847
2887
|
}
|
|
2848
2888
|
writeFileSync5(join12(libDir, AUTH_CONFIG_FILE), authContent);
|
|
2849
2889
|
}
|
|
2850
2890
|
function scaffoldAuthRoutes(workspace) {
|
|
2851
|
-
const apiDir = join12(workspace,
|
|
2891
|
+
const apiDir = join12(workspace, GAME_API_ROUTES_DIRECTORY);
|
|
2852
2892
|
const authApiDir = join12(apiDir, AUTH_API_SUBDIRECTORY);
|
|
2853
2893
|
if (!existsSync10(authApiDir)) {
|
|
2854
2894
|
mkdirSync4(authApiDir, { recursive: true });
|
|
@@ -2875,7 +2915,7 @@ export * from './auth'
|
|
|
2875
2915
|
}
|
|
2876
2916
|
}
|
|
2877
2917
|
function scaffoldProtectedExample(workspace) {
|
|
2878
|
-
const apiDir = join12(workspace,
|
|
2918
|
+
const apiDir = join12(workspace, GAME_API_ROUTES_DIRECTORY);
|
|
2879
2919
|
const sampleDir = join12(apiDir, SAMPLE_API_SUBDIRECTORY);
|
|
2880
2920
|
if (!existsSync10(sampleDir)) {
|
|
2881
2921
|
mkdirSync4(sampleDir, { recursive: true });
|
|
@@ -2951,7 +2991,7 @@ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as re
|
|
|
2951
2991
|
import { join as join13 } from "node:path";
|
|
2952
2992
|
|
|
2953
2993
|
// src/version.ts
|
|
2954
|
-
var cliVersion = false ? "0.0.0-dev" : "0.27.1-beta.
|
|
2994
|
+
var cliVersion = false ? "0.0.0-dev" : "0.27.1-beta.4";
|
|
2955
2995
|
|
|
2956
2996
|
// src/lib/init/database.ts
|
|
2957
2997
|
var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
|
|
@@ -3039,8 +3079,7 @@ async function setupPackageJson2(workspace, appName, stepOptions) {
|
|
|
3039
3079
|
return true;
|
|
3040
3080
|
}
|
|
3041
3081
|
}
|
|
3042
|
-
function hasDatabaseSetup() {
|
|
3043
|
-
const workspace = getWorkspace();
|
|
3082
|
+
function hasDatabaseSetup(workspace = getWorkspace()) {
|
|
3044
3083
|
return DRIZZLE_CONFIG_FILES.some((filename) => existsSync11(join13(workspace, filename)));
|
|
3045
3084
|
}
|
|
3046
3085
|
|
|
@@ -3301,7 +3340,7 @@ async function promptForCustomRoutes(requiresRoutes = false) {
|
|
|
3301
3340
|
}
|
|
3302
3341
|
const directory = await input({
|
|
3303
3342
|
message: "API routes directory:",
|
|
3304
|
-
default:
|
|
3343
|
+
default: GAME_API_ROUTES_DIRECTORY,
|
|
3305
3344
|
nonInteractiveHint: NON_INTERACTIVE_HINTS.API_ROUTES_DIRECTORY,
|
|
3306
3345
|
validate: (value) => {
|
|
3307
3346
|
if (!value || value.trim().length === 0) {
|
|
@@ -3364,22 +3403,24 @@ function errorMessage(err) {
|
|
|
3364
3403
|
return err instanceof Error ? err.message : String(err);
|
|
3365
3404
|
}
|
|
3366
3405
|
|
|
3367
|
-
// src/lib/bundle/backend.ts
|
|
3406
|
+
// src/lib/bundle/game-backend.ts
|
|
3368
3407
|
import { existsSync as existsSync13 } from "node:fs";
|
|
3369
3408
|
import { join as join15 } from "node:path";
|
|
3370
3409
|
var HOOKS_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
|
|
3371
|
-
function
|
|
3410
|
+
function resolveGameRoutesDir(config) {
|
|
3372
3411
|
const customRoutes = config?.integrations?.customRoutes;
|
|
3373
|
-
|
|
3374
|
-
|
|
3412
|
+
return typeof customRoutes === "object" && customRoutes.directory || GAME_API_ROUTES_DIRECTORY;
|
|
3413
|
+
}
|
|
3414
|
+
function getGameRoutesDirectory(projectPath, config) {
|
|
3415
|
+
return join15(projectPath, resolveGameRoutesDir(config));
|
|
3375
3416
|
}
|
|
3376
|
-
function
|
|
3377
|
-
const customRoutesDir =
|
|
3417
|
+
function hasGameCustomRoutes(projectPath, config) {
|
|
3418
|
+
const customRoutesDir = getGameRoutesDirectory(projectPath, config);
|
|
3378
3419
|
return existsSync13(customRoutesDir);
|
|
3379
3420
|
}
|
|
3380
|
-
function
|
|
3421
|
+
function hasGameHooksModule(workspace) {
|
|
3381
3422
|
return HOOKS_EXTENSIONS.some(
|
|
3382
|
-
(ext) => existsSync13(join15(workspace,
|
|
3423
|
+
(ext) => existsSync13(join15(workspace, GAME_SERVER_ROOT_DIRECTORY, `${GAME_HOOKS_FILE}${ext}`))
|
|
3383
3424
|
);
|
|
3384
3425
|
}
|
|
3385
3426
|
|
|
@@ -3409,27 +3450,27 @@ function hasKVSetup(config) {
|
|
|
3409
3450
|
init_file_loader();
|
|
3410
3451
|
import { existsSync as existsSync14, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
3411
3452
|
import { join as join16 } from "path";
|
|
3412
|
-
function hasAllRequiredIncludes(config) {
|
|
3453
|
+
function hasAllRequiredIncludes(config, required) {
|
|
3413
3454
|
if (!config.include) {
|
|
3414
3455
|
return false;
|
|
3415
3456
|
}
|
|
3416
|
-
return
|
|
3457
|
+
return required.every((entry) => config.include.includes(entry));
|
|
3417
3458
|
}
|
|
3418
|
-
function getMissingIncludes(config) {
|
|
3459
|
+
function getMissingIncludes(config, required) {
|
|
3419
3460
|
if (!config.include) {
|
|
3420
|
-
return [...
|
|
3461
|
+
return [...required];
|
|
3421
3462
|
}
|
|
3422
|
-
return
|
|
3463
|
+
return required.filter((entry) => !config.include.includes(entry));
|
|
3423
3464
|
}
|
|
3424
|
-
function addRequiredIncludes(config) {
|
|
3465
|
+
function addRequiredIncludes(config, required) {
|
|
3425
3466
|
if (!config.include) {
|
|
3426
3467
|
config.include = [];
|
|
3427
3468
|
}
|
|
3428
|
-
for (const entry of getMissingIncludes(config)) {
|
|
3469
|
+
for (const entry of getMissingIncludes(config, required)) {
|
|
3429
3470
|
config.include.push(entry);
|
|
3430
3471
|
}
|
|
3431
3472
|
}
|
|
3432
|
-
function updateExistingIncludeArray(content) {
|
|
3473
|
+
function updateExistingIncludeArray(content, required) {
|
|
3433
3474
|
const includeRegex = /"include"\s*:\s*\[([^\]]*)\]/;
|
|
3434
3475
|
const match = content.match(includeRegex);
|
|
3435
3476
|
if (!match || !match[1]) {
|
|
@@ -3437,9 +3478,7 @@ function updateExistingIncludeArray(content) {
|
|
|
3437
3478
|
}
|
|
3438
3479
|
const fullMatch = match[0];
|
|
3439
3480
|
const arrayContents = match[1];
|
|
3440
|
-
const missingEntries =
|
|
3441
|
-
(entry) => !arrayContents.includes(entry)
|
|
3442
|
-
);
|
|
3481
|
+
const missingEntries = required.filter((entry) => !arrayContents.includes(entry));
|
|
3443
3482
|
if (missingEntries.length === 0) {
|
|
3444
3483
|
return content;
|
|
3445
3484
|
}
|
|
@@ -3459,7 +3498,7 @@ function updateExistingIncludeArray(content) {
|
|
|
3459
3498
|
const newIncludeArray = `"include": [${newArrayContents}]`;
|
|
3460
3499
|
return content.replace(fullMatch, newIncludeArray);
|
|
3461
3500
|
}
|
|
3462
|
-
function addNewIncludeProperty(content) {
|
|
3501
|
+
function addNewIncludeProperty(content, required) {
|
|
3463
3502
|
const closingBraceMatch = content.match(/\n(\s*)\}(\s*)$/);
|
|
3464
3503
|
if (!closingBraceMatch || closingBraceMatch.index === void 0) {
|
|
3465
3504
|
return null;
|
|
@@ -3469,17 +3508,17 @@ function addNewIncludeProperty(content) {
|
|
|
3469
3508
|
const insertPosition = closingBraceMatch.index;
|
|
3470
3509
|
const beforeClosing = content.slice(0, insertPosition).trim();
|
|
3471
3510
|
const needsComma = beforeClosing.endsWith("]") || beforeClosing.endsWith("}") || beforeClosing.endsWith('"');
|
|
3472
|
-
const entriesStr =
|
|
3511
|
+
const entriesStr = required.map((e) => `"${e}"`).join(", ");
|
|
3473
3512
|
const comma = needsComma ? "," : "";
|
|
3474
3513
|
const includeEntry = `${comma}
|
|
3475
3514
|
${propertyIndent}"include": [${entriesStr}]`;
|
|
3476
3515
|
const updatedContent = content.slice(0, insertPosition) + includeEntry + content.slice(insertPosition);
|
|
3477
3516
|
return updatedContent;
|
|
3478
3517
|
}
|
|
3479
|
-
function addToIncludeArrayPreservingComments(content) {
|
|
3480
|
-
return updateExistingIncludeArray(content) || addNewIncludeProperty(content);
|
|
3518
|
+
function addToIncludeArrayPreservingComments(content, required) {
|
|
3519
|
+
return updateExistingIncludeArray(content, required) || addNewIncludeProperty(content, required);
|
|
3481
3520
|
}
|
|
3482
|
-
async function ensureTsconfigIncludes(workspace) {
|
|
3521
|
+
async function ensureTsconfigIncludes(workspace, required = TSCONFIG_REQUIRED_INCLUDES) {
|
|
3483
3522
|
for (const filename of TSCONFIG_FILES) {
|
|
3484
3523
|
const configPath = join16(workspace, filename);
|
|
3485
3524
|
if (existsSync14(configPath)) {
|
|
@@ -3489,19 +3528,22 @@ async function ensureTsconfigIncludes(workspace) {
|
|
|
3489
3528
|
stripComments: true
|
|
3490
3529
|
});
|
|
3491
3530
|
if (config && (!config.references || filename === TSCONFIG_JSON)) {
|
|
3492
|
-
if (hasAllRequiredIncludes(config)) {
|
|
3531
|
+
if (hasAllRequiredIncludes(config, required)) {
|
|
3493
3532
|
return filename;
|
|
3494
3533
|
}
|
|
3495
3534
|
try {
|
|
3496
3535
|
const rawContent = readFileSync8(configPath, "utf8");
|
|
3497
|
-
const updatedContent = addToIncludeArrayPreservingComments(
|
|
3536
|
+
const updatedContent = addToIncludeArrayPreservingComments(
|
|
3537
|
+
rawContent,
|
|
3538
|
+
required
|
|
3539
|
+
);
|
|
3498
3540
|
if (updatedContent && updatedContent !== rawContent) {
|
|
3499
3541
|
writeFileSync8(configPath, updatedContent);
|
|
3500
3542
|
return filename;
|
|
3501
3543
|
}
|
|
3502
3544
|
} catch {
|
|
3503
3545
|
}
|
|
3504
|
-
addRequiredIncludes(config);
|
|
3546
|
+
addRequiredIncludes(config, required);
|
|
3505
3547
|
writeFileSync8(configPath, `${JSON.stringify(config, null, 4)}
|
|
3506
3548
|
`);
|
|
3507
3549
|
return filename;
|
|
@@ -3520,9 +3562,9 @@ function detectBackendFeatures(workspace, config) {
|
|
|
3520
3562
|
hasDB: hasDatabaseSetup(),
|
|
3521
3563
|
hasKV: hasKVSetup(config),
|
|
3522
3564
|
hasBucket: hasBucketSetup(config),
|
|
3523
|
-
hasRoutes:
|
|
3565
|
+
hasRoutes: hasGameCustomRoutes(workspace, config),
|
|
3524
3566
|
hasQueues: hasQueueSetup(config),
|
|
3525
|
-
hasHooks:
|
|
3567
|
+
hasHooks: hasGameHooksModule(workspace),
|
|
3526
3568
|
hasSecrets: hasEnvFile(workspace)
|
|
3527
3569
|
};
|
|
3528
3570
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -7,7 +7,11 @@ export { GAME_WORKER_DOMAINS, PLAYCADEMY_BASE_URLS, PLAYCADEMY_DOMAINS } from '@
|
|
|
7
7
|
* Default directory for custom API routes
|
|
8
8
|
* Used when integrations.customRoutes.directory is not specified in config
|
|
9
9
|
*/
|
|
10
|
-
declare const
|
|
10
|
+
declare const GAME_API_ROUTES_DIRECTORY: string;
|
|
11
|
+
/**
|
|
12
|
+
* Route directory inside a dashboard app (relative to the dashboard root)
|
|
13
|
+
*/
|
|
14
|
+
declare const DASHBOARD_API_ROUTES_DIRECTORY = "api";
|
|
11
15
|
/**
|
|
12
16
|
* Auth routes subdirectory name within API directory (fixed structure)
|
|
13
17
|
*/
|
|
@@ -75,7 +79,7 @@ declare const DEFAULT_BUCKET_DIRECTORY = "bucket";
|
|
|
75
79
|
* Cloudflare-related constants
|
|
76
80
|
*
|
|
77
81
|
* Worker runtime pins (`CLOUDFLARE_COMPATIBILITY_DATE`,
|
|
78
|
-
* `
|
|
82
|
+
* `WORKER_COMPATIBILITY_FLAGS`) live in `@playcademy/constants` so every
|
|
79
83
|
* package that deploys or emulates workers (CLI, cloudflare, sdk, api-core)
|
|
80
84
|
* agrees on a single source of truth. Import them from there directly.
|
|
81
85
|
*/
|
|
@@ -310,12 +314,25 @@ declare const CLI_FILES: {
|
|
|
310
314
|
|
|
311
315
|
/**
|
|
312
316
|
* Default port numbers for local development servers
|
|
317
|
+
*
|
|
318
|
+
* Canonical on purpose: picking a free port at startup caused port climbing
|
|
319
|
+
* across dev-server restarts (8788 → 8789 → …) while the old process was
|
|
320
|
+
* still releasing its bind. Every server binds its canonical port and waits
|
|
321
|
+
* for it to free instead, so URLs stay stable between runs.
|
|
313
322
|
*/
|
|
314
323
|
declare const DEFAULT_PORTS: {
|
|
315
324
|
/** Sandbox server (mock platform API) */
|
|
316
325
|
readonly SANDBOX: 4321;
|
|
317
326
|
/** Backend dev server (project backend with HMR) */
|
|
318
|
-
readonly
|
|
327
|
+
readonly GAME: 8788;
|
|
328
|
+
/** Dashboard worker dev server (composed dashboard runtime + routes) */
|
|
329
|
+
readonly DASHBOARD: 8789;
|
|
330
|
+
/**
|
|
331
|
+
* Dashboard app dev server (the dashboard's own Vite). Deliberately in
|
|
332
|
+
* the 87xx family with the other backend ports — 5174 collided with the
|
|
333
|
+
* platform hub's local dev server.
|
|
334
|
+
*/
|
|
335
|
+
readonly DASHBOARD_APP: 8790;
|
|
319
336
|
};
|
|
320
337
|
|
|
321
338
|
/**
|
|
@@ -334,7 +351,7 @@ declare const NON_INTERACTIVE_HINTS: {
|
|
|
334
351
|
readonly PROJECT_DIRECTORY: 'Pass a project directory argument or run interactively.';
|
|
335
352
|
readonly MIGRATE_MODE: 'Pass --migrate keep or --migrate replace.';
|
|
336
353
|
readonly BUILD_PATH: 'Pass --build or set buildPath in playcademy.config.';
|
|
337
|
-
readonly
|
|
354
|
+
readonly GAME_METADATA_DRIFT: 'Resolve game metadata drift interactively or update local config first.';
|
|
338
355
|
readonly TIMEBACK_DRIFT: 'Resolve TimeBack config drift interactively before deploying.';
|
|
339
356
|
};
|
|
340
357
|
|
|
@@ -347,15 +364,15 @@ declare const NON_INTERACTIVE_HINTS: {
|
|
|
347
364
|
/**
|
|
348
365
|
* Server root directory (fixed location)
|
|
349
366
|
*/
|
|
350
|
-
declare const
|
|
367
|
+
declare const GAME_SERVER_ROOT_DIRECTORY = "server";
|
|
351
368
|
/**
|
|
352
369
|
* Server library/utilities directory (fixed location)
|
|
353
370
|
*/
|
|
354
|
-
declare const
|
|
371
|
+
declare const GAME_SERVER_LIB_DIRECTORY: string;
|
|
355
372
|
/**
|
|
356
373
|
* Hooks file basename (without extension) inside server/
|
|
357
374
|
*/
|
|
358
|
-
declare const
|
|
375
|
+
declare const GAME_HOOKS_FILE = "hooks";
|
|
359
376
|
/**
|
|
360
377
|
* Public assets directory (fixed location)
|
|
361
378
|
*/
|
|
@@ -376,4 +393,4 @@ declare const CONFIG_FILE_NAMES: string[];
|
|
|
376
393
|
*/
|
|
377
394
|
declare const DOCS_URL = "https://docs.dev.playcademy.net/platform";
|
|
378
395
|
|
|
379
|
-
export { AUTH_API_SUBDIRECTORY, AUTH_CONFIG_FILE, AUTH_PROVIDER_NAMES, BETTER_AUTH_VERSION, BUCKET_ALWAYS_SKIP, CALLBACK_PATH, CALLBACK_PORT, CLI_DEFAULT_OUTPUTS, CLI_DIRECTORIES, CLI_FILES, CLI_USER_DIRECTORIES, CLOUDFLARE_BINDINGS, CONFIG_FILE_NAMES,
|
|
396
|
+
export { AUTH_API_SUBDIRECTORY, AUTH_CONFIG_FILE, AUTH_PROVIDER_NAMES, BETTER_AUTH_VERSION, BUCKET_ALWAYS_SKIP, CALLBACK_PATH, CALLBACK_PORT, CLI_DEFAULT_OUTPUTS, CLI_DIRECTORIES, CLI_FILES, CLI_USER_DIRECTORIES, CLOUDFLARE_BINDINGS, CONFIG_FILE_NAMES, DASHBOARD_API_ROUTES_DIRECTORY, DB_FILES, DEFAULT_BUCKET_DIRECTORY, DEFAULT_DATABASE_DIRECTORY, DEFAULT_PORTS, DEFAULT_TEMPLATE_REF, DOCS_URL, DRIZZLE_CONFIG_FILES, ENV_EXAMPLE_FILE, ENV_FILES, GAME_API_ROUTES_DIRECTORY, GAME_HOOKS_FILE, GAME_SERVER_LIB_DIRECTORY, GAME_SERVER_ROOT_DIRECTORY, GODOT_ADDON_URL, GODOT_BUILD_DIRECTORIES, GODOT_BUILD_OUTPUTS, GODOT_EXECUTABLE_PATTERNS, GODOT_EXPORT_PRESETS_FILE, GODOT_PROJECT_FILE, GODOT_WEB_PLATFORM, MINIFLARE_D1_DIRECTORY, NON_INTERACTIVE_HINTS, OAUTH_CALLBACK_URL_PATTERN, PLACEHOLDER_APP_URL, PLAYCADEMY_AUTH_VERSION, PRESIGNED_UPLOAD_THRESHOLD, PUBLIC_DIRECTORY, QUEUE_NAME_PREFIX, SAMPLE_API_SUBDIRECTORY, SAMPLE_BUCKET_FILENAME, SAMPLE_CUSTOM_FILENAME, SAMPLE_DATABASE_FILENAME, SAMPLE_KV_FILENAME, SCHEMA_SUBDIRECTORY, SELF_WORKER_NAME_BINDING, SSO_AUTH_TIMEOUT_MS, TEMPLATE_RENAME_FILES, TEMPLATE_REPOS, TSCONFIG_APP_JSON, TSCONFIG_FILES, TSCONFIG_JSON, TSCONFIG_REQUIRED_INCLUDES, WORKSPACE_NAME, cacheVersionDir, getConfigDir, nativeBinaryPath };
|